Perl UTF-8 Spezial: Byte and Char Semantics

Byte oder Zeichenorientierung, Perl unterstützt Unicode ab Version 5

Ab dem Jahr 2004 etwa, also mit v5.6.1 unterstützt Perl kodierte Zeichenketten. Bis heute jedoch gibt es immer wieder Unklarheiten diesbezüglich. Dabei ist es gar nicht neu, daß außerhalb von Programmen, also nicht nur außerhalb von Perl, nur die Bytesemantics gilt. Das heißt, daß Dateien gar keine Kodierung kennen sondern nur Bytes. Die Zeichenkodierung einer Textdatei kennt nur der Ersteller/Editor! Perintern hingegen kommt es vor, daß dem Perlinterpreter die Kodierung bekannt sein muß. Zeichenorientiert oder Bytesemantisch, eine kleine DEMO:

use strict; use warnings; # Erzeuge Zeichen my $c = chr 228; # Latin my $cc = pack "U", 228; # UTF-8 # die Zeichen sind gleich print "eq\n" if $c eq $cc; use bytes; # Die Bytesequenzen sind nicht gleich print "ne\n" if $c ne $cc;

Erläuterung: Das kleine 'ä' wird einmal mit chr() erzeugt und zum Anderen mit pack(). Obwohl die beiden Zeichen unterschiedlich kodiert sind, sagt der Perlinterpreter daß die Zeichen gleich sind. Dieses Verhalten ist vollkommen korrekt, denn sowohl char(228) als auch pack("U", 228) erzeugen Strings welche Perl als Zeichen betrachtet und nicht als Bytesequenzen. Obwohl die Kodierung beider Zeichen unterschiedlich ist, sind die Zeichen eben gleich. Wird jedoch das Pragma use bytes; gesetzt, weist das den Perlinterpreter an, bytesematisch zu arbeiten. Infolgedessen liegen für den Perlinterpreter zwei unterschiedliche Bytesequenzen vor. Und auch das ist korrekt, denn $c beinhaltet genau ein Byte mit der Wertigkeit 228, während $cc eine Länge von 2 Bytes hat mit den Wertigkeiten 195 und 164.

Über Unicode

Unicode ist keine Zeichenkodierung sondern die Verwaltung. Dafür gibt es ein Konsortium welches auf internationaler Ebene arbeitet und sich darum kümmert daß alle Zeichen der Welt katalogisiert werden: Jedes Zeichen bekommt eine eindeutige numerische ID, den sogenannten Codepoint. Es gibt Zeichen die gleich aussehen jedoch verschiedene Codepoints haben. Von daher ist Unicode kanonisch aufgebaut und es gibt verschiedene Normalformen für Zeichen, siehe ebenda. Für Populationen in HTML wird UTF-8 empfohlen und die Zeichen selbst in der Normalform C zu verwenden. UTF-8 ist eine Kodierung, programmiertechnisch gesehen ein Serialize- Algorithmus welcher den Zusammenhang zwischen Codepoint und den Bytes eines Zeichens beschreibt.

Die Arbeitsweise von chr()

my $e = chr(0x20AC); # UTF-8 use bytes; print join " ", unpack "C*", $e; # 226 130 172

Was dieser Code zeigt ist, daß chr(CP > 255) utf-8-kodierte Zeichen erzeugt. Dieser Umstand ist vor der Verwendung von chr() stets zu beachten. Grundsäzlich betrachtet der Perlinterpreter mit chr() erzeugte Zeichen aber als Zeichen und nicht als Bytesequenzen. Das heißt, daß für mit chr() erzeugte Strings die Charsemantics gilt, auch für Zeichen die mit chr(CP <= 255) erzeugt werden und von daher nicht UTF-8-kodiert sind. Zur Erzeugung utf-8-kodierter Zeichen gibt es weitere Möglichkeiten:

my $ae = "\N{U+e4}"; $ae = "\N{LATIN SMALL LETTER A WITH DIAERESIS}";

Mit diese kodierten Zeichenketten arbeiten Stringfunktionen wie substr() und length() charactersemantisch, length() gibt also die Anzahl der Zeichen aus und nicht die Anzahl der Bytes. Auch Regularexpressions wie /\w/ funktionieren mit diesen Strings zeichenorientiert.

Zeichen zu Bytesequenzen

Eine Zeichenkodierung gilt nur Perlintern. Sobald Zeichen nach draußen (STDOUT, FILEHANDLE, MySQL) gereicht werden sollen, muß die Kodierung ausgeschaltet werden, denn Dateien kennen keine Zeichenkodierung. Vor print() Ausgaben ist also auf Bytesemantics umzuschalten. Dafür gibt es 2 Möglichkeiten, Pragma bytes ist eine dieser Möglichkeiten, siehe weiter oben. Die zweite Möglichkeit bietet das Perl-Modul Encode (seit v5.8.8 im Core).

use Encode; print encode 'utf8', pack "U", 0x20AC;

Schaltet für das Eurozeichen die Kodierung ab und gibt die richtigen Bytes aus. Hinweis: Wird encode mehrfach angewandt, oder zusätzlich use bytes; angewiesen, ergeben sich kaputte Strings:

my $ae = "\N{U+e4}"; use bytes; say encode 'UTF-8', "ae => $ae"; # kaputt # ergibt denselben Mist say encode 'UTF-8', encode 'utf8', "ae => $ae";

Anders ausgedrückt: Werden Bytes, also Zeichen ohne Kodierung der Funktion Encode::encode() übergeben entsteht Datenmüll:

use strict; use warnings; use Encode; my $cc = pack "CC", 195, 164; # Bytes für ä print encode "utf8", $cc; # Müll

Mit use bytes; entsteht kein Müll, das heißt, daß in jedem Fall die richtigen Bytes ausgegeben werden auch wenn dieses Pragma nicht gesetzt wurde:

use strict; use warnings; my $cc = pack "CC", 195, 164; use bytes; # Bytesemantic ab hier print $cc; # korrekt

Wegen lesbarem Code und aus anderen Gründen ist also abzuwägen zwischen beiden Möglichkeiten. Einen Grund auf das Pragma bytes zugunsten Encode zu verzichten gibt es jedoch nicht. Zudem sieht man bei gesetztem use bytes; auch optisch daß ab dieser Zeile die Byte Semantics gilt.

Kodierung ändern

Gelegentlich kommt es vor, daß eine Kodierung geändert werden soll, bspw. von Latin nach UTF-8. Dafür muß das Zeichen intern als Zeichen vorliegen, ein chr(228); erzeugt, wie oben bereits beschrieben ein Solches. Und mit print encode 'utf8', chr 228; wird aus dem kleinen 'ä' in Latin-Kodierung ein utf-8-kodiertes 'ä' wobei mit encode gleich die richtigen Bytes erzeugt werden für die print-Ausgabe.

Warum überhaupt Kodierung

Per Default arbeitet Perl bytesmantisch. Da auch Textdateien gar keine Kodierung kennen, muß man an den daraus gelesenen Bytes gar nichs tun will man diese wieder ausgeben bspw. über STDOUT an den Webserver. Die Zeichenkodierung ist nur dann einzuschalten, wenn sich Operationen in Perl auf Zeichen beziehen wie z.B. Textkürzungen mit substr() oder Prüfungen mit length() auf eine bestimmte Zeichenanzahl. Ansonsten (bytemode) kann es passieren daß mit substr() Zeichen zerschnitten werden, also daß beispielsweise von einem 'ä' (utf8) nur noch 1 Byte übrigbleibt. Die Kodierung wird also nur für bestimmte Aufgaben gebraucht, von daher ist es unsinnig die Kodierung generell für sämtliche Texte und Strings einzuschalten.

Viele Missverständnisse um UTF-8 entstehen nur dadurch daß Perlprogrammierer unter Zeichenkodierung 2 Dinge verstehen müssen: Zeichenkodierung und Zeichenorientierung. Während die Bytefolge 226 130 172 zweifelsfei ein UTF-8-kodiertes Eurozeichen repräsentiert muß man dem Perlinterpreter mitteilen ob er diese Bytesequenz als Folge von 3 Bytes oder als 1 Zeichen betrachten soll, je nachdem was Perl damit machen soll. Außerhalb von Perl gibt es nur Bytesequenzen!


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.