Teil von SELFHTML aktuell Teil von Artikel Teil von CGI/Perl

Plattformübergreifendes Handling von Zeilenumbrüchen

nach unten Autor
nach unten Andere Plattformen, andere Sitten
nach unten Was ist ein Newline?
nach unten Perls logisches Newline
nach unten binmode – Newline-Umsetzung abschalten
nach unten Hinweise und Standardtricks
nach unten Schlussbemerkungen und Literatur

Roland Blüthgen (a.k.a. Calocybe)

E-Mail: >E-Mail calocybe@yahoo.com

Bei Fragen zu diesem Beitrag bitte den Autor kontaktieren!

nach obennach unten

Andere Plattformen, andere Sitten

Zeilenumbrüche in Textdateien werden je nach verwendetem Betriebssystem oder zugrundeliegender Hardwareplattform unterschiedlich codiert. Wenn Sie mit Perl Textdaten verarbeiten, wird Sie das zunächst nicht weiter kümmern, wenn Sie nicht – ja wenn Sie nicht mächtig-coolen-Code™ schreiben. Mächtig-cooler-Code™ zeichnet sich dadurch aus, dass er in weiser Voraussicht von Anfang an so gestaltet ist, dass er später mit möglichst geringem Aufwand auf andere Systeme portiert werden kann. Und wenn Sie das tun, dann wird Sie der Zeilenumbruch in fieser Weise und ohne Gnade wieder einholen und genau in dem Moment zuschlagen, wenn Sie nicht an ihn denken, z.B. wenn Sie eine Binärdatei auf einem Windowssystem bearbeiten. Wieso bei dem Aufeinandertreffen verschiedener Plattformen Probleme entstehen können und wie man die verschiedenen Kulturen integriert, das erfahren Sie in diesem Artikel.

Hinweis:

Perls Auffassung davon, was eine "Zeile" ist, lässt sich mit der bereichsübergreifende Seite vordefinierten Variable $/ (oder auch mit dem Alias $INPUT_RECORD_SEPARATOR) auf einfache Weise komplett verändern. Das soll jedoch nicht Thema dieses Artikels sein. Hier wird angenommen, dass diese Variable für das Zeilentrennzeichen immer den Wert des (logischen) nach unten Newlines hat, also $/ eq "\n" gilt. Wenn Sie den Wert der Variable nicht selbst im Programm verändert haben, ist dies immer der Fall.

nach obennach unten

Was ist ein Newline?

Wenn Sie eine Textdatei betrachten, dann werden Sie feststellen, dass diese aus einer oder mehreren einzelnen Zeilen besteht. Das ist nun sicherlich keine weltbewegende Neuigkeit. Wichtig ist aber, sich zu vergegenwärtigen, dass eine Datei aus Sicht des Computers (bzw. des Betriebssystems) nichts weiter als eine Aneinanderreihung von einzelnen Zeichen ist – die Idee einer Zeile gibt es da noch nicht.

Zeilen entstehen erst, wenn ein ganz bestimmtes Zeichen (oder auch eine Kombination aus mehreren Zeichen) dieser Schlange als Zeilenumbruch interpretiert wird. Es gibt also ein Trennzeichen, das die einzelnen Zeilen voneinander separiert. Dieses Zeichen oder die Zeichenkombination nennt man Newline. Dabei ist es zunächst zweitrangig, welches Zeichen das konkret ist, Hauptsache ist, man kann die Zeilen voneinander unterscheiden. Doch da beginnt das Dilemma: Soll eine jede Software die Zeichenschlange einer Textdatei korrekt in Zeilen auftrennen, dann müssen natürlich alle dasselbe Zeichen als Newline betrachten. Das ist aber in der Praxis so ganz und gar nicht der Fall!

Zum Glück ist es aber auch nicht so, dass jedes einzelne Programm seine eigene Vorstellung vom Newline hat. Normalerweise wird auf einer Betriebssystem-Klasse immer dieselbe Zeichenkombination verwendet. Für die verbreitetsten Systeme heißt das im speziellen:

Betriebsystemklasse Zeichenkombination ASCII-Repräsentation
dezimal oktal hexadezimal
meiste Unix-Derivate incl. Linux LF 10 012 0x0A
MS-DOS, alle Windows-Varianten CR LF 13 10 015 012 0x0D 0x0A
Mac-OS CR 13 015 0x0D

Auf VMS werden abhängig von der jeweiligen Datei verschiedene Zeichenkombinationen als Newline verwendet. Aber das macht nichts – Perl kümmert sich schon darum, wie wir gleich sehen werden.

In der vorhergehenden Tabelle steht CR für Carriage-Return, LF für Linefeed. Das sind zwei wohldefinierte Zeichen des bereichsübergreifende Seite ASCII-Zeichensatzes.

Wie sich die Verwendung unterschiedlicher Newline-Zeichen äussert, weiss jeder Windows-Benutzer, der schon mal mit dem Notepad eine Datei, die von einer Unix-Maschine kommt, geöffnet hat. Da ist der ganze Text einfach aneinandergereiht, und dort wo ein Zeilenumbruch sein sollte, sieht man nur ein kleines schwarzes Kästchen. Umgekehrt sieht man in einem Editor unter Unix, mit dem man eine DOS/Windows-Datei öffnet, manchmal noch ein zusätzliches Zeichen oder den Code ^M, welcher für das CR-Zeichen steht, an jedem Zeilenende. Modernere Software kann unabhängig vom Betriebssystem die Art des Zeilenumbruchs automatisch erkennen. Perl auf Unix gehört aber zum Beispiel nicht dazu, weshalb es zu Problemen kommt, wenn man auf Windows-Rechnern erstellte Scripts unverändert auf einen Unix-Rechner lädt. Genaueres dazu finden Sie im Artikel Seite Inbetriebnahme eines CGI-Skripts von Michael Schröpl.

Das Newline als Trennzeichen zu betrachten ist übrigens nicht ganz korrekt. Genaugenommen muss man es als Zeilenabschlusszeichen ansehen, denn es ist immer das letzte Zeichen einer Zeile und gehört also zur Zeile dazu. Wenn Sie eine Textdatei einlesen und die einzelnen Zeilen untersuchen, werden Sie das feststellen. (Hinweis: In der letzten Zeile einer Datei fehlt das Newline oftmals. Verlassen Sie sich also nicht auf dessen Vorhandensein. z.B. wenn Sie die eingelesenen Daten analysieren.)

nach obennach unten

Perls logisches Newline

Perl, ursprünglich ein Unix-Programm, wurde im Laufe der Zeit auf beeindruckend viele Hardwareplattformen und Betriebssysteme portiert. Zwischen diesen Systemen gibt es Unmengen von Inkompatibilitäten, weshalb ein Perlprogramm auch nicht automatisch auf allen Plattformen läuft, wenn man sich nicht verdammt viel Mühe gibt. Aber für das Problem der Zeilenumbrüche hat Perl zum Glück eine einfache Lösung: Das logische Newline.

Perl weiss selbst sehr gut, auf welchem System es gerade läuft und kennt daher auch die dort vorherrschende Repräsentation eines Newlines. Deswegen kann es die eigentliche Codierung auf dem System beim Einlesen einer Textdatei in eine interne, logische Codierung umsetzen – und tut das auch. "\n" ist dieses logische Newline. Denken Sie daran, dass die Double-Quotes (") Perl zur Interpretation von Backslash-Sequenzen veranlasst. Es handelt sich also nicht um einen Backslash '\\' gefolgt von einem 'n', sondern um das Zeichen "\n". Genauso, wie beim Einlesen einer Datei die systemspezifischen Newlines in Perls logische umgesetzt werden, wird Perl beim Schreiben in Dateien die logischen in die systemspezifischen zurückübersetzen. nach unten Weiter unten erfahren Sie aber noch, wie Sie dieses Verhalten abschalten können.

Ein weit verbreiteter Irrtum ist, dass das logische Newline ein ganz normales ASCII-Zeichen ist, nämlich das Linefeed (LF, ASCII 10). Deshalb sieht man oft, dass mit dem print-Befehl ein "\n" ausgegeben wird, wenn in Wirklichkeit das Zeichen LF gemeint ist. Das ist falsch! Oft funktioniert das zwar ganz gut, weil ein Programm in der Praxis meist auf nicht allzu vielen verschiedenen Plattformen laufen muss. Aber Sie woll ja mächtig-coolen-Code™ schreiben, und daher achten Sie genau darauf, in welcher Situation Sie welches Zeichen verwenden: Was genau sich hinter "\n" verbirgt, also welcher ASCII-Wert, ist in Wirklichkeit völlig ungewiss. Es ist noch nicht mal gesagt, dass es überhaupt einen solchen Wert hat. Das heisst, wenn Sie auf manchen Systemen print "\n" ausführen, dann wird gar nichts ausgegeben. (Solche Systeme kennen soetwas wie ein Newline einfach nicht.)

Verwenden Sie also niemals "\n", wenn Sie eigentlich das Zeichen LF ausgeben wollen, das oft, aber eben oftmals auch nicht, dieselbe numerische Repräsentation hat. Verwenden Sie in dem Fall stattdessen die Möglichkeit, Zeichen mit ihrem oktalen oder hexadezimalen ASCII-Wert aufzuschreiben. Im Falle von LF ist das "\012" bzw. "\x0A". Ähnliches gilt für das Zeichen "\r", welches oft fälschlicherweise anstelle eines CR notiert wird. Dieses hat zwar keine logische Bedeutung wie ein Newline, aber genau wie "\n" hat es ebenfalls keinen systemübergreifend konstanten ASCII-Wert.

Umgekehrt sollten Sie aber auch nicht die konkreten Zeichen CR oder LF (also in ihrer numerischen Codierung) verwenden, wenn Sie wirklich "Zeilenende" meinen. Kommt Ihnen das jetzt langsam alles ein bißchen kompliziert vor? Keine Angst, ist es gar nicht: Verwenden Sie einfach "\n", wenn Sie "Zeilenende" in einem Text meinen, verwenden Sie aber die Zeichen CR und LF, wenn Sie diese ausgeben müssen. (Letzteres ist häufig bei der Kommunikation über Netzwerke auf Protokollebene notwendig. Viele Protokolle, nicht zuletzt auch HTTP, verlangen an verschiedenen Stellen speziell nach der Zeichenkombination CR+LF, also "\015\012".)

Der Vollständigkeit halber soll für die gängisten Systeme gezeigt werden, welche ASCII-Werte (in oktaler Notation) die Zeilenumbruchzeichen haben und welche Transformation Perl beim Einlesen einer Datei vornimmt (beim Schreiben einer Datei natürlich genau umgekehrt). Die Codes für die physischen (systemspezifischen) Newlines kennen Sie bereits; sie werden hier aus Gründen der Übersichtlichkeit nur wiederholt.

Betriebsystemklasse Physisches Newline Logisches Newline Transformation beim Einlesen
meiste Unix-Derivate incl. Linux "\012" (LF) "\n" eq "\012" (LF) keine Änderung
MS-DOS, alle Windows-Varianten "\015\012" (CR LF) "\n" eq "\012" (LF) CR LF —> LF
Mac-OS "\015" (CR) "\n" eq "\015" (CR) keine Änderung

Wie Sie erkennen können, gilt die verbreitete Annahme, dass "\n" eq "\012" eq "\x0A" ist, auf den meisten Unixen sowie auf DOS/Windows tatsächlich.

nach obennach unten

binmode – Newline-Umsetzung abschalten

Perls Haupteinsatzzweck ist zwar die Verarbeitung von Texten, aber selbstverständlich müssen Sie auch ab und zu mit Binärdaten wie z.B. Bildern oder komprimierten Dateien umgehen, und sei es nur, um diese von einem Ort zum anderen zu kopieren. Nun ist es so, dass binäre Daten auch teilweise genau die Zeichenkombination enthalten, die in Textdateien als physisches Newline verwendet wird, nur dass sie dort eben keinen Zeilenumbruch darstellen. Wenn Sie eine solche Datei einlesen und Perls Umsetzung von physischen in logische Newlines findet statt, dann heißt das effektiv, dass die Daten zerstört werden! Bei Binärdaten darf die Umsetzung also auf keinen Fall stattfinden!

Da Perl nicht feststellen kann (oder sich diese Entscheidung zumindest nicht herausnehmen darf), ob eine Datei textual oder binär ist, müssen Sie das explizit klarstellen. Dazu dient die Funktion bereichsübergreifende Seite binmode. Wenden Sie diese unmittelbar nach dem erfolgreichen Öffnen einer Datei auf das dann gültige Datei-Handle an.

Auf solchen Systemen, auf denen ohnehin keine Transformation der Newlines stattfindet, weil die physischen und die logischen die gleichen sind, bewirkt binmode nichts. Es ist dort eine Null-Operation, die nicht stört. Sie brauchen sich da also keine Gedanken machen und einfach nur immer binmode zu notieren, da ihr Programm ja unverändert auch auf Systemen laufen soll, auf denen extra in den binary mode geschaltet werden muss.

Wichtig ist, dass Sie binmode anwenden, bevor die erste Ein- oder Ausgabeoperation stattfindet. Zwar führt Perl Ihren Befehl auch später noch ohne zu zögern aus, das kann auf verschiedenen Betriebssystemen jedoch mehr oder weniger störende Nebenwirkungen haben. So wird unter Windows möglicherweise der Dateizeiger, der mit tell abgefragt werden kann, verändert. Unter VMS führt der Aufruf sogar zum Schließen und neuen Öffnen der Datei.

Die Umschaltung des Transformationsmodus erfolgt nicht etwa global, sondern pro geöffneter Datei. Somit sind Sie in der Lage, Text- und Binärdateien gleichzeitig zu bearbeiten. Wenn Sie also mehrere binäre Dateien lesen oder schreiben (egal ob gleichzeitig oder nacheinander), müssen Sie binmode auf jedes Datei-Handle separat anwenden. Haben Sie ein Handle einmal in den Binärmodus geschaltet, ist eine Rückschaltung in den Textmodus nicht mehr möglich. Sie müssten, wenn Sie das wirklich brauchen, die Datei schliessen und wieder öffnen. Normalerweise kann man sich aber vorher entscheiden, in welchem Modus man arbeiten will, sodass eine Rückschaltung nicht notwendig ist.

Beispiel:

#!/usr/bin/perl -w

use strict;

my $src = 'source-file.bin';
my $dst = 'dest-file.bin';
my $CHUNKSIZE = 64*1024;    # copy 64kB chunks
my ($buf, $i);

# open files and check if all went ok
open(SRC, "<$src") || die("Cannot open $src for reading because: $!");
unless (open(DST, ">$dst")) {
    close(SRC);
    die("Cannot open $dst for writing because: $!");
}

# switch newline transformation off
binmode(SRC); binmode(DST);

# copy
while ($i = read(SRC, $buf, $CHUNKSIZE)) {
    print DST $buf;
    last if ($i < $CHUNKSIZE);
}
warn("Failed to read from $src: $!") unless (defined($i));

# clean up
close(SRC); close(DST) || warn("Something went wrong on closing handle for $dst: $!");

Erläuterung:

Dieser kleine Programmschnipsel fertigt eine Kopie der Datei source-file.bin unter dem Namen dest-file.bin an. Nach dem Öffnen der Dateien, das im Fehlerfall zum Abbruch des Programm mittels die() unter Ausgabe einer aussagekräftigen Fehlermeldung führt, werden Ein- und Ausgabedateihandle in den Binärmodus geschaltet. Dabei ist es unerheblich, ob es sich wirklich um eine binäre Datei handelt. Auch von der Kopie einer Textdatei wird erwartet, dass sie dem Original auf jedes einzelne Bit genau gleicht! Sodann wird in einer simplen Lesen-Schreiben-Schleife der Dateiinhalt der Quelldatei in die Zieldatei übertragen und zum Schluss die Dateien natürlich ordnungsgemäß geschlossen.

Könnte man nicht genauso gut beide Dateihandles im Textmodus belassen? Schliesslich erfolgt doch beim Schreiben der Zieldatei dieselbe Umsetzung, die beim Einlesen stattgefunden hat, in die andere Richtung. Nein! Wenn Sie auf diese Weise z.B. eine Datei auf einem MS-DOS-System kopieren, auf dem der physische Zeilenbegrenzer aus den zwei Zeichen CR+LF besteht, werden diese Sequenzen beim Einlesen in "\n" übertragen, welches dort mit demselben ASCII-Wert wie das LF codiert wird. Kommt in einer Binärdatei ein einzelnes Zeichen LF vor, so wird dieses beim Einlesen natürlich unberührt gelassen, und es sieht dann für Perl genauso aus, wie wenn in dem Puffer $buf ein "\n" steht. Wenn Sie jetzt in die neue Datei schreiben, wird Perl aber jedes "\n" zu CR+LF "zurück"-übersetzen. Und damit werden auch die Zeichen, die ursprünglich ein einzelnes LF waren, zu CR+LF in der neuen Datei. Die Kopie ist damit zerstört – das ist nicht so gut.

nach obennach unten

Hinweise und Standardtricks

Da Sie jetzt, wo Sie das alles wissen, zweifellos umgehend ein paar Nächte durchmachen wollen, um die Welt mit einer neuen ascii-text-verarbeitenden Killer-Applikation wieder ein bißchen besser zu machen, sollen Sie noch ein paar Tipps mit auf den Weg bekommen.

chomp – Newline abhacken

Wenn Sie eine Textdatei zeilenweise einlesen, haben Sie als letztes Zeichen (fast) jeder Zeile ein "\n" in Ihren Variablen stehen. Das ist bei der weiteren Verarbeitung oftmals nicht gewünscht. Deshalb gibt es eine einfache Funktion, bereichsübergreifende Seite chomp, die das letzte Zeichen eines Strings abhackt, wenn - und nur wenn - dieses ein Newline ist. (Beachten Sie, dass dies nur gilt, wenn Sie nicht vorher den Wert der vordefinierten Variable $/ bzw. $INPUT_RECORD_SEPARATOR verändert haben.) Sie können es auf einen einzelnen Skalar genauso anwenden wie auf eine Liste. Im letzteren Fall geht chomp die Liste durch und bearbeitet jedes Element separat. Details über die Arbeitsweise der Funktion finden Sie in der Perldokumentation unter englischsprachige Seite perldoc -f chomp.

$ in regulären Ausdrücken und das Newline

Wenn Sie bereits mit bereichsübergreifende Seite regulären Ausdrücken gearbeiten haben, wissen Sie (oder sollten zumindest), dass ein $ als letztes Zeichen eines Suchmusters (Pattern) das Ende der geprüften Zeichenfolge bezeichnet. Weniger bekannt ist, dass dabei ein Newline, das direkt am Ende der Zeichenkette steht, ignoriert wird. D.h. der Ausdruck /(\d+)$/ findet die Ziffernkette in "abc123" genauso wie in "abc123\n". In manchen Fällen ist das aber unerwünscht; dort muss man ganz genau wissen, wann der String zu Ende ist. Verwenden Sie in solchen Fällen die Backslash-Sequenz \z anstelle des $. Der Ausdruck /(\d+)\z/ würde also beim zweiten der Beispielstrings kein "Match" mehr liefern.

Verarbeitung von Zeilenumbrüchen unterschiedlicher Herkunft

Manchmal müssen Sie Textdaten verarbeiten, von denen Sie nicht wissen, von welcher Art von System diese kommen. Gerade bei CGI-Programmen ist das regelmäßig der Fall. Da Sie nicht wissen, mit welchem System die Besucher Ihrer Website arbeiten, wissen Sie auch nicht, welche Art von Zeilenumbrüchen diese verwenden, wenn sie Ihnen einen Text über ein Formular schicken, wie dies bei Gästebüchern oder Foren normalerweise stattfindet. Und Perl weiss erst recht nichts über diese Zeilenumbrüche, denn Perl kennt ja nur Ihr eigenes System, aber nicht das Ihrer Besucher. Ihr Programm wird also mit allen möglichen Arten von Newlines konfrontiert werden und Perl kann nichts Automatisches für Sie tun.

Sie können das Pattern \015\012|\015|\012 in regulären Ausdrücken verwenden, um die am weitesten verbreiteten Formen der physischen Newlines (nämlich die in den obigen Tabellen aufgezeigten) zu erkennen. Dieser Ausdruck ist eine einfache bereichsübergreifende Seite Alternation der drei ASCII-Zeichen(kombinationen) CR+LF, CR und LF. Sie können ihn zum Beispiel in einem Ersetzungsausdruck verwenden, der die Zeilenumbrüche in einem hereinkommenden Text in die logischen Newlines Ihres Systems umsetzt: $text =~ s/\015\012|\015|\012/\n/g;. Oder Sie bereiten den Text gleich auf die Ausgabe in ein HTML-Dokument vor, setzen also das HTML-Element <br> ein: $text =~ s/\015\012|\015|\012/<br>/g;.

nach obennach unten

Schlussbemerkungen und Literatur

Um der Korrektheit willen ein paar Bemerkungen zum Schluss:

Die Umsetzung der Newlines nimmt genaugenommen nicht Perl selbst vor, sondern die C-Runtime-Bibliothek, gegen die Perl gelinkt ist. (Nein, Sie müssen das nicht verstehen.) Portabler Umgang mit Zeilenumbrüchen ist also kein perl-spezifisches Thema; C-Programmierer haben dieselben Probleme. Das logische Newline ist also keine Erfindung von Perl. Aus Sicht des Perl-Programmierers und damit für den vorliegenden Artikel ist das aber unwesentlich.

"Mächtig-cooler-Code" ist natürlich kein geschütztes Warenzeichen. Das ™-Zeichen ist ein oft verwendetes Stilmittel.

Der Inhalt dieses Artikels ist im wesentlichen aus der Dokumentation zu Perl zusammengeklaut. Allerdings sind dort die Informationen über viele unterschiedliche Kapitel verstreut und nicht immer leicht zu finden, sodass dieser Artikel durchaus eine gewisse Daseinsberechtigung hat. Die wichtigsten Bezugsstellen sind:
englischsprachige Seite perldoc -f binmode
englischsprachige Seite perldoc -f open
englischsprachige Seite perldoc perlop, Abschnitt "Quote and Quote-like Operators"
englischsprachige Seite perldoc perlport, vor allem englischsprachige Seite Abschnitt "Newlines"
englischsprachige Seite perldoc perlipc, Abschnitt "Internet Line Terminators"
englischsprachige Seite perldoc perlre, Abschnitt "Regular Expressions"

Aber aus Büchern kann man nicht alles lernen. Manche Dinge erfahren Sie nur, wenn Sie mit anderen Leuten zusammen Probleme lösen, so wie das im bereichsübergreifende Seite SELFHTML-Forum tagtäglich geschieht, oder den anderen wenigstens beim Diskutieren zuschauen.

Teil von SELFHTML aktuell Teil von Artikel Teil von CGI/Perl

© 2007 bereichsübergreifende Seite Impressum