Dateinamen in UTF-8 auf beliebigen Betriebssystemen

UTF-8-kodierte Dateinamen mit Perl anlegen, lesen und löschen

In diesem Artikel geht es nicht um das warum, daß muss jeder selbst mit sich ausmachen. Hier geht es nur um den richtigen Umgang und um das Verständnis ob dieser Dinge im Zusammenhang mit Perl.

Ein Dateisystem speichert Oktetten!

So bestehen Dateinamen also nicht aus Zeichen sondern aus Oktetten!

Oktetten oder Bytes?

In manchen Dokumentationen heißt es Bytes, in anderen Oktetten, wie z.B. in der POD zum Core-Modul Encode. Hier werden beide Begriffe benutzt, hinzu kommt, daß Oktetten bzw. Bytes eine bestimmte Wertigkeit haben. Das Byte ist die kleinste physikalische Speichereinheit im Filesystem eines jeden Betriebssystems. Jedes Byte verkörpert einen vorzeichenlosen Integer mit der Wertigkeit von 0..255. Das Byte was im Texteditor aussieht wie ein 'A' verkörpert bspw. die Zahl 0x41 (65).

Warum sehen wir im Explorer aber Zeichen?

Die Shell eines jeden Betriebssystems kennt die Kodierung. Genauso wie man einem Texteditor oder Browser mittteilen muss welche Kodierung vorliegt, lesen diese Ausgabegeräte (Shell, Browser, Editor) die vorliegenden Bytes und machen aus diesen Bytes lesbare Zeichen. Und welche Kodierung das ist, wird der Shell auf WinXP z.B. über die Codepage mitgeteilt, welche zu konfigurieren ist.

Der Zusammenhang zwischen Bytes und Codepoint schließlich wird durch die Kodierung selbst bestimmt. Wenn Letztere UTF-8 heißt, macht eine Shell z.B. Folgendes:

Des Weiteren kann der Codpoint eines Zeichen erst bestimmt werden, wenn alle zum Zeichen gehörigen Bytes, also deren Wertigkeiten, im Hauptspeicher liegen. Der Zusammenhang zwischen Anzahl und Wertigkeiten der zu einem Zeichen gehörigen Bytes wird durch die Kodierung festgelegt.

Beim Schreiben eines Dateinamen ist es genau umgekehrt. Ausgehend davon daß der Codepoint vorliegt, wird aus diesem berechnet welche Bytes sich ergeben. So ergibt sich aus dem Codepoint für das Eurozeichen (0x20AC) eine Folge von Bytes mit den Wertigkeiten 0xE2 0x82 0xAC.

Die Praxis mit Perl

Bis hierher hört sich das alles etwas kompliziert an, Perl macht jedoch den Umgang damit recht einfach. Bleiben wir mal beim Eurozeichen und geben den Codepoint vor, zu berechnen ist die Bytefolge, zunächst der Codepoint:

use strict;
use warnings;
use Encode;
use IO::File;

my $bytes = "€";
# Scriptdatei UTF-8-kodiert
# von daher sind das die richtigen Oktetten

# Zum Berechnen des Copdepoint
# muss intern die Kodierung bekanntgemacht werden
my $utf8 = decode_utf8($bytes);

# Codepoints ausgeben, die Berechnung ist in
# der U Schablone implementiert
$, = " ";
print unpack "U*", $utf8; # 8364

Und jetzt kommt eine Sache die immer wieder zu Missverständnissen führt. Will man nämlich die Datei mit dem Namen '€' anlegen, darf hierfür nicht die Zeichenkette $utf8 benutzt werden denn sie gilt nur intern! Vielmehr ist zur Übergabe an das OS die interne Kodierung wieder abzuschalten:

# Bytefolge berechnen
# Dazu die interne Kodierung wieder abschalten
my $raw = encode_utf8($utf8);
print $raw; # €

# Bytewertigkeiten
print unpack "C*", $raw; # 226 130 172

Merke diesen Grundsatz

Dateiname (und auch Dateiinhalte) sind als Rohdaten (Oktetten, Bytes) an das Betriebssystem zu übergeben! Eine etwaige interne Kodierung (siehe Encode) ist da also abzuschalten, denn eine interne Kodierung gilt eben nur intern. Auch wenn es dieselbe ist, welche die Shell eines OS verwendet!

Dieser Gundsatz gilt übrigens auch für sämtliche Ausgaben von Strings auf STDOUT, Sockets und FileHandles. Allgemein: Wenn Daten den Hauptspeicher verlassen in Form von Dateien, HTTP Requests usw.

Und genauso, wie Rohdaten an das OS geschickt werden, bekommt die auch ein Browser! Soll ein Browser also das Eurozeichen darstellen, bekommt er dafür nicht etwa eine perlintern kodierte Zeichenkette sondern die richtigen Bytes, Wertigkeiten siehe obenstehend.

Und nun alles zusammen, getestet auf WinXP

Das Anlegen einer Datei mit einem UTF-8-kodierten Dateinamen:

use strict;
use warnings;
use Encode;
use IO::File;


my $dir = "d:/tmp/files";
my $fname = 'äöü@€.txt'; # Scriptdatei utf-8-kodiert
my $fh = IO::File->new;

# Lege Datei an im Bytemode
$fh->open("$dir/$fname", O_CREAT) or die $!;
$fh->close;


chdir $dir or die $!;
my @files = <*.txt>;

# Ausgabe im Bytemode OK
print "@files\n";

# Zur Kontrolle die Cpodepoints
my $cname = $files[0];

# interne Kodierung einschalten
my $utf8 = decode_utf8($cname);

# Codepoints ausgeben
$, = " ";
print unpack "U*", $utf8; # 228 246 252 64 8364 46 116 120 116

# und das hier gibt eine warnung Wide character in print...
print $utf8; # Wrongg!!!

# Weil auf STDOUT keine kodierten Zeichenketten
# sondern Oktette gehören!

Mit der Codepage, z.B. cp1252 hat das alles nichts zu tun!

Abschließend betrachten wir noch den umgekehrten Fall: Die Codepage sei 1252 auf einen WinXP und wir legen eine Datei an mit dem Namen . Dazu benutzen wir jedoch nicht Perl sondern den Explorer, welcher auch brav diesen Namen anzeigt. Mit Perl jedoch lesen wir das Verzeichnis aus und erhalten den Namen der Datei:

$, = " ";
chdir "d:/tmp/files";
my $fname = <*>;
# Name, Anzahl der Bytes, Bytewertigkeit
print $fname, length $fname, unpack "C*", $fname;
# € 1 128

Dieser Code, weil er bytesemantisch arbeitet, liefert also die Oktetten zum Dateinamen. Folgerichtig gibt length() nicht die Anzahl der Zeichen sondern die Anzahl der Oktetten aus, in unserem Fall eine 1. Codepage 1252 kodiert das Eurozeichen mit einem Byte der Wertigkeit 0x80. Mit einem im Dateinamen ergibt sich also mit obenstehendem Test korrekt dieses Ergebnis.

Fehlersuche

Natürlich sieht man in der Shell nur Bytesalat, wenn die Shell mit cp1252 konfiguriert ist, die Datei jedoch mit einem UTF-8-kodierten Namen angelegt wurde. Deswegen ja die eingangs kommunizierte Bemerkung bezüglich der Frage des Warum: Man sollte schon wissen was man tut. Nichtsdestoweniger kann es immer mal wieder dazu kommen, daß nicht die erwarteten Zeichen dargestellt werden. Eine Fehlersuche kann jedoch nur dann erfolgreich sein, wenn die hier dargestellten obenstehenden Grundsätze beachtet werden!


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.