Manchmal unterscheiden sich zwei geöffnete Dateien um kein einziges sichtbares Zeichen, doch diff meldet, dass sich jede Zeile geändert hat, oder dass nur die erste oder die letzte Zeile verschieden ist. Die Ursache sind meist Zeichen, die auf dem Bildschirm nicht zu sehen sind. Dieser Artikel stellt die Arten solcher „unsichtbaren Unterschiede“ und ihre Lösungen vor.
Zeilenumbruchzeichen: CRLF, LF, CR
Wie eine Zeile beendet wird, ist je nach Betriebssystem unterschiedlich geregelt.
| Bezeichnung | Bytes | Vor allem verwendet in |
|---|---|---|
| LF | 0A (\n) | Linux, macOS, den meisten Entwicklerwerkzeugen |
| CRLF | 0D 0A (\r\n) | vielen Windows-Programmen wie dem Editor, Internetprotokollen wie E-Mail und HTTP |
| CR | 0D (\r) | sehr altem Mac OS (bis Version 9) |
Ist eine Datei mit CRLF und die andere mit LF gespeichert, wird beim zeilenweisen Vergleich an jedem Zeilenende ein unsichtbares \r als Unterschied erkannt, und alles scheint geändert. GNU diff kann diesen Unterschied mit --strip-trailing-cr, git diff mit --ignore-cr-at-eol ignorieren.
Zeilenumbruch am Dateiende
Nach POSIX-Konvention endet jede Zeile einer Textdatei mit einem Zeilenumbruchzeichen. Viele Werkzeuge setzen deshalb auch hinter die letzte Zeile einen Umbruch, manche Editoren aber nicht. Im Unified Diff wird dieser Unterschied so dargestellt:
@@ -1,2 +1,2 @@ host=example.com-port=8080\ No newline at end of file+port=8080
\ No newline at end of file bedeutet, dass am Ende der Zeile direkt darüber kein Zeilenumbruch steht. Im Beispiel ist der Inhalt gleich; nur die neue Datei hat einen abschließenden Zeilenumbruch bekommen. Viele Code-Styleguides verlangen einen Umbruch am Dateiende, und mit der Editor-Einstellung „Zeilenumbruch am Dateiende einfügen“ lässt sich das vereinheitlichen.
BOM: wenn nur die erste Zeile abweicht
Manchmal stehen am Anfang einer UTF-8-Datei die drei Bytes EF BB BF. Das ist die Byte-Order-Mark (BOM). UTF-8 braucht keine Byte-Reihenfolge, aber manche Programme fügen sie als Kennzeichen „diese Datei ist UTF-8“ ein. Die BOM ist unsichtbar — meldet diff, dass nur die erste Zeile verschieden ist, sollten Sie an eine BOM denken. Diese Seite entfernt beim Laden einer Datei eine BOM am Anfang, und bei aktivierten Leerraum-Optionen wird eine BOM (U+FEFF) in eingefügtem Text ebenfalls wie Leerraum behandelt.
Tabs, Leerzeichen und besondere Leerzeichen
- Tab und Leerzeichen: Ein Tab und 4 Leerzeichen sehen auf dem Bildschirm gleich aus, sind aber verschiedene Zeichen.
- Geschütztes Leerzeichen (NBSP, U+00A0): Gerät oft mit Text aus Webseiten oder Textverarbeitungen hinein.
- Ideografisches Leerzeichen (U+3000): Kann über Eingabemethoden für Koreanisch, Japanisch und Chinesisch hineinkommen.
- Breitenloses Leerzeichen (U+200B): Hat die Breite 0 und ist völlig unsichtbar, zählt aber als Zeichen.
Die meisten dieser Zeichen werden herausgefiltert, wenn Sie die Leerraum-Optionen aktivieren (Zeichen wie U+200B, die nicht als Leerraum klassifiziert sind, können allerdings auch dann bestehen bleiben). Stellen Sie den Vergleich innerhalb der Zeile auf Zeichen um, zeigt die Hervorhebung, an welcher Stelle ein unsichtbares Zeichen steht.
Unicode-Normalisierung: gleiches Zeichen, andere Bytes
Das „ü“ lässt sich als ein einziges Zeichen (U+00FC) schreiben oder als „u“ (U+0075), gefolgt vom kombinierenden Trema (U+0308). Ebenso kann die koreanische Silbe „가“ als ein vorkomponiertes Zeichen (U+AC00) oder als Folge der Jamo ㄱ (U+1100) und ㅏ (U+1161) dargestellt werden. Unicode nennt die erste Form NFC und die zweite NFD (UAX #15). Die beiden Texte sehen auf dem Bildschirm identisch aus, sind beim Vergleich aber verschiedene Zeichen. Ein typisches Beispiel: Auf macOS erstellte Dateinamen erscheinen auf anderen Betriebssystemen manchmal in ihre Bestandteile zerlegt, etwa koreanische Silben als einzelne Jamo („ㄱㅏ“). Die Lösung: vor dem Vergleich eine Seite in NFC umwandeln, damit beide übereinstimmen.
Zeilenumbrüche in git vereinheitlichen
In Repositorys, an denen auf mehreren Betriebssystemen gearbeitet wird, lassen sich Zeilenumbrüche über git-Einstellungen vereinheitlichen.
core.autocrlf: Mittruewird beim Auschecken LF in CRLF und beim Committen CRLF in LF umgewandelt (vor allem unter Windows).inputwandelt nur beim Committen CRLF in LF um..gitattributes: Legt die Regeln pro Repository fest, sodass das Ergebnis auch bei unterschiedlichen persönlichen Einstellungen gleich ist.
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
text=auto normalisiert die Zeilenumbrüche von Dateien, die git als Textdateien erkennt, im Repository auf LF; eol=lf und eol=crlf legen fest, welche Zeilenumbrüche beim Auschecken ins Arbeitsverzeichnis verwendet werden.
In diesem Werkzeug ausprobieren
Zeilenenden normalisieren ist im Textvergleich standardmäßig aktiv: CRLF, CR und LF gelten als derselbe Umbruch, und ob am Dateiende ein Umbruch steht, wird ignoriert. Schalten Sie die Option aus, wird ein CR am Zeilenende als ␍ und eine letzte Zeile ohne Umbruch mit ∅ angezeigt, sodass Sie die bisher unsichtbaren Unterschiede sehen. Die Leerraum-Optionen erklärt Leerraum ignorieren, die Bedeutung der Symbole im Ergebnis Unified Diff lesen.