Perl Spezials zm Umgang mit UTF-8, Codepoins, Bytes, Oktetten und Dateien
Die Unicode-Unterstützung in Perl ist seit Version 5.6 bis heute in Bewegung. Wesentliche Schritte waren hierfür:
Im Folgenden werden wir uns etwas näher mit diesen beiden Sachen befassen. Hinweise: Im vorliegenden Artikel wird für das Byte auch der Begriff Oktette benutzt, also beide Begriffe nebeneinander..
Betrachte untenstehenden Code:
use Encode; my $c = pack "U", 0x20AC; print encode_utf8 $c;
Die Funktion pack()
bekommt die Schablone "U" (das steht für Unicode) und betrachtet das Argument 0x20AC als Codepoint. So erzeugt $c = pack "U", 0x20AC;
ein utf8kodiertes Euro-Zeichen. Wenn dieses Zeichen ausgegeben werden soll, müssen daraus die Oktetten erzeugt werden, dies erfolgt durch encode_utf8 $c
. Sofern diese Umwandlung nicht erfolgt, erzeugt Perl eine Warnung wide character in print...
Beachte: Die Kodierung gilt nur Perlintern, für die Ausgabe auf STDOUT
ist die Kodierung wieder auszuschalten.
Die Erzeugung der Oktetten für die print()-Ausgabe soll nun etwas näher untersucht werden, betrachte hierzu den Code:
use Encode; # erzeuge ein utf8kodiertes Zeichen my $c = pack "U", 0x20AC; print join " ", map{ sprintf "%X", $_ } unpack "C*", encode_utf8 $c; # E2 82 AC
Zum Verständnis des Codes lese diesen von unten nach oben und von rechts nach links: Die Funktion encode_utf8()
erzeugt aus utf8kodierten Zeichen die Oktetten und über unpack "C*"
werden die Wertigkeiten dieser Oktetten ermittelt. Die Funktion sprintf "%X"
schließlich bringt diese Wertigkeiten in eine hexadezimale und leserliche Schreibweise.
Da Perl ab Version 5.6 zwischen kodierten Zeichenketten und Bytesequenzen unterscheidet, verhalten sich auch die für Operationen mit Zeichenketten bestimmten Funktionen dementsprechend. Schau in das Stück Code:
use Encode; my $bin = pack "C", 0xC9; print $bin, lc $bin; # É É print $bin, encode 'ISO-8859-1', lc decode('ISO-8859-1', $bin); # É é
Merke: Funktionen die für Operationen mit Zeichenketten bestimmt sind, funktionieren nicht mit Oktetten sondern nur mit Zeichenketten deren Kodierung bestimmt ist. Im Beispiel ist das die Kodierung ISO-8859-1
und in diesem Fall ist die Bytewertigkeit gleich dem Codepoint. Ansonsten, wenn nur die Bytes vorliegen haben die Funktionen lc()
und uc()
keinen Effekt.
Wenn wir ein print "ä";
notieren werden, je nach Kodierung der Scriptdatei, die dem Zeichen entsprechenden Oktetten auf STDOUT
ausgegeben. Sofern die Datei utf8kodiert gespeichert wurde sind das die Oktetten C3 A4
, was untenstehender Code zeigt.
print map{sprintf "%X",$_} unpack "C*", "ä"; # C3 A4
Wurde die Datei jedoch in ANSI gespeichert sehen wir E4
als Oktettenwertigkeit. Merke: Aus der eigenen Scriptdatei liest Perl per default keine kodierten Zeichenketten sondern die den Zeichen entsprechenden Bytes. Wenn das für die weitere Verarbeitung Zeichenketten sein sollen, muss deren Kodierung explizit bekanntgegeben werden und die Bytesequenz ist zu dekodieren, siehe also Encode::encode()
.
Das Pragma bewirkt, daß die in der eigenen Scriptdatei gespeicherten Zeichenketten utf8kodiert verwendet werden. Vor der Ausgabe auf STDOUT ist die Kodierung wie gehabt wieder auszuschalten:
use Encode; use utf8; print encode_utf8 "€"; # Es werden die richtigen Oktetten ausgegeben
Sofern der Wunsch besteht, den ganzen Dateiinhalt als utf8kodierte Zeichenkette in den Hauptspeicher zu bekommen, kann dies direkt über den sog. Layer erfolgen:
use IO::File; my $fh = IO::File->new; $fh->open('datei', "r") or die $!; $fh->binmode(':utf8'); read($fh, my $characters, -s $fh);
Also nach dem Öffnen der Datei und vor dem Lesen dieser. Im Beispiel wird der Layer :utf8
verwendet. In $characters
steht somit eine utf8kodierte Zeichenkette. Merke: Auf diese Art und Weise den gesamten Dateiinhalt zu dekodieren ist nicht immer sinnvoll bzw. nur dann sinnvoll wenn Stringfunktionen eben auf den gesamten Dateiinhalt anzuwenden sind.
Umgekehrt, also beim Schreiben in die Datei sorgt der Layer dafür, daß die Kodierung wieder ausgeschaltet wird. Somit werden automatisch die richtigen Oktetten in die Datei geschrieben:
use IO::File; my $fh = IO::File->new; $fh->open('datei.txt', O_RDWR) or die $!; $fh->binmode(':utf8'); $fh->print( pack "U", 0x20AC); # Keine Warnung
Merke: Der über $fh->binmode(':utf8')
bekanntgegebene Layer vermittelt automatisch zwischen utf8kodierten Zeichenketten und Bytes derart daß beim Lesen der Datei die Kodierung eingeschaltet und beim Schreiben der Datei die Kodierung wieder ausgeschaltet wird. Wenn Text-Dateien nur gelesen werden um sie unverändert wieder auszugeben, ist es unsinnig einen Encoding-Layer zu deklarieren!
Untenstehend ein Beispiel für eine Datei deren Inhalt ISO-8859-1
-kodierte Zeichen sind. Für die print()-Ausgabe wird der gesamte Dateinhalt in Kleinbuchstaben umgewandelt, was nur funktioniert weil der Layer entsprechend gesetzt ist:
use IO::File; my $fh = IO::File->new; $fh->open('datei.txt', O_RDWR|O_CREAT) or die $!; $fh->binmode(':encoding(ISO-8859-1)'); read($fh, my $text, -s $fh); print lc $text;
Wenn viel mit utf8kodierten Strings gearbeitet wird, lohnt es sich, den Layer für die Ausgabe entsprechend zu deklarieren:binmode STDOUT, ":utf8";
womit das explizite Umwandeln der Strings zu Bytesequenzen automatisch erfolgt.
Wenn über diesen Encoding-Layer nicht utf8kodierte Zeichen (Oktetten) ausgegeben werden, gibt es Zeichensalat!
Auch hierzu ein Beispiel, siehe untenstehend. Die Umwandlung erfolgt mit der Funktion from_to()
, diese Funktion bekommt Oktetten übergeben und gibt die Anzahl der resultierenden Bytes zurück wobei die Änderung der Kodierung im Hauptspeicher abgewickelt wird.
use Encode qw(from_to decode encode); my $c = pack "C", 0xC9; # Oktetten!!! my $n = from_to($c, 'iso-8859-1', 'utf-8'); print $c, encode 'utf8', lc decode 'utf8', $c; # Éé
Untenstehende Funktion erzeugt ein Hexmap und zeigt damit die Wertigkeiten einzelner Bytes, unabhängig davon ob eine kodierte Zeichenkette oder eine Bytesequenz vorliegt bzw. übergeben wurde:
sub showbytes{ my $s = shift; use bytes; local $, = " "; print map{ sprintf "%X", $_ } unpack "C*", $s; } showbytes( pack "U", 0x20AC ); # E2 82 AC
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. sos@rolfrost.de.