Charsets und Kodierungen in Perl

(Update: eine überarbeitete und verbesserte Version dieses Artikels kann man hier finden.)

Wieso werden mit Perl meine Umlaut nicht richtig dargestellt? Wieso
funktioniert es auf einem Rechner, auf einem anderen nicht?

Grund ist meistens, dass der Programmier nicht genug darauf geachtet hat, wie
ein String codiert ist. Um das zu beheben, braucht man ein bisschen
Hintergrundwissen.

Am Anfang war alles ASCII

Als die Erde noch jung und elektronischer Speicherplatz Mangelware war,
überlegten sich clevere Leute, wie man ein ‘A’ binär kodieren könnte – und
einigte sich auf ASCII, den “American Standard Code for Information
Interchange”.

ASCII definiert 128 Zeichen, benutzt also 7 Bit. Das reicht für Klein- und
Großbuchstaben, Ziffern, Rechenzeichen, diverse Klammern und ein paar
Kontrollzeichen – Umlaute sind nicht dabei.

…dann ging das Chaos los

Zum Glück sind ein Byte 8 bit, d.h. man kann in einem Byte 256 Zeichen
kodieren – nur welche?

In den verschiedenen Ländern wurden, abhängig von der Sprache, verschiedene
Zeichen gebraucht. In Mitteleuropa z.B. für Deutschland die Umlaute und
scharfes s, für Frankreich akenzentuierte ‘e’s und “c cedile” ç, für
Großbritannien das Pfund-Zeichen £. Damit und mit ein paar anderen Zeichen
waren die restlichen 128 Zeichen schnell aufgebraucht. Den Zeichensatz für
Mitteleuropa nannte man “Latin1″ (Lateinische Zeichen, Sprachraum 1),
festgehalten im Standard iso-8859-1.

Aber in Griechenland, Russland und vielen anderen Ländern braucht man ganz
andere Zeichen, also sprossen die verschiedenen Kodierungen wie Unkraut aus
dem Boden: Latin2 für Osteuropa, Latin3 für Südeuropa, Latin4 für den
baltischen Sprachraum, Latin5 für Kyrillische Zeichen, Latin6 für arabische
und so weiter.

In den meisten Kodierungen sind die ersten 128 Zeichen mit ASCII identisch,
nur die zweiten 128 Zeichen unterscheiden sich (also die, die binär mit einer
1 anfangen).

Für die meisten asiatischen Schriften reichen 256 bei weitem nicht, der
Zeichensatz Big5 z.B. verwendet zwei Bytes pro chinesischem Schriftzeichen.

Das Problem

Das erste Problem daran ist, dass man mit diesen Zeichensätzen erst einmal nur
Zeichen aus einem Zeichensatz darstellen kann. Wer Umlaute verwendet, kann
keine griechischen Zeichen verwenden, keine arabischen, keine kyrillischen
usw.

Das zweite Problem ist, dass man von Daten wissen muss, in welcher Kodierung
sie vorlieben, um sie korrekt darzustellen. Z.B. bedeutet in Latin1 das Byte
0xE7 ‘à’, in Latin2 bedeutet es ‘ŕ’ und in Latin10 (nordisch) ist es ein ‘ā’.

Wenn man keine Informationen darüber hat, welche Interpretation man wählen
muss, kann man nur raten.

Unicode

Eine Lösung für das erste Problem ist Unicode. Das ist ein Projekt mit dem
Ziel, alle möglichen Zeichen zu kodieren – alle bekannten, zum Teil selbst
fiktionale Zeichen (z.B. klingonische Schrift).

Dabei wird jedem Zeichen (z.B. LATIN CAPITAL LETTER A) eine Zahl zugewiesen,
der sogenannte Codepoint. Der wird üblicherweise in hexadezimaler Notation
angegeben: U+0041.
Dabei gibt es nicht nur die “normalen” Zeichen, sondern auch die “combining
characters”, die andere Zeichen modifizieren – bekanntestes Beispiel sind
Akzente (ein A combiniert mit COMBINING GRAVE ACCENT wird dann zu À).

Damit ist aber noch nicht festgelegt, mit welchen Bytefolgen ein bestimmtes
Zeichen kodiert wird. Dazu gibt es die “Unicode Transformation Formats”, das
bekannteste ist UTF-8.

Die ersten 128 Zeichen von UTF-8 sind mit ASCII identisch, andere Zeichen
werden mit einer variablen Anzahl von Bytes kodiert, z.B. deutsche Umlaute mit
zwei Bytes, andere Zeichen können bis zu vier Bytes aufbrauchen.

Kodierungen und Perl

Perl kann ab Version 5.8 recht gut mit Unicode umgehen. Auch kann man zwischen
den verschiedenen Kodierungen relativ leicht umrechnen – man muss sich nur
bewußt sein, dass man es auch tun muss.

Perl verwendet ein internes Format, um Strings zu speichern. Man sollte alle
Eingabedaten in dieses Interne Format umwandeln bevor man etwas damit macht.

Dann werden Zeichen, die in mehreren Bytes kodiert werden, auch tatsächlich
als ein logisches Zeichen behandelt, wie das folgende Beispiel auf einem auf
UTF-8 eingestelltes Terminal zeigt:

$ echo 'ä' | perl -wlne 'chomp; print length($_)'
2
$ echo 'ä' | perl -MEncode -wlne 'chomp; $_ = decode("utf8", $_); print length($_);'
1

Hier wird das Core-Modul Encode geladen, und mit dessen
Funktion decode verwendet, um die Eingabedaten in perls internes
Format umzuwandeln. Dabei muss man die Kodierung der Eingabedaten angeben.

Wenn man diese nicht weiß, hat man ein großes Problem. Man kann probieren, mit
Heuristiken die Kodierung zu erraten, aber das ist unzuverlässig und
rechenintensiv. Wenn man es doch probieren will, kann man es so machen:

use Encode::Guess;

sub my_decode {
    my $str = shift;
    return '' if (!defined $str or $str eq qq{});

    my @encodings = qw(ascii utf-8 iso-8859-15 gb2312);
    my $encoder = guess_encoding($str, @encodings);
    if (ref $encoder){
        return $encoder->decode($str);
    } else {
		# irgend wie muss man die Daten dekodieren. Alternativ könnte man,
		# je nach Anwendung, auch einen Fehler auslösen
        return decode("latin1", $str);
    }
}

# Hier Daten irgendwie in $input einlesen, und dann

my $internal = my_decode($input);

Wenn man die Daten ausgibt, muss man sie wieder in das Format kodieren, in dem
man sie ausgeben will. Das kann man z.B. per Hand machen:

use Encode;
print encode('utf8', $string);

Man kann auch Dateihandle dazu bringen, automatisch nach utf-8 zu
konvertieren:

open my $filehanlde, '>:utf8', 'dateiname.txt';
# oder wenn das filehandle schon offen ist:
binmode($filehandle, ':utf8');
binmode(STDOUT, ':utf8');
# oder mit anderen Kodierungen:
binmode($filehandle, ':encoding(iso-8859-1)');

Man kann auch dafür sorgen, dass alle Dateien zum lesen und schreiben in einem
ganzen Programm immer mit ‘:utf8′ geöffnet werden:

use open ':utf8';

7 Comments »

  1. clinton said,

    January 23, 2008 @ 7:05 pm

    Das Getue um Unicode habe ich nie verstanden. Es ist immer noch voller Fehler(siehe Unicode Bugs). Dieses ganze decoden (auch in verbindung mit meiner SQL DB) nervt wirklich extrem.

  2. moritz said,

    January 23, 2008 @ 7:53 pm

    Und was willst du sonst nehmen? ASCII? Und vollständig auf alle anderen Zeichen verzichten?

    Natürlich ist es nervig und nicht gerade einfach, aber das liegt nicht an den Implementierungen, sondern daran, dass menschliche Schrift nicht einfach ist.

    Aber wenn man mal ein bisschen durchblickt und sich Zeit nimmt, alles korrekt aufzusetzen, geht das alles. Ich habe vor kurzem auf sudokugarden.de die Hiscore-Listen Unicode-Safe gemacht, und das ging zu 95% nach Schema F.

  3. Rauchen said,

    January 29, 2008 @ 11:06 pm

    Ich sehe das genau wie Moritz. ASC II geht gar nicht. Man muss sich halt ein bisschen mit dem Thema auseinandersetzen und auch mal in den sauren Apfel beißen.

  4. anke said,

    May 14, 2008 @ 12:38 am

    Ist Unicode eigentlich noch in Mode?

  5. Michael Web said,

    June 6, 2008 @ 8:21 pm

    Gute Frage, benutzt denn überhaupt noch jemand Unicode?

  6. moritz said,

    June 6, 2008 @ 10:03 pm

    Was sind denn das für Fragen? Ob es in Mode ist? ist doch egal. Ob es benutzt wird? Klar, überall. Jedes Office-Dokument verwendet Unicode, viele Webseiten, und überhaupt fast jede ordentliche Software, die in irgend einer Form mit beliebigen Benutzereingaben zurechtkommen muss.

    Die meisten erwähnen es halt nicht explizit, aber man hat schlicht und ergreifend keine ordentlichen Alternativen.

  7. Markus said,

    January 5, 2010 @ 11:35 am

    Unicode in Mode? Ich glaub, da hat jemand was so brutal nicht kapiert wie man es nur irgendwie nicht kapieren kann. Jeder Text, der deutsche Umlaute und etwa osteuropäische Zeichen mischen können soll, oder jeder Text, der westliche und asiatische Zeichen gemischt enthalten soll, benötigt Unicode dringendst, und es gibt keine Ausnahmen. Jede Email, die von Europa nach Asien geschickt werden soll, und dort auch mit europäischen Zeichen ankommen soll, hätte sonst ein Problem. Oder ein russischer Text, den Du mit einem westeuropäischen Texteditor bearbeiten willst… USW.

    Gemischte Dokumente sind auf Basis der alten ISO 8859 Varianten aber einfach nicht machbar. Hier braucht man eine einheitliche Zeichentabelle, die ALLE jemals erfundenen Zeichen der menschlichen Kultur umfaßt, und genau das ist Unicode. Naturgemäß ist diese Zeichentabelle ziemlich groß und benötigt einen gewaltigen Adressraum.

    8 Bit aka 1 Byte (256 Varianten) reicht natürlich nicht
    16 Bit aka 2 Byte (65k Varianten) reicht auch nicht, schon alleine Chinesisch bringt 25k Zeichen mit.
    24 Bit aka 3 Byte ist eine scheußlich krumme Zahl, die kein Informatiker so recht mag. Allerdings wär der Adressraum groß genug (16,7 Mio Zeichen)
    32 Bit aka 4 Byte wäre prima, ist aber mindestens 1 Byte Platzverschwendung, was sich hochgerechnet höchstens auf den Umsatz der Festplattenhersteller positiv auswirkt. Ansonsten einfach nur Quatsch, vor allem, weil viele Texte nur Latin-Zeichen benutzen und damit mit 1-2 Byte tatsächlich genutzter Adressraumbreite abgespeist werden könnten.

    Die Thematik UTF(8/16) bzw. UCS ist wieder etwas gaaaanz anderes. Das hat nix mit der Zeichentabelle zu tun, sondern es ist ein Speicherverfahren. Und das Clevere an UTF ist, daß die tatsächlich abgespeicherten Zeichen unterschiedlich viel Platz brauchen. Wenn es ein ausgefallenes Zeichen ist, braucht es halt 5 oder mehr Bytes Platz, wenn es ein klassisches ASCII-Zeichen ist, braucht es nur 1 Byte Platz. Damit hat man vor allem ein extrem effizientes Speicherverfahren für gemischtsprachige Texte.

    Und im Moment befinden wir uns eben in der Umstellungsphase, was wohl noch ein paar Jahre dauern wird, bis die letzten ISO-Systeme abgeschaltet werden. Aber dann leben wir hoffentlich endlich alle in der tatsächlich globalisierten Welt.

RSS feed for comments on this post · TrackBack URI

Leave a Comment