Archive for the ‘CharacterEncoding’ Category

Unicode mit Perl

Dienstag, August 7th, 2007

Unicode unter Perl funktioniert meistens einfach. Über das warum und wieso muss man sich meistens keine sorgen machen. Wenn man denn trotzdem mit Zeichensätzen spielen möchte, so muss man hinter die Kulissen blicken.

Ausgangssituation

Zuerst einmal die Ausgangssituation: Meine mp3-Sammlung. Sie hat im Laufe ihres Lebens diverse male ihr Speichermedium gewechselt und ist weiter gewachsen. Bei jedem Kopiervorgang einer Datei wird eine neue Datei mit dem gleichen Dateinamen und dem gleichen Inhalt angelegt. Bei den Dateinamen fangen die Probleme an. Was bedeutet „gleich“. Heutzutage werden auf jedem vernünftigen Betriebssystem die Dateinamen als UTF gespeichert (meistens UTF-8). Jedoch sind auch Zeichensätze der iso-8859 (iso-8859-1 oder iso-8859-15 im europäischen Raum) oder gar Codepages (wie cp1250, cp850, cp437) auf Windows-Systemen im Einsatz.

ASCII

Das ist alles kein Problem, solange sich alle Zeichen eines Dateinamens im ASCII-Zeichensatz befinden. Der ASCII-Zeichensatz wurde von Amerikanern erfunden, die der Meinung waren, dass die ganze Welt nur die Zeichen A-Z, a-z, 0-9, Sonderzeichen wie !“$&\|[](){}=*+-/%~#’_.:,?!; (nicht vollständig) und verschiedene Leer- und Steuerzeichen gebraucht. Es wurde jedoch bald erkannt, dass diese 128 verschiedenen Zeichen (\x00-\x7F) nicht der Weisheit letzter Schluss sind. Also entstanden genau so kurzsichtig diverse Definitionen, die den übrigen 128 Zuständen eines Bytes (\x80-\xFF) ein Bildchen verpassten. Somit waren die ASCII-Erweiterungen (iso-8859-1 bis iso-8859-15 und einige Codepages) geboren. Am Beispiel einer Tabelle einiger Zeichensätze auf Wikipedia werde ich mein Problem verdeutlichen.

Kopieren

Ich habe es wie auch immer geschafft, eine Datei mit dem Zeichen ´ (\xB4) auszustatten. Das ist der Akzent, der aus einem Cafe einen Café macht. Jedenfalls hieß die Datei dann „Salt´n´Pepper – Push It.mp3“. Abgesehen davon, dass die Band falsche geschrieben ist (Salt-N-Pepa), sind da nun noch zwei nicht-ASCII-Zeichen versteckt. Es ergab sich, dass dieser Dateiname in der Windows Codepage 1252 (oder 1250? – ich weiß es nicht mehr) gespeichert wurde. Die Festplatte unter Linux angesprochen hat die Bytekette, die den Dateinamen repräsentiert nun aber als iso-8859-15 interpretiert, und daher für das Zeichen Ž (\x017D) gehalten. Das ganze dann als UTF-8 neu abgespeichert ergibt „SaltŽnŽPepper – Push It.mp3“.

Fast jede Datei hat so ihre Geschichte. Die meisten bestehen zum Glück nur aus ASCII-zeichen. Und viele sind einfach UTF-8 interpretierte, aber iso-8859-15 kodierte Dateinamen. Ich wollte diese jetzt alle korrekt nach UTF-8 konvertieren, da mir dieser Zeichensatz zukunftssicher erscheint.

Repräsentation in Perl

Perl speichert Strings in Scalaren (z.B. $file). Ein Scalar enthält Zeichen und einen Perl-internen Overhead der diverse Flags und die Länge enthält. Damit hat man aber eigentlich nichts am Hut. Seit Perl Version 5.6 wird für jedes Zeichen eines Scalares ein Wide-Character (2 Bytes) benutzt. Vorher galt ein Byte pro Zeichen. Erstmal ändert sich dadurch nichts. Ein UTF-8-kodierter String (z.B. aus einer Datei, einem UTF-8 kodierten Perl-Script oder dem Terminal als Argument) wird nach wie vor in einem Haufen von Zeichen mit einem dezimalen Wert von 0-244 gespeichert. (Die Kombinationen \xF5-\xFF, sowie \xC0 und \xC1 kommen in gültigen UTF-8 Sequenzen nicht vor.) Wenn er wieder geschrieben wird, so bleibt er UTF-8 kodiert. Ein m/ü/ in einem script (UTF-8) ist das gleiche wie m/\xC3\xBC/ (ü = \xFC (iso-8859-1) = \xC3\xBC (UTF-8)). Das ist schlecht, wenn Script und Daten in verschiedenen Zeichensätzen kodiert sind.

Nun gibt es aber für jeden $scalar ein UTF8-Flag. Perl merkt sich damit, ob es in den Wide-Characters des $scalars direkt den code point gespeichert hat anstatt nur die kodierte Form. Der code point ist eine eindeutige Zahl, die von der ISO-10646 jedem Zeichen zugeordnet ist – für ASCII-Zeichen ist er äquivalent.

Umwandlung in Perl

Wie bekommt man nun Perl dazu, den Inhalt eines $scalars als Zeichen zu interpretieren und in Codepoints zu dekodieren?

  • use utf8; $scalar=“äöü“; wenn der Inhalt von $scalar im script steht.
  • binmode(FILEHANDLE, „:utf8“); $scalar=; wenn der Inhalt von $scalar aus einem file handle kommt.
  • use Encode; $scalar2=decode(„utf8“,$scalar1); falls $scalar1 bereits UTF-8 kodierten Inhalt enthält.

Das Flag lässt sich mit $utf_encoded = Encode::is_utf8($scalar); abfragen. Anstatt „utf8“ kann man auch „iso-8859-1“ oder ähnliches verwenden. Ich bin so faul und erlaube mir für weitere Informationen auf die Dokumentation von Encode, binmode, utf8 und perlunicode zu verweisen.

Konvertierungs-Script

Mein Script nimmt eine Liste von Datei- und Verzeichnisnamen entgegen benennt alle Dateien in den Verzeichnissen und Unterverzeichnissen in UTF-8 um. Zudem ändert es auch die Dateinamen in .md5 und .sfv Dateien. Es beachtet dabei auch mehrfache falsch-Konvertierungen wie Eingangs beschrieben, indem es sich an Hand einer Zeichen-Bewertung die schönste Konvertierung heraus sucht. Die Präferenzen, was schön ist und was nicht muss von Nation zu Nation angepasst werden. Für Deutsche ist im Dateinamen ein Umlaut schöner als ein ´ schöner als ein Ž. Kroaten mögen andere Zeichensätze bevorzugen und das Ž höher als Umlaute und ´ bewerten.

Es bleibt jedem überlassen, wie er sich das Script anpasst. Getestet werden sollte es allerdings erstmal in einem kleinen Verzeichnis von dem man ein Backup angelegt hat.
Herunterladen kann man es unter

http://download.entropie.li/perl/recode.pl.txt