Viele Kommandozeilentools sind überreich mit Optionen gesegnet – die meisten fristen ein Schattendasein. Um effiziente und robuste Skripte zu verfassen, lohnt es, einige ins Licht zu zerren.
Das erste Beispiel mit Verbesserungspotenzial erfordert keine weite Reise: Im Perl-Snapshot “Schicker Umzug” [1] verwendet Michael Schilli folgenden Ausdruck, der so oder ähnlich in vielen Skripten vorkommt:
find . -type f -exec grep Suchstring {}/dev/null \;
Der Nachteil des Konstrukts: Für jede gefundene Datei erzeugt das Betriebssystem per Fork einen neuen Prozess. Jeder eingeloggte User, der auf diese Weise ein ganzes Dateisystem durchsucht, flutet den Host mit sehr vielen, nur kurz laufenden Prozessen.
Dabei zeigt ein Blick in die Manpage von Grep, dass der User für die Verzeichnisrekursion auch ohne »find« auskommt: Mit einer der Optionen »-r« , »-R« , »–recursive« oder »–directories=recurse« durchsucht Grep die als Argument übergebenen Verzeichnisse rekursiv. In diesem Fall entsteht für alle Dateien nur ein Prozess:
grep -r Suchstring .
Wer nicht alle Dateien durchsuchen will, verwendet die Grep-Optionen »–include=« und »–exclude=« . Beide erwarten eine Bash-artige Wildcard-Spezifikation wie »*.xml« , also keinen regulären Ausdruck. Weitere Möglichkeiten, Dateien auszuschließen, sind »–exclude-dir=« , das ganze Verzeichnisse links liegen lässt, und die Option »–exclude-from=« , die eine Datei mit beliebig vielen Ausdrücken entgegennimmt.
Null Ahnung
Mike Schillis Find-Zeile bedient sich eines Tricks, damit sie neben den gefundenen Zeilen auch den Dateinamen ausgibt – Grep unterdrückt nämlich den Dateinamen, wenn es nur eine Datei übergeben bekommt. Mikes »/dev/null« setzt Grep nun immer eine zweite Datei vor, sodass es zusätzlich den Dateinamen liefert. Doch der Trick ist gar nicht nötig, das Gleiche erreicht die Grep-Option »-H« (oder »–with-filename« ). Neuere Versionen von Find unterstützen auch diese Syntax:
find . -type f -exec grep -H Suchstring {} +
Hier übergibt Find mehrere Dateifunde auf einmal an das per »-exec« definierte Kommando, analog zu dem weiter unten beschriebenen Xargs.
Beschränkte Kommandozeile
Mit der Bash führen oft mehrere Wege zum Ziel, so auch hier. Der ebenfalls oft zu sehende Ausdruck
grep Suchstring `find . -type f`
startet zwar keine Unmenge Prozesse. Allerdings weist das Verfahren – außer den veralteten Backticks – zwei Nachteile auf: Zum einen scheitert der Befehl, wenn Find auf Dateien mit Leerzeichen trifft. Zum anderen bereitet es der Bash Probleme, wenn Find sehr viele Dateien zurückliefert, denn die Länge der Kommandozeile ist begrenzt.
Die selten genutzte »-print0« -Option bringt die Lösung: Falls der Benutzer nicht aus einem anderen Grund eine Ausgabeoption von Find wie »-print0« , »-printf« oder andere nutzt, verwendet Find implizit »-print« , gibt also einfach jede gefundene Datei in einer Zeile aus. Die »-print0« -Option trennt dagegen die Dateien nicht per Newline, sondern per Null-Zeichen. Das Ganze ergibt Sinn im Zusammenspiel mit »xargs« . Die Befehlskette lautet:
find . -type f -print0 | xargs -0 grep -HSuchstring
Die Option “minus Null” ist das Pendant zu »-print0« und liest die einzelnen Argumente durch Null-Zeichen getrennt. Xargs packt so viele Argumente wie möglich in eine Kommandozeile, ruft also Grep nicht öfter als nötig auf.
Xargs besitzt weitere sehr nützliche Optionen: Mit »-P n« startet Xargs n Befehle parallel. Wenn der Befehl nur eine begrenzte Zahl Argumente auf einmal verarbeiten kann, dann limitiert die Option »-L Zeilen« die verarbeiteten Zeilen.
Sehr findig
Find hat eine Reihe von Besonderheiten, so sortiert es – anders als »ls« – die gefundenen Dateien nicht. Wer nur nach Dateinamen zu sortieren braucht, leitet die Ausgabe von Find per Pipe an »sort« . Will der Anwender nach Zeit sortieren, kommt mit »-printf« eine weitere nützliche Find-Option ins Spiel:
find . -type f -printf "%T@ %p\n" | sort -n| cut -d" " -f2
Printf ist sehr mächtig, es liefert verschiedenste Attribute der gefundenen Dateien zurück, etwa nur den Verzeichnisnamen. Das Beispiel oben gibt deshalb neben dem Dateinamen (»%p« ) zuerst den Zeitpunkt der letzten Datei-Änderung in Sekunden seit 1970 (»%T@« ) aus. Das »-n« sorgt beim Sort für die numerische statt der alphabetischen Reihenfolge.
In Archivierungs- und Cleanup-Skripten verarbeiten Admins “alte” Dateien. Hier hilft Find schon länger mit den verschiedenen »-time« -Optionen, etwa »-mtime +n« (modifiziert in den letzten n*24 Stunden) oder »-mmin +n« (analog, aber in Minuten). Nützlich in diesem Zusammenhang ist die neuere Option »-daystart« , mit der Find die Zeit ab Mitternacht rechnet, was eigene Rechnerei spart. Noch einfacher geht es mit einer der »-newer« -Optionen, etwa so:
find . -type f -not -newermt "yesterday"
Statt »yesterday« kann jeder Zeitausdruck stehen, den auch »date« versteht.
Die löchrigen Dateien
Linux ist in der Lage, große, fast leere Dateien sehr effizient zu speichern. Solche so genannten Sparse-Files belegen auf der Platte weniger Platz als eigentlich ihrer Größe entspricht. Ärgerlich ist, dass kaum ein Skript darauf Rücksicht nimmt. Insbesondere Backupverfahren, die auf »tar« beruhen, erzeugen dadurch unnötige große Sicherungen. Ein Beispiel dafür liefert der Artikel [2].
Übergibt der Anwender jedoch die Option »-S« oder »–sparse« , behandelt Tar solche Dateien effizient. Der normale Kopierbefehl »cp« kennt auch eine »–sparse« -Option, die aber ein Argument erwartet. Der Default »–sparse=auto« verwendet eine heuristische Methode, um Sparse-Files zu erkennen. Alternativ lässt sich »–sparse=always« oder »–sparse=never« angeben.
Lange Zeilen
Das letzte Beispiel wendet sich der Frage zu, wie man die maximale Breite, also die Länge der längsten Zeile in einer Datei, findet. Große Entwicklerteams arbeiten oft mit Vorgaben hinsichtlich der Zeilenlänge, Skripte könnten deren Einhaltung automatisch prüfen. Die erste Spur führt zu »wc« :
wc -L Datei
Die Option »-L« beziehungsweise »–max-line-length« ist wenig bekannt, vielleicht weil es sie für andere »wc« -Implementation, etwa unter AIX, nicht gibt. Auch ist sie nicht ohne Tücke:
echo -e "abc\txyz" | wc -L echo -e "abc\t\txyz" | wc -L
Der erste Aufruf liefert den Wert 11, der zweite 19. Die – je nach Erwartung – überraschenden Ergebnisse verursachen die Tabstopps, die »wc« zu acht Leerstellen expandiert. Fatal: Die Manpage hilft bei der Suche nach dem Effekt keinen Deut weiter (Abbildung 1), erst die Info-Seite zu »wc« erwähnt das Prinzip. Wer wirklich nur Zeichen zählen will, der greift daher zu:

Abbildung 1: Die Kürze der Manpage von »wc« mag die sprichwörtliche Würze geben, hält den Benutzer aber auch von wichtigen Details fern.
tr '\n' ' ' < Datei | wc -L
Und wer Tabs zwar interpretieren will, aber eben nicht an jeder achten Stelle, verwendet:
expand -t 2 Datei | wc -L
Ein Aufruf mit »-t 1« wäre gleichwertig mit der tr-Lösung.
Bash-Benutzer, die nicht nur an der maximalen Länge interessiert sind, sondern die längste(n) Zeile(n) auch ausgeben möchten, findet bei [3] verschiedene Implementationen.
Fazit und eine Warnung
Anwender, die interaktiv Befehle auf der Kommandozeile tippen, um einmalig eine Aufgabe zu erledigen, möchten sich nicht ausgiebig über ausgefuchste Optionen Gedanken manchen. In immer wieder ablaufenden Skripten sieht das anders aus, hier lohnt sich etwas Recherche – so wie es dieser Artikel vormacht.
Gut zu wissen: Die Maintainer der zentralen Tools aus den Core- und Textutils, die in fast keinem Bash-Skript fehlen, entwickeln diese stetig weiter. Deshalb ist nach jedem Systemupdate ein Blick in die Manpage ratsam. In portablen Skripten haben brandneue Erweiterungen sowieso nichts zu suchen. (jk)
Infos
- Michael Schilli, “Schicker Umzug”: Linux-Magazin 09/11, S. 104
- Andrea Müller, “Linux zieht um”: ct-Magazin 07/11, S. 178
- Längste Zeile einer Datei: http://unix.stackexchange.com/questions/24509/how-to-print-the-longest-line-in-a-file





