Nudge am 19.04.2009

Character Sets / Zeichen-Kodierung

in Linux, Tipp | Tags: ASCII, gettext, i18n, Kodierung, Konsole, l10n, Linux, Locale, MySQL, Sprache, Terminal, Unicode

Letzte Woche bin ich mit MySQL fast verzweifelt. Irgendwie haute die ganze Kodierung nicht mehr hin. Das Backup hatte auf einmal zerrissene Umlaute, und nichts konnte es bewegen, es wieder richtig zu stellen. Nach verzweifelter Suche fiel mir der Fehler auf: Terminal, Verbindung, MySQL – all das muss passen. Und da hatte sich der kleine Fehlerteufel eingeschlichen: WordPress hielt es nicht für nötig, die Tabellen auf die benutzte Kodierung umzustellen. Hier gleich die Entwarnung: Die aktuelle WordPress-Version 2.7.1 tut das auf jeden Fall ordentlich.

Auf jeden Fall hat es mich angespornt, zu diesem Thema einen kleinen Beitrag zu verfassen. Und so fange ich mit einem kurzen Abschweifer eines geschichtlichen Abrisses an, um dann später auf die Einstellung des Linux-Basissystems zu sprechen zu kommen. Weil damit schon so viel Text durch den Äther fliegt, kommt MySQL etwas später dran.

Wildwuchs der Kodierungen

Die richtige und konsistente Kodierung von Zeichenketten, das scheint ein Buch mit sieben Siegeln. Verursacht wurde das Chaos durch ein zugegebenermaßen kurzsichtiges 7bit-Denken unserer IT-Väter aus den Staaten, als die wenigen englischen Buchstaben ausreichend erschienen. Und jetzt haben wir den Salat: Dateisysteme, Datenbanken, Netzwerke, Webseiten – durch alle Instanzen zieht sich dieses leidige Thema. Seit Jahrzehnten, und jeder ist vom Schlamassel betroffen.

Nach der Einöde der 127 Zeichen ergriff die Welt der blanke Kodierungswahn. Mit dem Erfolgszug der Computer in alle Teile der Welt entstand der Bedarf, auch lokale Zeichen digital zu benutzen. Dabei erfand praktisch jede nationale Standardisierungsbehörde ein eigenes Zeichenset. Manche Behörden, wie Japan’s JIS (Japan Information Standard), erfanden über die Jahre sogar immer wieder neue Kodierungen – allein in Japan gibt es schon unbeherrschbar viele.

Vor allem die Beschränkung auf zunächst 1 Byte mit 8-Bit Breite pro Schriftzeichen führte zu einer Menge an Kodierungen. Hier siedelten die nationalen Zeichen alle im selben Wertebereich an. Man denke nur an die vielen europäischen Zeichensets! Durch diese Mehrfachverwendungen entstand an den Reibungsstellen mehr Schaden als Nutzen.

Klar, mehr als ein Byte pro Zeichen, das ist mit Aufwand verbunden. So muss man die Länge einer Zeichenkette zwischen Bytes und Schriftzeichen unterscheiden, Trennungen richtig durchführen. Daneben ist die Prüfung valider Eingaben viel komplizierter und anfälliger für Fehler – und damit auch für Sicherheitsrisiken. Bis vor wenigen Jahren war der Rechenaufwand (die Performance) um vieles wichtiger als die Unterstützung fremder Sprachen, die im eigenen Land unter “ferner liefen” ignoriert werden konnten.

Das rächte sich kurze Zeit später. Mit häßlichen Fragezeichen oder Vierecken in den Worten, fehlenden Akzenten, abgeschnittenen Sätzen oder allein den inkompatiblen Umbrüchen der verschiedenen Betriebssysteme holte uns die Vergangenheit grausam ein. Allen voran unsere Redmonder Freunde haben da ganze Arbeit geleistet und mit ihrer DOS- und (frühen) Windows-Welt ganz eigenbrödlerisches Handeln an den Tag gelegt, und die Nutzer baden es aus.

Die Geburt von Unicode

Um dem ganzen ein Ende zu bereiten, gründete sich 1991 das Unicode Consortium. Es hatte das Ziel, durch eine Schaffung einer einheitlichen Nummerierung aller auf der Welt benutzen Schriftzeichen das Wirrwarr zu entflechten. Diese Nummerierung ist der Unicode eines Zeichens. Damit Unicode sich durchsetzen kann, gibt es eine Garantie auf eine sogenannte round-trip conversion. Das heißt, bei einer “Übersetzung” eines nationales Zeichens auf Unicode und zurück soll wieder dasselbe Zeichen entstehen. Was vielleicht ganz einfach und logisch klingt, ist allerdings verdammt kompliziert. Diese Basis war wichtig, damit alle Systeme problemlos auf Unicode migrieren können.

OK, Kurs auf Unicode! Doch bis heute hat sich Unicode leider nicht richtig durchsetzen können. Das Problem bei der Verbreitung von Unicode sind die bestehenden Daten und Anwendungen, die mit mehr als einem Byte pro Zeichen nicht zurechtkommen. Eine Hauruck-Umstellung ist vollkommen unmöglich, man denke an die schiere Unzahl von Computern und Dokumenten in der Welt. Und eine schleichende Umstellung ist teuer. Denn jedes Stück Text muss gekennzeichnet werden. Welche Kodierung hat es? Welche Sortierung ist die richtige?

Daneben gibt es nicht nur ein Unicode: Es kommt ebenfalls mit verschiedenen Kodierungen daher. Diese sind gewissermaßen Umsetzungen des Unicode auf die verschiedene Bit-Breiten, mit denen Computer oder Netzwerke umgehen können: UCS-4 und UCS-2 für 32 bzw. 16 Bit, UTF-8 und UTF-7 für 8 bzw. 7 Bit. Dabei benutzen UCS-2 und UCS-4 feste Breiten von 2 bzw. 4 Byte pro Zeichen  (das ist also doppelt oder 4mal sowie Platz wie bei DOS). Beispiele für UCS-2 sind Windows (ab Windows 2000) und Java. Wenn man dort Unicode-Dateien mit einem einfachen Texteditor öffnet, so sieht am am Anfang zwei Binärzeichen, die a) sagen, es ist UCS-2, und b) erklären, ob das niederwertige Byte zuerst oder zuletzt kommt – ein sogenanntes endianess mark.

Degegen steht UTF-* für Unicode Transformation Format und wurde für die Übertragung von Unicode über nicht-unicode-Systeme erfunden, ganz ähnlich einem IPv6-over-IPv4-Tunnel. UTF ist also als Datenstrom selbst in 7-Bit (UTF-7) bzw. 8-Bit (UTF-8) gehalten. Es  benutzt dann zum Teil mehrere Zeichenkombinationen, um das gewünschte Unicode-Zeichen zu erklären. Der Vorteil: Die US-ASCII-Zeichen sind vollständig gleich zur ASCII-Kodierung. Dadurch sind praktisch alle alten Systeme, die nix von Zeichensätzen wissen, Unicode-kompatibel. Ein toller Trick also, und Amerika ist wieder fein raus. Soll der Rest der Welt sich um ihre komischen Hieroglyphen selbst kümmern! 😉

Der Perso für Textzeilen

Unicode allein macht also nicht glücklich. Wir bräuchten den Personalausweis für Texte, um die Kodierung richtig zu verstehen. Meta-Daten für Text-Daten sind gefragt. Und ebenda stehen wir jetzt: Mit Unmengen von Einstellungen für Dateisysteme, Terminals, Shell-Kommandozeilen, GUI Toolkits und Webseiten, Fonts und Druckern mühen wir uns ab. Damit alles richtig zusammenspielt, und man am Ende das richtige Zeichen am Bildschirm sieht, ist ein konsequenter Umgang mit den Character Sets wichtig. Für den Programmierer oder Web-Designer bedeutet dies: Seine Dateien müssen in der richtigen Kodierung angelegt werden. Sein Dateisystem und sein Editor müssen damit umgehen können. Sein Webserver muss das Dokument richtig ausliefern, und der Browser muss es richtig interpretieren. Der Browser muss auch den richtigen Font laden und das richtige Zeichen rendern. Das klingt nach einer Menge Arbeit.

Glücklicherweise haben schon so viele Hände ins Uhrwerk gefasst, und ein paar Teile sind schon an Ort und Stelle. Vorausgesetzt, man installiert ein modernes Betriebssystem, so sind Browser, Font und Anzeige bereits funktionstüchtig. Linux-Distributionen aller Art haben nach der Jahrtausendwende fast standardmäßig eine Unicode-Umgebung in UTF-8 aktiviert. UTF-8, das kennen wir unter anderem von Google (!), ist dafür ein sehr gut geeigneter Zeichensatz. Zum einen kann es alle Unicode-Zeichen darstellen, zum anderen ist es fast vollständig kompatibel zu US-ASCII. Nur nicht-US-ASCII-Zeichen (127 und höher) werden aus mehrbytigen Ketten gebildet. Deutsche Umlaute aus zwei Zeichen (zum Beispiel das “ä” des ISO-8859-1 / latin1 aus “Ô und “¤”), japanische Zeichen bereits aus drei Bytes.

Egal ob Unicode oder nicht, wir brauchen immer und überall die richtigen Daten plus deren dazu passenden Meta-Daten. Für Linux-Freunde bedeutet dies: Das Terminal, das wichtigste Element unseres Systems, muss auf die richtige Kodierung eingestellt sein. Sonst erstellen wir den Inhalt in einer völlig falschen Zeichensprache. Alternativ lassen sich natürlich Websites auch mit Klickiklackli-Editoren aufbereiten. Möchte man jedoch die volle Linux-Power mit grep, sed, tail und anderen Text-Tools nutzen, so ist die richtige Einstellung des Terminals Pflicht.

Locales

Doch halt, wir haben etwas vergessen: Es geht nicht nur im die Kodierung, es geht auch um Sprache. Es ist ja wohl ein Unterschied, ob ich sage “latin1”, und morgen verstehe ich nur noch Spanisch, oder ob auf die Meldungen auch in meiner gewünschten Sprache erscheinen. Wir brauchen also Sprache und Kodierung in einem. Und dies vereint die Locale. Sie vereint sogar noch die landesspezifische Version einer Sprache: Aufgebaut ist eine Locale-Angabe aus einer ISO-Sprachkennung, zwei kleinen Buchstaben wie “de”, “en” oder “fr”, gefolgt von einem Unterstrich und einer ISO-Landeskennung in zwei Großbuchstaben wie “DE”, “US” oder “ES” für Spanien. Danach folgt optional mit “@” oder “.” getrennt die gewünschte Kodierung.

Zum Beispiel ist “en_US” das amerikanische Englisch, “en_GB” das britische. Beides ließe sich in Latin1 (“en_US.ISO-8859-1” und “en_GB.ISO-8859-1”) wie auch in UTF-8 (“en_US.UTF-8” und “en_GB.UTF-8”) benutzen. Stellt man nun auf dem Linux-System die richtige Locale ein, so werden alle Meldungen in der gewünschten Sprache und auch Kodierung ausgegeben. Hat man das Terminal auf dieselbe Kodierung gestellt, dann sollten sogar lesbare Zeichen erscheinen. 🙂

Für Freunde der Übersetzung: Gettext

Kennt Linux also alle Sprachen? Das ist wohl unmöglich, kein Programmierer der Welt kann das leisten. Doch viel besser – es ist ganz modular. Dabei wird Software auf Englisch erstellt, doch jeder Textausgabe wird gesagt: Achtung, du bist ersetzbar. Später wird jemand kommen, der dich eventuell austauscht, also mach dir es nicht zu gemütlich! Dieser Vorgang ist die Internationalization, auch i18n genannt.

Mit einem GNU-Tool namens gettext werden dann aus dem Programmtext die Ausgaben extrahiert und in eine Übersetzungsdatei mit der schönen Endung *.po geschrieben. Diese wird dann von Leuten aus aller Welt an die jeweilige Sprache angepasst.  Dies nennt man Localization, im Programmierjargon abgekürzt mit l10n. Diese werden getrennt vom eigentlichen Programm gepflegt. So muss man nur die Sprachpakete installieren, die man wirklich braucht. Das spart jede Menge Platz. Sprachpakete gibt es in Debian ohne Ende:

1
2
3
4
5
6
nudge@linux:~> apt-cache search l10n
iceweasel-l10n-af - Afrikaans Sprachpaket für Iceweasel
iceweasel-l10n-all - Alle Sprachpakete für Iceweasel (Meta)
iceweasel-l10n-ar - Arabisches Sprachpaket für Iceweasel
iceweasel-l10n-be - Weißrussisches Sprachpaket für Iceweasel
...
nudge@linux:~> apt-cache search l10n
iceweasel-l10n-af - Afrikaans Sprachpaket für Iceweasel
iceweasel-l10n-all - Alle Sprachpakete für Iceweasel (Meta)
iceweasel-l10n-ar - Arabisches Sprachpaket für Iceweasel
iceweasel-l10n-be - Weißrussisches Sprachpaket für Iceweasel
...

Habe ich die richtige Locale aktiviert, lädt das Programm die entsprechende *.po-Datei (eigentlich eine davon abgeleitete, komprimierte Version auf *.mo), so gibt das Programm die Übersetzungen aus. Mit diesen Locales arbeitet das Linux-System auch auf der Basis von Kernel-Meldungen. Es kann also auf Spanisch sagen: “Login fehlgeschlagen”, wie auch immer das heißen würde. Auf japanisch sagt mir mein “su” mit falschem Passwort:

1
2
3
4
nudge@linux:~> su
Password:
su: 認証失敗
nudge@linux:~>
nudge@linux:~> su
Password:
su: 認証失敗
nudge@linux:~>

Werd’s der Geier draus schlau! (Naja, euch kann ich es ja verraten. Es wird “ninshoo shippai” ausgesprochen und bedeutet soviel die “Fehler bei der Authentifizierung” – was sonst! ;-)). Wichtig dabei: Die Übersetzungen von Linux sind Übersetzungen der zugrundeliegenden C-Bibliothek, der glibc. Hat ein System gar keine Einstellungen erhalten, so heißt diese Grundeinstellung entsprechend “C”.

Das Terminal auf die richtige Locale einstellen

Also, wie bekomme ich nun raus, ob meine Konsole richtig eingestellt ist? Die derzeitige Sachlage ermittelt man einfach mit dem Kommando locale (wer hätte das gedacht? ;-)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nudge@linux:~$ locale
LANG=de_DE@euro
LANGUAGE=de_DE@euro
LC_CTYPE="de_DE@euro"
LC_NUMERIC="de_DE@euro"
LC_TIME="de_DE@euro"
LC_COLLATE="de_DE@euro"
LC_MONETARY="de_DE@euro"
LC_MESSAGES="de_DE@euro"
LC_PAPER="de_DE@euro"
LC_NAME="de_DE@euro"
LC_ADDRESS="de_DE@euro"
LC_TELEPHONE="de_DE@euro"
LC_MEASUREMENT="de_DE@euro"
LC_IDENTIFICATION="de_DE@euro"
LC_ALL=
nudge@linux:~$ locale
LANG=de_DE@euro
LANGUAGE=de_DE@euro
LC_CTYPE="de_DE@euro"
LC_NUMERIC="de_DE@euro"
LC_TIME="de_DE@euro"
LC_COLLATE="de_DE@euro"
LC_MONETARY="de_DE@euro"
LC_MESSAGES="de_DE@euro"
LC_PAPER="de_DE@euro"
LC_NAME="de_DE@euro"
LC_ADDRESS="de_DE@euro"
LC_TELEPHONE="de_DE@euro"
LC_MEASUREMENT="de_DE@euro"
LC_IDENTIFICATION="de_DE@euro"
LC_ALL=

Das @euro ist ein Synonym für “.ISO-8859-15”, einen relativ jungen Latin1-Zeichensatz, wie ISO-8859-1, aber inclusive Euro-Symbol. Die Grundeinstellung des Systems findet man bei Debian oder Ubuntu in /etc/default/locale. Welche Zeichensätze sich dort einstellen lassen, könnt ihr mit locale -a herausfinden:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nudge@linux:~$ locale -a
C
de_DE
de_DE@euro
de_DE.iso88591
de_DE.iso885915@euro
de_DE.utf8
deutsch
german
ja_JP
ja_JP.eucjp
ja_JP.ujis
ja_JP.utf8
japanese
japanese.euc
POSIX
nudge@linux:~$ locale -a
C
de_DE
de_DE@euro
de_DE.iso88591
de_DE.iso885915@euro
de_DE.utf8
deutsch
german
ja_JP
ja_JP.eucjp
ja_JP.ujis
ja_JP.utf8
japanese
japanese.euc
POSIX

Da hätten wir auch de_DE.utf8 für eine Umstellung auf Unicode. Achtung aber hier, die Angabe in LANG, LANGUAGE oder LC_ALL sollte dann als de_DE.UTF-8 angegeben werden. Gerade diese Angaben macht leider jede Software anders <stöhn>.

Was macht man, wenn eine Locale nicht verfügbar ist?

Zunächst die Sprachausgabe von Linux (also seiner C-Bibliothek glibc) für diese Locale vorbereiten. Diese Bibliothek unterstützt nämlich so viele Sprachen und Zeichensätze, dass nicht alle diese Erweiterungen automatisch installiert werden. Debian oder Ubuntu installieren stattdessen Templates, wie die Meldungen in locale-Dateien generiert werden, die der Benutzer gern hätte (andere Distributionen machen dies ein wenig verschieden). Auf Wunsch auch gern zwei…oder drei…oder viele. Es ist also möglich, dass ein Benutzer auf deutsch arbeitet, während zeitgleich ein weiterer User am gleichen System auf indonesisch begrüßt wird! Na, das ist doch mal was! 🙂

Die dafür von Debian/Ubuntu unterstützten Locales befinden sich in der Datei /etc/locale.gen aufgelistet. Früher waren hier nur die aktiven drin, jetzt sind mittlerweile alle aufgeführt und die aktiven als einige nicht auskommentiert. Man braucht also nun bloß nach der gewünschten Kodierung suchen, und entfernt das #-Zeichen am Anfang der Zeile. Denselben Effekt erreicht man übrigens mit dem Befehl

1
dpkg-reconfigure locales
dpkg-reconfigure locales

Dieser führt die Auskommentierung ebenfalls durch und ruft danach das Programm /usr/sbin/locale-gen auf, welches die Datei /etc/locale.gen liest und für alle dort aktivierten Zeichnsätze das Programm /usr/sbin/localedef durchführt. localedef wiederum generiert aus Templates die locale-spezifischen Sprachausgaben der C-Bibliothek. Ändert man die Datei /etc/locale.gen zu Fuß, muss man locale-gen danach selbst aufrufen. Um bei Unicode zu bleiben: localedef schaut zum Beispiel nach der Zuordnung von Unicode-Nummern zu den Ausgabezeichen. Die Unicode-Nummern sind dabei in UCS-4 hinterlegt, wo jeder Buchstabe eine feste 32-Bit-Länge hat, was 4 Byte entspricht.

dpkg-reconfigure locales - Locales ganz bequem auswählen

dpkg-reconfigure locales - Locales ganz bequem auswählen

Damit haben wir nun alle Sprachen zur Hand, die wir jemals auf unserem System benutzen wollen. Doch welche wollen wir nun aktivieren? Die systemweite Einstellung wird über die Datei /etc/default/locale gesteuert. Mit dem Programm update-locale kann sie auf neue Werte geändert werden. Es empfiehlt sich allerdings, das Basissystem nicht zu verändern. Stattdessen sollte die Einstellung benutzerspezifisch getan werden. Am einfachsten kann man für Freunde der Bash-Shell dazu die folgenden Zeilen in der Datei .bashrc im Home-Directory hinzufügen:

1
2
3
export LANG=de_DE.UTF-8
export LANGUAGE=de_DE:de
export LC_ALL=de_DE.UTF-8
export LANG=de_DE.UTF-8
export LANGUAGE=de_DE:de
export LC_ALL=de_DE.UTF-8

Wie schön wäre hier ein LDAP-Eintrag, der beim Login ausgelesen wird. Unglaublich schön, denke ich. Jetzt müssen wir aber noch einmal kontrollieren, ob das richtig ankam:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nudge@linux:~> locale
LANG=de_DE.UTF-8
LANGUAGE=de_DE:de
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC="de_DE.UTF-8"
LC_TIME="de_DE.UTF-8"
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY="de_DE.UTF-8"
LC_MESSAGES="de_DE.UTF-8"
LC_PAPER="de_DE.UTF-8"
LC_NAME="de_DE.UTF-8"
LC_ADDRESS="de_DE.UTF-8"
LC_TELEPHONE="de_DE.UTF-8"
LC_MEASUREMENT="de_DE.UTF-8"
LC_IDENTIFICATION="de_DE.UTF-8"
LC_ALL=de_DE.UTF-8
nudge@linux:~> locale
LANG=de_DE.UTF-8
LANGUAGE=de_DE:de
LC_CTYPE="de_DE.UTF-8"
LC_NUMERIC="de_DE.UTF-8"
LC_TIME="de_DE.UTF-8"
LC_COLLATE="de_DE.UTF-8"
LC_MONETARY="de_DE.UTF-8"
LC_MESSAGES="de_DE.UTF-8"
LC_PAPER="de_DE.UTF-8"
LC_NAME="de_DE.UTF-8"
LC_ADDRESS="de_DE.UTF-8"
LC_TELEPHONE="de_DE.UTF-8"
LC_MEASUREMENT="de_DE.UTF-8"
LC_IDENTIFICATION="de_DE.UTF-8"
LC_ALL=de_DE.UTF-8

Alles klar, das Schiff läuft aus mit Kurs Unicode. Den richtigen Beweis erbringen wir jedoch erst jetzt, und zwar mit einem einfachen Test:

1
2
3
nudge@linux:~> echo "äöü" > test.txt
nudge@linux:~> ls -al test.txt
-rw-r--r-- 1 nudge nudge 7 19. Apr 12:08 test.txt
nudge@linux:~> echo "äöü" > test.txt
nudge@linux:~> ls -al test.txt
-rw-r--r-- 1 nudge nudge 7 19. Apr 12:08 test.txt

Die Datei ist also 7 Byte groß! Das siebente Byte ist wohl das Datei-Ende-Zeichen “^D”, und jeder Buchstabe verbraucht nun 2 Byte:

1
2
3
4
5
6
nudge@linux:~> export LANG=de_DE.ISO-8859-1
nudge@linux:~> export LANGUAGE=de_DE.ISO-8859-1
nudge@linux:~> export LC_ALL=de_DE.ISO-8859-1
nudge@linux:~> xterm
nudge@linux:~> cat test.txt
äöü
nudge@linux:~> export LANG=de_DE.ISO-8859-1
nudge@linux:~> export LANGUAGE=de_DE.ISO-8859-1
nudge@linux:~> export LC_ALL=de_DE.ISO-8859-1
nudge@linux:~> xterm
nudge@linux:~> cat test.txt
äöü

Toll. 😉 Der erste Zeichen-Salat, der mich richtig freut.

Fazit

Das wäre es für heute. Unser Linux-System ist auf UTF-8 umgestellt, und nebenbei haben wir die Spezifika von Linux bezüglich Kodierung und Sprache kennengelernt. Damit Webseiten mit PHP und MySQL mitziehen, gibts demnächst eine Fortsetzung. Um es in der richtigen Locale zu sagen: Bleib aufm Sender!


Das mark ich mir: Alltagz Mr Wong Yigg Del.icio.us Yahoo MyWeb Blinklist Google folkd
 

Leave a Reply

Your email address will not be published. Required fields are marked *