Dateien, Unicode, Codepoints, Bytes und Oktetten

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..

Aus Codepoints werden UTF-8-kodierte Zeichen

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.

Aus utf8kodierten Zeichen werden Bytes

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.

Operationen mit Zeichen

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.

Literale bzw. Texte aus der eigenen Scriptdatei

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 utf8

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

Texte aus beliebigen Dateien

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!

Layer für andere von UTF-8 abweichende Kodierungen

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;

Encoding-Layer für STDOUT

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!

Änderung der Zeichenkodierung

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;
  # Éé

Fehlersuche und Debugging

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. s​os­@rolf­rost.de.