Kommandozeilen-Magie

 

  Martin's virtual lair

In diesem Artikel beschreibe ich einige Linux-Kommandozeilentools zum Analysieren und Konvertieren von Textdateien zwischen verschiedenen Zeichenkodierungen. Ich werde dabei auch die verschiedenen Zeilenumbruchkodierungen der gängigsten Betriebssysteme anschneiden. Zuletzt werfen wir einen Blick auf die "Forensik" von Dateien mit beschädigten Zeichenkodierungen und wie man sie reparieren kann.

Leute, die auf ihrem Computer nur mit englischen Texten arbeiten, wissen nicht, wie glücklich sie sind: Ihre Sprache wird vollständig von der begrenzten Anzahl eindeutiger ASCII-Zeichen abgedeckt, und Umwandlungen englischer Texte zwischen verschiedenen Zeichenkodierungen (wie z.B. US-ASCII, ISO 8859-1 auch bekannt als Latin-1, oder UTF-8) führen nur selten zu Problemen. Leider gibt es keinen "Goldstandard" für Zeichenkodierungen. Am nächsten zu einem solchen Standard, insbesondere für westliche Sprachen, kommt UTF-8. Aus diversen Gründen ist UTF-8 jedoch nicht die bestmögliche Zeichenkodierung für ostasiatische Sprachen (Chinesisch, Japanisch, Koreanisch), weshalb Webseiten mit diesen Sprachen überproportional vom Problem des Zeichensalats betroffen sind. Hintergrundinformationen über die Entwicklung und Geschichte von Zeichenkodierungen finden Sie in diesem sehr guten Artikel (auf Englisch). Die wichtigste Regel ist, dass es so etwas wie "reinen Text" nicht gibt! Jeder mit einem Computer geschriebener Text basiert auf einer Zeichenkodierung.

Unter Linux sind die folgenden Kommandozeilentools nützlich für den Umgang mit Zeichenkodierungen:

  • Bestimmung von Dateityp und Zeichenkodierung: file
  • Anzeigen der Einzelzeichen einer Datei: hexdump
  • Konvertierung zwischen verschiedenen Zeichenkodierungen: recode, iconv
  • Ersetzen oder Löschen einzelner Zeichen: tr
  • Konvertierung der Zeilenumbruchkodierungen verschiedener Betriebssysteme: dos2unix

 

Bestimmung von Dateityp und Zeichenkodierung

Mit dem Kommandozeilentool file können wir den Dateityp und die Zeichenkodierung bestimmen. Eine vernünftige Textdatei sollte auf den Befehl

file -bi textfile.txt

folgendermaßen antworten:

text/plain; charset=us-ascii

Weitere gängige Kodierungen ("charset") sind iso-8859-1 und UTF-8. Andererseits hat man ein Problem, wenn man folgendes erhält:

application/octet-stream; charset=binary

In diesem Fall konnte file die Zeichenkodierung der Textdatei nicht bestimmen und nimmt daher an, dass es sich um eine Binärdatei handelt, also keine Textdatei. Für weitere Untersuchungen empfiehlt sich das Betrachten der Datei in einem Texteditor. Falls der Inhalt nur teilweise falsch angezeigt wird, macht es Sinn, die Datei auf der Ebene der Einzelzeichen mit Tools wie hexdump zu analysieren. Dazu später mehr.

 

Konvertierung zwischen verschiedenen Zeichenkodierungen

Um Textdateien von einer Zeichenkodierung in eine andere zu konvertieren, können wir den Befehl recode verwenden. Beispielsweise konvertiert der folgende Aufruf eine ISO-8859-1-Datei zu UTF-8:

recode ISO-8859-1..UTF-8 textfile.txt

Natürlich muss man sich vergewissern, dass die Eingabedatei tatsächlich als ISO-8859-1 vorliegt. Zu beachten ist auch, dass Konvertierungen von größeren Zeichensätzen zu kleineren (z.B. von UTF-8 zu ISO-8859-1) unweigerlich Probleme verursachen, falls die Eingabedatei Sonderzeichen enthält, die nicht Teil des kleineren Zeichensatzes sind. Um zu sehen, mit welchen Kodierungen recode umgehen kann, kann man sich mit 'recode -l' eine Liste anzeigen lassen.

 

Validierung von Zeichenkodierungen

Obwohl iconv in erster Linie dazu dient, zwischen verschiedenen Zeichenkodierungen zu konvertieren, kann das Tool auch dazu verwendet werden, um die Konformität einer Textdatei zu einer bestimmten Zeichenkodierung zu validieren. Zum Beispiel können wir mit dem folgenden Befehl verifizieren, ob eine Textdatei in UTF-8 kodiert ist:

iconv -f UTF-8 textfile.txt -o /dev/null ; echo $?

Dieser Befehl konvertiert die Eingabedatei von UTF-8 zu UTF-8, verwirft die resultierende Ausgabe in den virtuellen Papierkorb, und gibt nur den Rückgabewert auf dem Bildschirm aus. Der Rückgabewert 0 steht für Erfolg, während 1 einen Fehler bedeutet. Da die Umwandlung nur gelingt, falls die Datei bereits in UTF-8 vorlag, bedeutet der Ausgabewert 0, dass die Datei korrekt in UTF-8 kodiert ist.

 

Konvertierung der Zeilenumbruchkodierungen verschiedener Betriebssysteme

Als ob unterschiedliche Zeichenkodierungen nicht schon schlimm genug wären, kodieren verschiedene Betriebssysteme den Zeilenumbruch in Textdateien mit unterschiedlichen Steuerzeichen. Unter Unix, Linux und Mac OS X werden neue Zeilen durch den Zeilenvorschub kodiert (\n). In Windows und DOS werden dagegen neue Zeilen durch einen Wagenrücklauf, gefolgt von einem Zeilenvorschub, kodiert (\r \n).

Um die Zeilenumbruchkodierung einer unbekannten Textdatei zu bestimmen, können wir hexdump verwenden. Angenommen, wir haben die folgende Textdatei:

Hi,

You OK?

Je nachdem, ob die Datei unter Unix oder Windows erstellt wurde, liefert hexdump verschiedene Ausgaben für die in der Datei enthaltenen Einzelzeichen sowie deren Codewerte im Oktalsystem:

$ hexdump -bc unix.txt
0000000 110 151 054 012 012 131 157 165 040 117 113 077 012
0000000 H i , \n \n Y o u O K ? \n
000000d
$ hexdump -bc windows.txt
0000000 110 151 054 015 012 015 012 131 157 165 040 117 113 077 015 012
0000000 H i , \r \n \r \n Y o u O K ? \r \n
0000010

Wenn eine unter Linux erstellte Textdatei unter Windows in Notepad geöffnet wird, brechen die Zeilen in der Regel nicht um, da Windows die Zeilenumbrüche nicht erkennt. Umgekehrt erkennen und handhaben Linux-Anwendungen verschiedene Zeilenumbruchkodierungen viel besser, so dass man sich unter Linux oft gar nicht bewusst ist, mit welcher Zeilenumbruchkodierung man gerade arbeitet.

Die Konvertierung der Zeilenumbruchkodierung von Windows zu Linux gestaltet sich mit dem Dienstprogramm dos2unix sehr einfach:

dos2unix windows.txt

Dieses Programm stellt auch den selbsterklärenden Befehl unix2dos bereit.

 

Forensik: wie man beschädigte Zeichenkodierungen repariert

Schauen wir uns ein Beispiel aus dem echten Leben an, welches mir bei der Aufbereitung der Analysis-Vorlesungsskripten meines Vaters für die Veröffentlichung als Buch begegnet ist. Die Skripten wurden zwischen 1992 und 2003 als LaTeX-Dateien in verschiedenen proprietären Editoren unter MS-DOS und Windows 9x geschrieben und enthielten die deutschen Sonderzeichen (ä, Ä, ö, Ö, ü, Ü und ß). Später wurden die Dateien auf DVD gebrannt und noch später wurden sie von DVD auf Linux übertragen. Zu irgendeinem Zeitpunkt wurde die Codierung der Sonderzeichen beschädigt.

Beim Öffnen der Dateien im Emacs bemerkte ich sofort, dass die deutschen Sonderzeichen nicht richtig angezeigt wurden. Mit hexdump ermittelte ich, welche Zeichencodes (Werte im Oktalsystem) an Stelle der korrekten Zeichen erschienen. Die nachfolgende Tabelle zeigt die korrekten Oktalwerte der deutschen Sonderzeichen im ASCII und UTF-8 System, sowie die ungültigen Codewerte, die stattdessen in den beschädigten Dateien auftraten. So wurde beispielsweise das Zeichen "ä" fälschlicherweise von \204 und in anderen Fällen von \342 \200 \236 repräsentiert.

Zeichen	ASCII	UTF-8		ungültige Codewerte

ü \374 \303 \274 \201
Ü \334 \303 \234 \232
ä \344 \303 \244 \204 oder \342 \200 \236
Ä \304 \303 \204 \216
ö \366 \303 \266 \224 oder \342 \200 \235
ß \337 \303 \237 \341

Wie aus einer ASCII-Tabelle ersichtlich ist, entspricht glücklicherweise keiner der ungültigen Codewerte einem im Deutschen verwendeten Zeichen. Dies ermöglicht es, mit Hilfe von Stapelverarbeitung alle Vorkommen von ungültigen Codewerten durch die korrekten Werte zu ersetzen.

Im ersten Schritt habe ich sed benutzt, um die ungültigen Codewerte in die korrekten ASCII-Werte zu konvertieren. Der Befehl zum Reparieren der ungültigen Vorkommen von "ä" lautet beispielsweise:

sed -i 's/\o204/\o344/g; s/\o342\o200\o236/\o344/g' textfile.txt

Warum das Schweizer Taschenmesser sed benutzen, statt dem spezielleren tr Tool? Zwar hat tr eine sehr klare Syntax, aber es ist in seinen Fähigkeiten etwas eingeschränkt und erlaubt nur das Ersetzen oder Löschen einzelner, nicht fortlaufender, Codewerte. Die erste der beiden oben mit sed durchgeführten Ersetzungsoperationen erreicht man in tr mit:

tr "\204" "\344" <textfile.txt >outputfile.txt

Nach Konvertierung der Zeichencodes aller deutscher Sonderzeichen und dem Löschen eines ungültigen Zeichens, das aus irgendeinem Grund ganz am Ende der Textdatei vorhanden war, erfüllte meine Datei den ISO-8859-1 Kodierungsstandard:

$ file -bi textfile.txt
text/x-tex; charset=iso-8859-1

Von da an verlief alles reibungslos. Mit recode habe ich die Datei in den "Goldstandard" UTF-8 umgewandelt:

recode ISO-8859-1..UTF-8 textfile.txt

Zuletzt vergewisserte ich mich, dass die Umwandlung erfolgreich war:

$ iconv -f UTF-8 textfile.txt -o /dev/null && echo $?
0

Mit einfacher Stapelverarbeitung habe ich die beschädigte Zeichenkodierung in mehreren hundert Seiten Text repariert und in gültiges UTF-8 umgewandelt!