Weil heute der 8. Dezember ist, geht es um find. Weil ihr fragt euch ja bestimmt auch, wo all die Zeit hingekommen ist und wieso das mit den Weihnachtsgeschenken schon langsam eng wird …
Man hat ja so selten Zeit zum aufräumen. Auch wenn ich – durch nachdrückliche Motivation seitens meiner Frau – inzwischen nicht mehr jedes Zimmer das ich bewohne in einen Drachenhort aus Müll verwandle. Auch wenn meine Schubladen inzwischen Label haben und die Grenzscharmützel in der Besteckschublade nur noch alle paar Monate aufkommen, sind meine digitalen Daten eine andere Geschichte.
Ich meine hier nicht die Git-Repos, an denen wir arbeiten. Die zwingen mich zur Ordnung (oder verstecken meinen Müll in den Ramschschubladen von git stash
). Ich meine den Ramsch außen herum. Ich spreche von meinen Download-Ordner, von den Schlachtfeldern alter Mayday-Projekte, von den externen Festplatten auf denen mein digitales Leben der letzten zwanzig Jahre liebevoll gesichert ist.
Wer soll da noch irgendwas finden? In endlicher Zeit? Mit granularen Filtern?
find
Glücklicherweise gibt es ja Suchfunktionen und das Terminal ist da keine Ausnahme.
find /home/eric -name "passwords.kdbx"
find ist ein beeindruckendes Arbeitspferd. Es geht durch den kompletten Verzeichnisbaum und sucht alle vorkommenden Dateien, auf die die Filter zutreffen. Das heißt natürlich auf der anderen Seite, dass man es manchmal liebevoll von bestimmten Verzeichnissen wegsteuern möchte.
find -name 'node_modules' -prune -o -name 'ThingExplainer.js' -print
Filter lassen sich also aneinanderreihen, ohne dass man auf Pipes zurückgreifen muss. -prune
bedeutet hier „wirf das hier weg“. Wenn das passiert ist -prune
true und der zweite Teil (hinter dem -o
, Kurzform von -or
) wird nicht mehr ausgewertet.
Natürlich lässt sich das auch gleich mit einer fuzzysearch verbinden und direkt in das entsprechende Programm füttern.
cvlc "$(find ~/Downloads -name '*.mp3' | fzf)"
Löschen, sammeln oder weiterverarbeiten?
Wer mal seinen inneren Dalek rauslassen will und alle Dateien eines bestimmten Typs systematisch loswerden möchte, freut sich über diesen Befehl:
find git_repos/projekt -name "*.swp" -delete
find bringt außerdem ein eigenen Mechanismus mit, um Befehle auf jeder gefundenen Datei auszuführen.
find /tagebuch -name "*.txt" -exec tar -rvf archiv.tar {} \;
Schlüssel ist hier das -r
, dass alle Dateien in das bestehende Archiv hinzufügt.
Natürlich hält uns niemand davon ab, unseren alten Freund xargs zu bemühen.
find /tmp -name '*mayflower*' -type f -print0 | xargs -I{} -0 cp {} /home/eric/will_ich_behalten/
-type f
beschränkt uns hier auf Dateien. -print0
terminiert die Strings mit 0-Bytes anstelle von Zeilenumbrüchen. Das erlaubt uns Dateinamen mit Whitespace-Zeichen.
Granulare Filter
Okay, okay. Wir haben jetzt eine grobe Idee davon, was man damit machen kann. Welche Filter sind nun eigentlich möglich? So ziemlich alle Attribute, die eine Datei so hat. Alle Dateien größer als 50 Mb?
find /home/eric/Downloads -type f -size +50M
Alle Order die vor 2023 angelegt wurden? Ein wenig kniffliger, denn entweder nehmen wir -mtime
und parsen ein Datum in Tagen rein …
find /home/eric/Documents -type d -mtime +$(($(date +%s -d '2023-01-01') / 86400 - $(date +%s) / 86400))
… oder wir negieren -newermt
find /home/eric/Documents -type d ! -newermt '2023-01-01'
Ein „älter als ein Jahr“ lässt -mtime
wieder praktischer aussehen.
find /home/eric/Documents -mtime +365
Wir können auch anhand von Besitzern suchen …
find /tmp -user kevin
… oder Gruppenzugehörigkeit.
find /var/www -group www-data
Wir können auch nach gesetzten Berechtigungen suchen.
find /etc -perm 0777
Und ein besonderes Highlight ist das Finden von leeren Verzeichnissen und Dateien.
find /home/eric/repos -type d -empty
Engermaschige Netze
Alle diese Filter kann man beliebig kombinieren. Die logischen Operatoren verhalten sich so, wie man es erwartet.
-not
oder ! bindet am stärksten.-and
oder-a
bindet stärker als …-or
oder-o
Sobald ein Teil eines Oder-Blocks true ist, wird nichts weiter angewendet. Das ist wichtig, wenn man Blöcke mit -prune
(„Wirf das hier weg, geh hier nicht tiefer, nichts zu sehen, weitergehen …“) und -print
(„Das möchte ich in meinem Output haben.“) kennzeichnet. Ein erfolgreiches -prune
evaluiert zu true.
Das -and
ist in den meisten Fällen implizit, deswegen sieht man häufig nur -o
.
find /home/eric/repos/projekt (-name '.git' -o -name 'node_modules') -prune -o -type f -size +4M -newermt '2023-01-01' ! -name '*.jpg'
Alle Dateien im Projekt (außer in .git und node_modules), die größer als 4 Mb sind und seit Anfang diesen Jahres hinzugefügt wurden und keine jpg-Dateien sind.
Pipes rein und raus
Wir haben gesehen, dass man den Output von find leicht in Pipes wiederverwenden kann. Aber man kann auch eine Liste von Verzeichnissen als Startpunkte in find hineinpipen! Wichtig ist hier wieder, die Zeilenumbrüche in Nullbytes zu übersetzen.
cat verzeichnisliste.txt | tr '\n' '\0' | find -files0-from=- -type f
find
nistet sich also hervorragend in unseren Kommandozeilenwerkzeugkasten ein.
Developerzeit ist kostbar. Dinge zu automatisieren ist verführerisch, dauert aber immer länger als man denkt. Dennoch …
Wenn man merkt, dass man dieselben Befehle immer wieder ausführt, wenn man merkt, dass sich Muster wiederholen, ist es vielleicht an der Zeit, ein kleines Skript zu schreiben.
Diese Weisheiten fangen langsam an, aufeinander aufzubauen. Da „Shell für die Ewigkeit“ ist, lohnt es sich also immer wieder, einen Blick in die manpages zu werfen. Aus diesem Grund – und weil „Automatisierung […] fest integriert“ ist – lohnt es sich, auf Kommandozeilenwerkzeuge zurückzugreifen. Vor allem iterativ.
Das Skript aus dem ersten Blogpost dieser Serie (mit dem man seine Stunden buchen kann), lässt sich mit Sicherheit noch mehr parametrisieren und erweitern. Es könnte zum Beispiel gleich auf das Ticket buchen, auf dessen Branch wir unterwegs sind.
--- SCHNIPP --- # feature/TICKET-123 -> TICKET-123 # genauso für pr/TICKET-123, bugfix/TICKET-123, usw. ticket=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | awk -F'/' '{print $NF}') # awk splittet den String auf dem "/" und nimmt das letzte Feld. Mehr zu awk hinter einem anderen Adventstürchen :) if [ -n "$2" ] then ticket=$2 fi if [ -z "$ticket" ] || [ "$ticket" = "HEAD" ] then echo "Kein Git-Repo oder detached HEAD." echo "Bitte Ticket-Nummer angeben." exit 1 fi --- SCHNIPP ---
Wir wissen nie alles von vorne herein, selbst wenn es Arbeiten sind, die wir gefühlt hundertmal ausgeführt haben. Dieses „Bastler-Mindset“, über die eigene Arbeitsweise zu reflektieren, monotone Arbeiten nicht einfach hinzunehmen und immer wieder kleine Verbesserungen zu machen, ist das, was uns voranbringt. Das Leben ist geprägt von ständigem Lernen, Automatisierung ist nur die logische Fortführung desselbigen.
Schreibe einen Kommentar