Fundbüro
Wer die gesuchte Information im System nicht findet oder genötigt ist, aufzuräumen, greift in der Regel zu klassischen Hilfsmitteln wie find . Ein Unix-Werkzeug, das aber noch einiges mehr kann, vor allem, wenn es aus der GNU-Ecke stammt.
- Michael Riepe
Das Programm find durchsucht Verzeichnisbäume nach Einträgen mit bestimmten Eigenschaften. Neben dem Namen kann der Benutzer nahezu alle in der Inode gespeicherten Dateiattribute als Suchkriterien verwenden: Zeitstempel, Dateilänge, Besitzer und Zugriffsrechte, Dateityp oder Anzahl der vorhandenen Links.
Systemverwalter setzen find gern für regelmäßig anfallende Aufräumarbeiten ein, zum Beispiel für das Löschen alter Core-Dumps oder nach Programmabstürzen liegen gebliebener temporärer Dateien. Aber selbst für Otto Normalbenutzer kann find nützlich sein. Der Brief an Tante Ruth zum Geburtstag im letzten Jahr muss noch irgendwo auf der Platte liegen; ein Befehl wie find $HOME -name '*Ruth*' -print verrät, wo - vorausgesetzt, der Verfasser kann sich an einen Teil des Dateinamens - in diesem Fall den Vornamen der geliebten Tante - oder ein anderes Detail erinnern.
Hat der Benutzer sein Quota überschritten und muss Platz schaffen, ist find gern behilflich. Die Zeile find $HOME -type f -size +200 -print findet alle Dateien, die größer sind als 100 KByte, und gibt ihre Namen aus. Will der Benutzer die Dateien im gleichen Arbeitsgang komprimieren lassen, kann er -print zum Beispiel durch -exec gzip -9 {} \; ersetzen. Das Suchwerkzeug ersetzt den Platzhalter {} durch den jeweiligen Dateinamen und führt dann für jede gefundene Datei den Befehl gzip -9 aus. Das Semikolon zeigt find an, wo der gzip-Befehl endet; da die Shell selbst ein Semikolon als Ende des find-Befehls interpretieren würde, muss der Benutzer einen Backslash vor das Zeichen setzen oder es in einfache oder doppelte Anführungszeichen einschließen.
Alternativen zur -exec-Option
Die Verwendung von -exec hat einen Nachteil: find muss für jede gefundene Datei einen neuen Prozess erzeugen, um das Programm gzip zu laden und zu starten. Da es wie die meisten Unix-Werkzeuge beliebig viele Argumente akzeptiert und sie nacheinander abarbeitet, wäre es effektiver, mehrere Namen auf einmal zu übergeben. Die Shell bietet dafür den Mechanismus der ‘Befehlssubstitution’: sie führt einen in Backquotes (gemeint ist der ‘umgekehrte’ Apostroph ‘`’) eingeschlossenen Befehl aus und fügt seine Ausgabe anstelle des Befehls in die Kommandozeile ein. Das Beispiel sähe so aus: gzip -9 `find $HOME -type f -size +200 -print`. Der Find-Befehl gibt die Dateinamen aus, die Shell liest sie und setzt sie in die Kommandozeile ein.
Eine solche, auf den ersten Blick elegante Lösung hat jedoch ein paar unerwartete Haken. Zum einen kann die Liste der Dateinamen überlaufen, zum anderen dürfen die Dateinamen keine Leerzeichen, Tabulatoren oder Zeilenumbrüche enthalten. Den Dateinamen Brief an Tante Ruth.txt zerlegt die Shell in seine Bestandteile und reicht diese einzeln an gzip weiter, woraufhin dies versucht, die vier Dateien Brief, an, Tante und Ruth.txt zu komprimieren, die wahrscheinlich gar nicht existieren. Der Schaden könnte sogar größer ausfallen: etwa, wenn der Befehl nicht gzip -9 lautet, sondern rm -f, und eins der Namensfragmente gleichzeitig der Name einer existierenden Datei ist.
Das Programm xargs stellt eine Alternative zur Befehlssubstitution dar. Es liest Dateinamen von der Standardeingabe und ruft einen in der Kommandozeile übergebenen Befehl mit so vielen Argumenten wie möglich auf - droht die Argumentliste überzulaufen, startet xargs den Befehl ein weiteres Mal, um die übrigen Argumente abzuarbeiten. Es zerlegt aber ebenfalls alle Dateinamen, die ‘white space’ enthalten; deswegen ist auch die Alternative find $HOME -type f -size +200 -print | xargs gzip -9 mit Vorsicht zu genießen.
Im günstigsten Fall funktioniert ein Befehl wie der obige einfach nicht - schlimmstenfalls kann er die Sicherheit oder die Funktionsfähigkeit des Systems beeinträchtigen. Listing 1 zeigt, wie ein bösartiger Benutzer eine hinterhältige Falle für unvorsichtige Systemadministratoren aufstellen kann. Es handelt sich um ein klassisches Trojanisches Pferd - an sich ist es völlig harmlos, aber wenn der Superuser beim Aufräumen eine Woche später einen Befehl wie find /tmp -type f -mtime +5 -print | xargs rm -f ausführt - oder ausführen lässt -, um alle temporären Dateien zu löschen, die älter sind als fünf Tage, entfernt rm nicht etwa die Datei mit dem Namen "/tmp/boese /etc/passwd", sondern die Passwortdatei des Systems - mit -exec wäre das nicht passiert.
Die GNU-Versionen von find und xargs - beide Programme sind im Paket ‘findutils’ enthalten - bieten eine sichere Lösung an. Das Argument -print0 veranlasst GNU-find, den Dateinamen nicht wie üblich mit einem Linefeed-Zeichen, sondern mit einem Null-Byte abzuschließen. Dies ist das einzige Zeichen, das in einem Dateinamen nicht vorkommen kann - eine Folge der Konvention für die Parameterübergabe von Unix und C. Es eignet sich deshalb ideal als Trennzeichen.
Mit den komplementären Optionen -0 oder --null fasst xargs nur Null-Bytes als Trennzeichen auf. Der Befehl find /tmp -type f -mtime +5 -print0 | xargs -0 rm -f ist genauso sicher wie find /tmp -type f -mtime +5 -exec rm -f {}, geht nur etwas schonender mit den Betriebsmitteln um, wenn viele Dateien zu löschen sind. Superuser, die jetzt den Drang verspüren, ihr ganzes System nach unsicheren find-Befehlen abzusuchen, könnten dazu die Zeile find / -type f -print0 | xargs -0 grep find benutzen.
Viele Versionen von find kennen nur zwei Ausgabeformate: -print liefert nur den Namen (mitsamt dem kompletten Pfad), -ls liefert zusätzlich die wichtigsten Dateiattribute, ähnlich wie es der Befehl ls -dils tut. GNU-find unterstützt mit der Option -printf ein zusätzliches, benutzerdefinierbares Format. Diese Option erwartet als Argument einen Formatstring und funktioniert ähnlich wie die gleichnamige C-Funktion - nur dass die Platzhalter im Formatstring andere Bedeutungen haben. So stehen %p und %f für den Dateinamen mit und ohne den vollständigen Suchpfad, %s für die Länge der Datei (in Byte), %u für den Namen des Besitzers und %t für den Zeitpunkt der letzten Änderung. Anstatt -print oder -ls zu benutzen und die Ausgabe von find mit Tools wie sed oder awk nachzubearbeiten, kann der Benutzer sich mit -printf die Daten gleich im gewünschten Format ausgeben lassen.
Normalerweise schreibt find auf die Standardausgabe. Will der Benutzer das Ergebnis speichern, muss er die Ausgabe in eine Datei umleiten. GNU-find erlaubt es, direkt in eine oder sogar mehrere Dateien zu schreiben. Die Optionen -fls, -fprint, -fprint0 und -fprintf arbeiten wie ihre Gegenstücke ohne das vorangestellte f, erwarten aber als zusätzliches erstes Argument den Namen der Datei, in die find schreiben soll. Das erlaubt Konstruktionen wie die folgende:
find / -type f -fprint Dateien \
-o -type d -fprint Verzeichnisse \
-o -type l -fprint Symlinks \
-o -print
Mit dieser Sequenz schreibt die GNU-Variante die Namen aller Dateien, Verzeichnisse samt symbolischer Links in verschiedene Ausgabedateien und gibt die Namen aller übrigen Objekte - Gerätedateien, Named Pipes oder Sockets - auf dem Terminal aus. Natürlich ließe sich das mit mehreren einzelnen Befehlen realisieren, aber diese Lösung ist schneller, weil sie den Verzeichnisbaum nur ein einziges Mal durchsuchen muss. Schließen sich die Suchkriterien nicht wie hier gegenseitig aus, sollte der Anwender statt der Oder-Verknüpfung <I>-o<I> eine weitere GNU-Spezialität einsetzen: den Komma-Operator ",", der wie sein Gegenstück in der Sprache C alle Suchausdrücke von links nach rechts abarbeitet.
Sachen zum Ausprobieren
Stark erweitert hat die GNU-Gemeinde die Such- beziehungsweise Vergleichsoptionen. Während das klassische find die Zeitstempel einer Datei mit den Optionen -atime, -ctime und -mtime nur grob in 24-Stunden-Intervalle einteilen kann, erlaubt die GNU-Version dank der neuen Optionen -amin, -cmin und -mmin minutengenaues Timing.
Als nützlich erweisen sich zudem -uid und -gid, die nach numerischen Benutzer- oder Gruppen-Ids fahnden. Zwar gibt es bei allen Versionen von find die Optionen -user und -group, die ebenso numerische Argumente verstehen. Sie haben aber den Nachteil, dass der Benutzer nur einen einzigen Wert angeben kann. Dagegen unterstützen -uid und -gid genau wie -atime oder -nlinks die Angabe einer Größer/Kleiner-Relation: ein Pluszeichen vor dem Argument bedeutet ‘größer als’, ein Minuszeichen ‘kleiner als’. Alle Dateien mit Benutzer-Ids zwischen 100 und 200 fördert der einfache Ausdruck -uid +100 -uid -200 zu Tage.
Ins Grübeln kommt man, wenn einem zwar das Bruchstück eines Dateinamens einfällt, aber nicht die genaue Schreibweise. Lästig ist mitunter außerdem, dass die Optionen -name und -path Groß- und Kleinschreibung unterscheiden - wer will schon alle denkbaren Schreibweisen explizit aufzählen? Die GNU-Erweiterungen -iname und -ipath vereinfachen das: sie ignorieren Groß- und Kleinschreibung.
Manchmal hat der Anwender als Suchbegriff nur einen ‘regulären Ausdruck’ zur Verfügung, wie ihn das Programm grep verwendet. Kann er diesen Ausdruck nicht so umwandeln, dass find ihn versteht, hilft es, sich von find alle Namen ausgeben zu lassen und diese Liste anschließend mit grep zu filtern. Mit den Optionen -regex und -iregex erübrigt sich dieser Umweg bei GNU-find.
Einige Operationen sind mit find schwer oder gar nicht zu realisieren. Wie identifiziert man zum Beispiel ein leeres Verzeichnis? Es ist länger als 0 Byte, weil es immer noch die Einträge ‘.’ und ‘..’ enthält, die tatsächliche Länge variiert aber je nach Betriebssystem und von Datei- zu Dateisystem. Zwar hat das GNU-Werkzeug mit der Option -empty die passende Antwort, aber der Benutzer in anderen Fällen nicht so viel Glück. Der universelle Lösungsansatz für ‘schwierige’ Fälle besteht darin, ein externes Testprogramm zu schreiben und dieses über -exec aufzurufen: find / -exec testprogramm {} \; -print. Gibt das Testprogramm den Wert ‘0’ an find zurück, ist der Ausdruck -exec testprogramm {} \; wahr und find gibt den Dateinamen aus. Hat es einen anderen Rückgabewert, setzt find die Suche beim nächsten Dateinamen fort.
Eine Liste aller Optionen der Programme find und xargs ist in den dazugehörigen Manpages zu finden. Die Texinfo-Dokumentation zum Paket ‘findutils’ ist etwas ausführlicher gehalten; jedoch nicht wie gewohnt unter dem Paketnamen, sondern unter ‘find’ zu finden. Einer der Befehle info find, info -n '(find)' oder info -f find sollte auf jeden Fall zum Ziel führen.
Listing 1
Lücke: Das Leerzeichen hinter /tmp/böse ist der Übeltäter, mit dem /etc angreifbar wird.
$ mkdir '/tmp/boese '
$ mkdir '/tmp/boese /etc'
$ touch '/tmp/boese /etc/passwd'
$ ls -ld `f find /tmp/boese* -print`
(rh)