Bereits im Artikel „Dateikonvertierung unix2dos und dos2unix“ haben wir uns mit der Konvertierung von systemspezifischen Zeilenendungen befasst. Wenn man genau weiß, welche Zeilenendung eine Datei verwendet, sind die vorgestellten Skripten sehr nützlich. Häufig haben wir jedoch den Fall, Textdateien von unbekannten Systemen zu erhalten. Es wäre also praktisch, wenn wir ein Skript hätten, welches uns automatisch die erhaltene Textdatei in das Unix-Format wandeln kann, unabhängig davon, welche Zeilenendung in der Originaldatei verwendet wird. Ein solches Skript eoln2unix wollen wir in diesem Workshop entwickeln. Das Skript soll selbst erkennen, welches Zeilenendeformat vorliegt und dieses gegebenenfalls wandeln.
Zeilenendeformate
Sehen wir uns zuerst an, welche Zeilenendeformate wir überhaupt vorfinden können, die wir in unserem Skript erkennen müssen:
- LF wird von Unix-Systemen verwendet, also auch von Gnu/Linux und macOS bzw. OS X, wie es bis vor Kurzem noch hieß.
- CRLF wird von DOS bzw. Windows-Systemen verwendet.
- CR wurde ehedem vom Mac-Rechnern benutzt, bevor sich Apple zu Unix bekannte.
- LFCR könnte zumindest theoretisch vorkommen, auch wenn ich kein System kenne, welches diese Zeilenendung verwendet.
Jetzt stellt sich nur noch die Frage, wie wir die Zeilenenden erkennen. Dazu bietet sich das file-Kommando an.
Auf eine DOS-/Windows-Textdatei angewendet, meldet es beispielsweise:
1 2 |
$ file text.dos text.dos: UTF-8 Unicode text, with CRLF line terminators |
Auf die gleiche Datei im Unix-Format angesetzt, sieht das Ganze so aus:
1 2 |
$ file text.unix text.unix: UTF-8 Unicode text |
Wer merkt etwas? – Nun, der interessante Teil des Ergebnisses ist der, der mit „with“ beginnt. Danach steht, in Leerzeichen eingeschlossen, die erkannte Zeilenendung. Bei Unix-Dateien schweigt das file-Kommando. Die Idee ist also, eine ausgewiesene Dateiendung zu extrahieren und dann fallweise, je nach Dateiendung, eine Konvertierung vorzunehmen.
Skript zur Zeilenendeerkennung
Schreiben wir uns ein kleines Test-Skript, um die erkannten Zeilenendungen auszugeben:
1 2 3 4 |
#!/bin/bash # Zeilenendekennung extrahieren eoln_type="$(file $1 | sed 's/.*with //' | sed 's/ .*//')" echo $eoln_type |
Das Skript benutzt das sed-Kommando, um die Ausgabe von file zu filetieren. Wir entfernen alle Zeichen bis zu einem möglichen „with“, inklusive des folgenden Leerzeichens. Sodann wird alles weggeschnitten, was einer eventuell vorhandenen Zeilenendekennung folgt. Wenn eine Zeilenendekennung ausgegeben wurde, bleibt diese übrig und wird der Variablen eoln_type zugewiesen. Diese geben wir dann einfach aus.
Zeilenenden manuell konvertieren
Im Grunde sind die erforderlichen Operationen ganz einfach. Nehmen wir beispielsweise eine DOS-/Windows-Datei. Dann wandelt das folgende Kommando die Zeilenenden nach Unix um:
1 |
$ tr -d '\r' <text.dos >text.unix |
Selbiges erreichen wir natürlich auch mit
1 |
$ cat text.dos | tr -d '\r' >text.unix |
Wir löschen einfach das CR
(Carriage Return) aus der Zeichenfolge CRLF
, welches durch '\r'
symbolisiert wird. Das LF
(Line Feed) bleibt erhalten. Die Datei ist im Unix-Formt. Das gleiche Kommando funktioniert, wenn wir eine Datei bekommen, die die Zeilenenden mit LFCR
markiert. – Welches System auch immer das tun mag.
Bei Mac-Dateien müssen wir '\r'
durch '\n'
ersetzen. Das Schema ist das gleiche.
Wir erkennen: Das Prinzip ist einfach.
eoln2unix – automatische Konvertierung von Zeilenenden
Die eigentliche Arbeit besteht darin, das Ganze in ein halbwegs stabiles Shell-Skript zu verpacken. Das folgende Skript zeigt eine mögliche Lösung. Es verwendet das getopt-Kommando, um mögliche Kommandozeilenoptionen zu verarbeiten. Das Kommando ist bitte nicht mit dem Builtin getopts der Bash zu verwechseln. Ich habe das Builtin absichtlich nicht verwendet, um das Skript möglichst einfach und kompatibel zu halten, auch wenn getopts über mehr Fähigkeiten verfügt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#!/bin/bash # # eoln2unix # # Das Skript konvertiert unterschiedliche Zeilenendungen in das Unix-Format. # Es muss ein Dateiname angegeben werden, weil das Skript die uebergebenen # Dateien mit dem file-Kommando ueberprueft. # # Im Falle eines Fehlers bricht das Skript ab! # # Exit-Codes: 0 - Erfolg # 1 - Hilfe wurde aufgerufen # 2 - Falscher Aufruf # 3 - Datei nicht gefunden # # Autor: Karsten Brodmann <kb@punkt-akademie.de> # Datum: 2017-02-24 ############################################################################ # Ausgabe der Gebrauchsanweisung function usage { echo "eoln2unix [-h] file(s)" echo " -h display help and exit" echo " file(s) one or more files to convert" echo "The eoln2unix utility copies the input to the standard output." } # Kommandozeilenargumente auswerten args=$(getopt h $*) if [ $? != 0 ] then usage exit 2 fi set -- $args for i do case "$i" in -h) usage exit 1 ;; --) shift; break;; esac done # wenn kein Dateiname angegeben wurde, ist das ein Fehler if [ $# -lt 1 ] then usage exit 2 fi # ok, alles als Dateinamen interpretieren for f in "$@" do if [ -f $f ] then # Zeilenendekennung extrahieren eoln_type="$(file "$f" | sed 's/.*with //' | sed 's/ .*//')" # je Nach Zeilenendekennung 'tr' aufrufen case "$eoln_type" in CR) # Mac: '\r' durch '\n' ersetzen cat "$f" | tr '\r' '\n' ;; CRLF|LFCR) # DOS|Windows,???: '\r' löschen cat "$f" | tr -d '\r' ;; *) # Eingabedatei ist bereits eine Unix-Datei - nichts zu tun ;; esac else echo "$f does not exist" 1>&2 exit 3 fi done exit 0 |
Dank der Kommentare kann ich mir weitere Erläuterungen sparen. Der größte Aufwand besteht darin, das Skript angenehm in der Nutzung zu gestalten. Man kann damit mehrere Dateien, unabhängig von ihrem Typ, ins Unix-Format wandeln. Weil das Skript nur auf der Standardausgabe ausgibt, kann das Resultat via Ausgabeumlenkung nur in einer gemeinsamen Datei gesammelt werden.
1 2 |
$ eoln2unix datei1 >ergebnis1 $ eoln2unix datei1 datei2 >ergebnis12 |
Verbesserungen
Nur auf die Standardausgabe ausgeben zu können, ist ersteinmal eine Einschränkung. Es steht jedoch jedem frei, eine weitere Option zu realisieren, die es ermöglicht, jede Datei direkt zu konvertieren, also die ursprüngliche Eingabedatei zu überschreiben. Alternativ kann man sich natürlich auch des Skriptes overwrite, welches im UNIX-Werkzeugkasten vorgestellt wird, bedienen.
Nehmen wir an, wir hätte mehrere DOS-/Windows-Dateien namens dos1 bis dos6, so könnten wir diese in einer Rutsche mittels
1 2 3 4 |
$ for i in dos? > do > overwrite $i eoln2unix $i > done |
konvertieren. Das Skript muss also nicht zwingend erweitert werden. Es ist auch in dieser einfachen Form schon sehr nützlich und bequem zu verwenden!
Karsten Brodmann