Unicode, Perl, Bytesequenzen und UTF-8-kodierte Zeichenketten

Perl unterscheidet seit Version 5 zwischen Character und Bytesemantic

Beginnen wir mit einer Zeile Code, die oft zu sehen ist:

binmode STDOUT, ":utf8";

Was hat es damit auf sich? Nun, genauso oft, wie diese Zeile zu lesen ist, wird dazu angemerkt, dass damit eine Ausgabe mit UTF-8-kodierten Zeichen vorbereitet und die Ausgabe in UTF-8 erfolgen soll. Tatsächlich jedoch schaltet der Layer ":utf8" die interne Kodierung nicht ein sondern aus! Um diese Dinge geht es hier und außerdem um die Vielfalt beim Umgang mit Zeichenkodierungen die mit keiner anderen Programmiersprache so zweckmäßig gemeistert werden kann wie mit Perl.

Bytesemantic und Charactersemantic

Perl in den ersten Versionen arbeitete byteorientiert. D.h., der Perlinterpreter machte keinen Unterschied zwischen Zeichen und Bytes, ein Verhalten also, wie wir es aus der guten alten ASCII-Welt kennen. Ab Major-Release 5 jedoch ist die Unicode-Unterstützung hinzugekommen und wurde seither ständig weiterentwickelt. Eng verbunden mit Unicode ist die Zeichenkodierung UTF-8 und bei dieser Kodierung ist es eben so, dass ein Zeichen nicht gleich einem Byte entspricht sonderen aus mehreren Bytes zusammengesezt sein kann.

Aus diesem Grund muss Perl-intern unterschieden werden, ob z.B. die Funktion length() eine Anzahl von Bytes liefern soll oder eine Anzahl an Zeichen. Diese Unterscheidung zieht auch einen Default im Verhalten des Interpreters nach sich und per Default arbeiten sämtliche Stringfunktionen wie reguläre Ausdrücke byteorientiert. Untenstehende Zeile:

print "Ä" =~ /ä/i ? "passt" : "passt nicht";

quittiert dieses Verhalten mit einem "passt nicht" weil die Bytesequenz eines "Ä" eine Andere ist als die eines "ä". Das wird jedoch sofort anders, wenn über der print-Ausgabe das Pragma use utf8; notiert wird. Dieses Pragma bewirkt, dass das im Script notierte Literal "Ä" vom Interpreter als ein UTF-8-kodiertes Zeichen aufgefasst wird und in diesem Fall gibt es einen Match aufgrund des /i Modifiers. Vorausgesetzt natürlich, dass die Script-Datei mit der Kodierung UTF-8 abgespeichert wurde.

Da das Pragma utf8 nur auf die innerhalb des Scripts notierten Literale wirkt, nicht jedoch auf lexikalische Variablen, die anderweitig und auch in einer von UTF-8 abweichenden Kodierung vorliegen, wurde mit Encode, seit Version 5.8 im Core, ein Modul geschaffen was die Unterstützung beliebiger Zeichenkodierungen ermöglicht. Siehe Beispiel untenstehend:

# Script-Datei in Kodierung
# ISO-8859-1 gespeichert!

use Encode;
my $chars = decode("ISO-8859-1", "Ä");
print $chars =~ /ä/i ? "passt" : "passt nicht";

# Ausgabe: passt

Des Weiteren sei festgestellt, dass Encode auch zwischen verschiedenen Zeichenkodierungen vermitteln kann. Mit dem Code obenstehend jedoch wird die Kodierung nicht geändert, sondern es wird dem Perl-Interpreter lediglich mitgteilt, um welche bereits vorliegende Kodierung es sich handelt. Um den Umfang und die Wirkungsweise von Encode zu demonstrieren, habe ich bewusst die Kodierung ISO-8859-1 gewählt weil: Ohne Encode war es gar nicht möglich den /i Modifier auf ISO-Kodierte Zeichenketten anzuwenden.

Weitere Codebeispiele:

use strict;
use warnings;
use Encode qw(encode decode);
use v5.10;


# Datei in utf-8 gespeichert
# Oktetten, Binary
my $bin = "Ä";
say length $bin;
# Ergebnis: 2
# es sind 2 Oktetten

# Oktette zu Zeichen
my $char = decode("UTF-8", $bin);
say length $char;
# Ergebnis: 1
# es ist 1 Zeichen

# Case insensitive match
my $lchar = decode("UTF-8", "ä");
say $char =~ /$lchar/i ?
    "passt" : "passt nicht";
# Ergebnis: passt

# lower to upper case
my $uchar = uc $lchar;
printout($uchar, "€");
# Ausgabe: Ā

# Ausgabe der Oktetten
sub printout{
    # Umschalten nach Bytesemantic
    use bytes;
    print @_, "\n";
    # Bytesemantic wieder ausschalten
    no bytes;
}

Merke: Sämtliche Stringfunktionen (length, substr, lc, uc, usw.) und reguläre Ausdrücke arbeiten nur dann erwartungsgemäß, wenn dem Interpreter mitgeteilt wurde, ob es sich um Bytesequenzen oder um kodierte Zeichenketten handelt.

Kommen wir zum nächsten Thema, siehe Code untenstehend:

use utf8;
print "€€€";
# Fehlermeldung: Wide character in print

Bytesequenzen kennen keine Zeichenkodierung

Niklaus Wirth fasste um 1980 den Dateibegriff: Dateien (Files) sind gewöhnlich Sequenzen. Korrekterweise muss es heißen "Bytesequenz" für das was in einer Datei vorliegt. Der Begriff Textdatei kommt ja auch nur daher, dass die in einer Textdatei vorliegende Bytesequenz lesbaren Text ergibt, sofern der Browser, Editor o.a. Dateibetrachter die Kodierung kennt.

Merke: Für jede Ausgabe in eine Datei, STDOUT, Socket oder beliebiges Handle sind nur Bytesequenzen zulässig. Eine Ausgabe von kodierten Zeichenketten, siehe obenstehendes Beispiel, ist möglicherweise fehlerhaft und mit einer Fehlermeldung "Wide character in print..." verbunden. Zum Vermeiden dieser Fehlermeldung und im Sinne einer korrekten Ausgabe gibt es mehrere Möglichkeiten:

Was für STDOUT gilt, gilt natürlich auch für STDIN, Sockets und beliebige Handles. Auch über die CGI-Schnittstelle reinkommende Strings kennen keine Zeichenkodierung sondern sind Bytesequenzen (Rohdaten, Oktetten).

Finessen: Codepoints

Maßgeblich ist, neben der Schablone "U", ob die Funktion unpack() byteorientiert oder zeichenorientiert arbeiten soll. Letzteres wird über Encode::decode erreicht, diese Funktion deklariert Oktetten mit der entsprechenden Kodierung. Eine andere Möglichkeit ergibt sich infolge Verwendung des Pragma utf8, siehe untenstehende Code-Beispiele.

use Encode qw(encode decode);
use v5.10;

# Codepoint des
# €-Zeichen ermitteln

# Kodierung intern vermitteln
my $euro = decode("UTF-8", "€");
say unpack "U", $euro;
# Codepoint dezimal
# 8364

# Literal äöü als utf-8-kodiert betrachten
use utf8;
say "@{[unpack 'U*', 'äöü']}";
# gibt die Codepoints aus
# in Dezimalschreibweise
# 228 246 252

# Bytesemantic einstellen
# Rückfall auf den Default
no utf8;
say "@{[unpack 'U*', 'äöü']}";
# gibt die Oktettenwertigkeiten aus
# in Dezimalschreibweise
# 195 164 195 182 195 188

Das zuletzt notierte Beispiel zeigt, das der Perlinterpreter mit no utf8; wieder auf den Default Bytesemantic eingestellt wird. Dieser Default ergibt sich dadurch, dass Perl spontan die Moduldatei bytes.pm bei jedem Start kompiliert ohne dass mit use bytes; dieses Pragma ausdrücklich angefordert wird. Hier zeigt sich einmal mehr der Sinn und die Bedeutung des Begriffes: Default steht für eine fehlende Angabe bzw. Anweisung -- Bytesemantic ist nicht angewiesen, also gilt der Default. Natürlich ist es auch möglich, mit use bytes; die Bytesemantic explizit anzuweisen, siehe Funktion printout() weiter oben.

Analog zu unpack() ist es mit pack() und Schablone "U" möglich, aus gegebenen Codepoints die Oktetten (Bytesequenzen) zu erzeugen:

use bytes;
print pack "U", 8364;
# €

Zu beachten ist wie gehabt, dass Oktetten nach STDOUT geschickt werden, dafür sorgt das bytes-Pragma. Die pack()-Funktion selbst erzeugt mit der "U"-Schablone aus einem oder mehreren Codepoints nicht gleich die Oktetten sondern zunächst einen UTF-8-kodierten String.


Datenschutzerklärung: Diese Seite dient rein privaten Zwecken. Auf den für diese Domäne installierten Seiten werden grundsätzlich keine personenbezogenen Daten erhoben. Das Loggen der Zugriffe mit Ihrer Remote Adresse erfolgt beim Provider soweit das technisch erforderlich ist. s​os­@rolf­rost.de.