Der kleine Unterschied

Softwareentwickler schätzen diff , patch und darauf aufbauende Werkzeuge wie RCS oder CVS als unersetzliche Hilfsmittel. Aber selbst wer nicht programmiert, kann diff hin und wieder nutzbringend einsetzen.

vorlesen Druckansicht 7 Kommentare lesen
Lesezeit: 8 Min.
Von
  • Michael Riepe
Inhaltsverzeichnis

Einmal im Monat steht der Autor dieser Artikelreihe vor der Aufgabe, den von der iX-Redaktion bearbeiteten Text ein letztes Mal zu korrigieren, bevor er in Satz geht. Der erste Schritt dieser Arbeit besteht darin festzustellen, welche Passagen der Redakteur geändert hat - und dabei hilft diff.

Allerdings gilt es zunächst eine Hürde zu überwinden. Deutsch hat wenig Ähnlichkeit mit den synthetischen ‘Computersprachen’ mit stark eingeschränkter Syntax und Grammatik, bei denen fast jedes einzelne Zeichen sinngebende Funktion hat. Für die Bedeutung eines Satzes spielt es gar keine Rolle, wie zwei benachbarte Wörter voneinander getrennt sind - mit einem oder mehreren Leerzeichen, Tabulatoren oder Zeilenumbrüchen -, solange sie nur getrennt sind. Menschen sind in der Lage, ihre Wahrnehmung auf den Inhalt eines Textes zu beschränken und technisch-formale Einzelheiten zu ignorieren, diff hingegen kann nur Zeichen miteinander vergleichen.

Der Benutzer kann diff mit den Optionen -b (langer Name: --ignore-space-change) und -w (--ignore-all-space) etwas mehr Spielraum beim Vergleich von so genanntem ‘white space’ geben. Der Befehl diff -w ignoriert Leerzeichen sowie Tabulatoren ganz und vergleicht nur die übrigen Zeichen miteinander; der signifikante Unterschied zwischen ‘Werkstatt’ und ‘Werk statt’ entgeht ihm dadurch. Für Texte ist diff -b besser geeignet, weil es zwar auf das Vorhandensein beziehungsweise Fehlen von Zwischenräumen reagiert, nicht aber auf deren Zusammensetzung, mit einer Ausnahme: diff -b ignoriert genau wie diff -w White Space am Ende einer Zeile.

Genau genommen ist die Option -b ein Relikt aus der Zeit, als Programmierer ihre Software noch per UUCP oder Usenet unter die Anwender brachten und Umwandlungen von Leerzeichen und Tabulatoren während der Datenübertragung durchaus keine Seltenheit waren. Aus dem gleichen Grund kennt das Programm patch die Option -l beziehungsweise --ignore-whitespace, die es dem Benutzer erlaubt, einen Patch auch dann anzuwenden, wenn die in ihm enthaltenen Leerräume nicht exakt mit denen der zu patchenden Datei übereinstimmen.

Neu formatierte Absätze stellen für diff eine besondere Herausforderung dar. Zwar sind die Wörter noch dieselben, aber das Entfernen oder Hinzufügen von Zeilenumbrüchen bringt das Programm so aus dem Tritt, dass es schlimmstenfalls gar keine Gemeinsamkeiten mehr feststellen kann. Es erzeugt zwar eine Ausgabe, die - im technischen Sinne - korrekt ist, aber diese ist länger als die beiden Ausgangsdateien zusammengenommen und unübersichtlich.

Geht es dem Anwender darum, einen Überblick über die Änderungen zu erhalten, muss er andere Mittel anwenden. So kann er beide zu vergleichenden Dateien neu formatieren und diff auf die Resultate anwenden (Listing 1).

Mehr Infos

Listing 1

Gleichart: Nach dem Formatieren ist der Vergleich der Dateien aussagekräftiger.

$ fmt Datei.alt > tmp1
$ fmt Datei.neu > tmp2
$ diff tmp1 tmp2

Die Befehlsfolge lässt sich verkürzen, indem man die Tatsache ausnutzt, dass diff eine der Eingabedateien von der Standardeingabe lesen kann (Listing 2).

Mehr Infos

Listing 2

Umweg: Der eine Kandidat kann direkt über die Standardeingabe kommen.

$ fmt Datei.alt > tmpa
$ fmt Datei.neu | diff tmpa -

Ein einzelnes Minuszeichen signalisiert diff, dass die zweite Eingabedatei auf der Standardeingabe zum Lesen bereit steht.

Noch eleganter lässt sich die Aufgabe in der Kornshell und in Version 2 der GNU-Shell bash mit der so genannten ‘process substitution’ formulieren:

$ diff <(fmt Datei.alt) <(fmt Datei.neu)

Die Shell führt die Anweisungen fmt Datei.alt und fmt Datei.neu im Hintergrund aus und leitet ihre Ausgaben in zwei Pseudodateien um, deren Namen sie als Argumente an diff weiterleitet. Je nach Betriebssystem kommen als Pseudodateien entweder Pipes oder die als ‘Named Pipes’ bekannten FIFOs zum Einsatz.

Sind Inhalt und Format des Textes verändert, empfiehlt sich der Einsatz von wdiff. Dieses wenig bekannte GNU-Programm - ein Unix-Pendant existiert nicht - vergleicht Texte wortweise miteinander. Seine Ausgabe enthält jedoch nicht nur die Unterschiede, sondern den vollständigen Text; gelöschte und hinzugefügte Wörter hebt wdiff durch Markierungen hervor.

Die Default-Markierungen ‘[-‘, ‘-]’, ‘{+’ und ‘+}’ kann der Benutzer mit den Optionen -w, -x, -y und -z beziehungsweise deren ‘langen’ Äquivalenten --start-delete, - -end-delete, --start-insert und --end-insert nach Belieben ändern - etwa in TeX-Befehle, SGML- oder XML-Tags, Steuerzeichen für einen Drucker oder ein Terminal. Letztere sollte wdiff sogar automatisch in der systemeigenen termcap- oder terminfo-Datenbank nachschlagen, wenn der Anwender beim Start die Option -t oder --terminal verwendet. Leider funktioniert diese in der verbreiteten Version 0.5 des Programms nur, wenn der Systemverwalter beim Übersetzen des Quelltextes die für GNU-Software typische automatische Konfiguration austrickst.

Ruft der Benutzer wdiff mit der Option -p (--printer) auf, bereitet das Programm die Ausgabe so auf, dass ein Zeilendrucker gelöschte Wörter unterstrichen und hinzugefügte fett darstellt. Auf PostScript-Druckern hängt die Art der Darstellung davon ab, mit welchem Programm der Rechner Textdateien umwandelt; die unter Linux häufig eingesetzten Filterprogramme a2ps und GNU enscript sind beide in der Lage, die Ausgabe von wdiff -p zu verarbeiten.

Speziell für den Pager less ist die Option -l (--less-mode) gedacht. Sie funktioniert ähnlich wie -p, markiert jedoch zusätzlich die Wortzwischenräume, wenn mehrere geänderte Wörter aufeinander folgen. Auf Terminals oder Terminalemulatoren (xterm) sieht diese Darstellungsform etwas gefälliger aus - im Gegensatz zu einem Drucker beherrscht ein Terminal weder Unterstreichungen noch Kursivschrift, deshalb benutzt less als Ersatz inverse Darstellung.

Streng genommen bildet wdiff nicht die ‘Differenz’ zweier Dateien, sondern ihre ‘Vereinigungsmenge’ (englisch ‘union’) - beide Ausgangsdateien sind vollständig in der Ausgabe enthalten. Diff ist ebenfalls in der Lage, zwei ähnliche Dateien zu einer dritten ‘zusammenzumischen’ (‘to merge’), jedoch nur zeilen- und nicht wortweise.

Der Befehl diff -D VARIABLE dürfte manchem C-Programmierer geläufig sein. Er liest zwei Versionen eines Programms und konstruiert daraus eine dritte, in der alle Unterschiede zwischen den Originalen mit den Präprozessor-Anweisungen #ifdef VARIABLE, #ifndef VARIABLE, #else und #endif eingeklammert sind. Ist VARIABLE beim Kompilieren definiert, übersetzt der C-Compiler eine Variante des Codes, sonst die andere.

Für andere Programmiersprachen als C und C++ ist die Option -D nutzlos, ebenso für alle übrigen Textdokumente. GNU diff besitzt jedoch eine weit mächtigere Mischfunktion, bei der der Benutzer mit den Optionen --old-group-format, --new-group-format, --changed-group-format und --unchanged-group-format das Format der einzusetzenden Marker selbst bestimmen kann. Jede der vier Optionen erwartet als Argument eine Zeichenkette; zusammen beschreiben diese vier Strings die vollständige Ausgabe von diff.

Zeilen, die in beiden Eingabedateien vorkommen, gibt diff im ‘unchanged group format’ aus. Ist eine Zeile nur in einer der Dateien enthalten, benutzt das Programm das ‘old group format’ oder das ‘new group format’. Das ‘changed group format’ bestimmt das Erscheinungsbild, wenn Zeilen ersetzt beziehungsweise geändert wurden. In allen Fällen versucht diff, möglichst viele Zeilen zu sammeln, bevor es mit der Ausgabe beginnt.

Die Format-Strings können normalen Text, mit ‘%’ beginnende Steueranweisungen oder eine Kombination aus beiden enthalten. Besondere Bedeutung kommt den Platzhaltern ‘%<’, ‘%>’ und ‘%=’ zu, die diff durch den Inhalt gelöschter, hinzugefügter beziehungsweise unveränderter Zeilen ersetzt. Listing 3 zeigt zur Illustration eine Nachbildung des Befehls diff -D VARIABLE datei1 datei2.

Mehr Infos

Listing 3

Kette: Mit Hilfe der Formatausgaben lassen sich Quellen filtern.

diff \
--old-group-format='#ifndef VARIABLE
%<#endif
' \
--new-group-format='#ifdef VARIABLE
%>#endif
' \
--changed-group-format='#ifndef VARIABLE
%<#else
%>#endif
' \
datei1 datei2

Mit den Optionen --old-line-format, --new-line-format und --unchanged-line-format kann der Benutzer diff anweisen, Textzeilen noch vor dem Einsetzen in die Platzhalter ‘%<’, ‘%>’ und ‘%=’ umzuformatieren. Eine weitere Option, --line-format, setzt alle drei Formate gleichzeitig. Auch in den Zeilenformaten sind Platzhalter erlaubt; dabei stehen %L und %l für die Originalzeile mit und ohne das abschließende Zeilenendezeichen. Das Default-Format für alle Zeilen ist %l gefolgt von einem Linefeed.

Das Ausgabeformat von GNU diff lässt sich mit den Formatoptionen in weiten Grenzen variieren. Alle Details lassen sich an dieser Stelle nicht aufzählen; interessierte Leser können sie in der Info-Dokumentation nachlesen (info -f diff). Zu wdiff gibt es beides: eine Manpage und eine Info-Datei.

Michael Riepe
studiert Elektrotechnik an der Universität Hannover.
(rh)