CLI-Adventskalender, Tag 7: Pipe-Tools

CLI-Adventskalender: Pipe-Tools

Avatar von Eric

Hinter dem siebten Türchen unseres CLI-Adventskalenders verbergen sich Pipe-Tools. Seid gespannt!

Was haben System-Administratoren und Super Mario gemeinsam? Beide sparen sich sehr viel Zeit mit Pipes. Der Witz funktioniert besser auf Englisch, aber die folgenden Befehle funktionieren auf fast jedem System.

Pipe-Tools

In Unix-ähnlichen Betriebssystemen kann der Output jedes Befehls Input für den nächsten sein.

cat data.csv | cut -d',' -f1 | sort | uniq -c

Wähle die erste Spalte einer CSV-Datei aus, sortiere die Felder und gebe jeden einzigartigen Wert nur einmal aus, mit der Anzahl des Vorkommens davor. Mit genügend Shell-Werkzeugen wird jede Textdatei zu einer Datenbank.

Hier ist das Äquivalent eines Update-Statements:

awk -F, 'BEGIN {OFS=","} {$3=($2==12?100:$3)} 1' data.csv > temp.csv && mv temp.csv data.csv

„Setze die dritte Spalte auf 100, falls die zweite Spalte 12 ist.“

Mit > leiten wir den Output direkt in eine Datei, die notfalls angelegt wird, falls sie nicht existiert und KOMPLETT ÜBERSCHRIEBEN wird, falls sie existert (>> hängt den Input stattdessen hinten an die Datei). In diesem Beispiel schreiben wir den Output erst einmal in eine temporäre Datei, weil wir sonst mit hoher Wahrscheinlichkeit den Inhalt der alten Datei kaputt machen, bevor wir alles verarbeitet haben. Das sponge-Werkzeug aus den moreutils könnte uns das Leben hier leichter machen.

awk -F, 'BEGIN {OFS=","} {if($1 ~ /John/) $2=23} 1' | sponge data.csv

„Setze die Spalte 2 auf den Wert 23, falls der Wert der ersten Spalte John enthält.“

sponge sammelt den Input und schreibt dann in die Datei.

Eine Tasse tee?

Pipes lassen sich beliebig verlängern … und abzweigen.

curl -s http://json-api.example.com/user/21.json | tee debug.txt | jq .foo

tee ist ein T-förmiges Rohr; es lässt den Stream in zwei Richtungen abzweigen. Es ist sehr nützlich zum Debuggen derartiger Kommandos. Es ist auch nützlich zum Mitschneiden von Output, den man direkt sehen möchte: top -n 1 | tee

Wer gleich zwei Pipes auf einmal haben möchte, dem hilft das grundschulhumorfreundlich benanntete Tool aus den moreutils.

cat data.csv | pee 'wc -l' 'sort | uniq -c'

Wie viele Einträge gibt es insgesamt und welche kommen wie oft vor? Nützlich für Leute, die wirklich alles in einer Pipe haben wollen.

Komponisten willkommen

Wenn sich alle Befehle kombinieren lassen, kann man mit ein bißchen Kreativität viele nützliche Dinge bauen. Zum Beispiel: Welche IP-Adressen greifen am häufigsten auf dieses System zu?

cat access.log | cut -d ' ' -f 1 | sort | uniq -c | sort -nr | head

Suche mir alle Konfigurationsdateien in denen noch der alte Hostname steht und ersetze ihn durch den Neuen.

grep -rl 'ftpserver.mayflower.de' ./ | xargs sed -i 's/ftpserver.mayflower.de/mondbasis.mayflower.de/g'

Wandle alle .png-Dateien in allen Unterverzeichnissen in .jpg um.

find . -name '*.png' | xargs -I {} mogrify -format jpg {}

Gib mir eine Liveansicht aller Fehler, die in meinem Log auftauchen.

tail -f /var/log/syslog | grep -i error

Welche Prozesse fressen gerade den meisten Speicher?

ps aux | sort -nrk 4 | head

… und welche Kommandozeilenbefehle gebe ich am häufigsten ein?

history | cut -d ' ' -f 3 | sort | uniq -c | sort -nr | head

Die Kombinationen sind endlos. Ganze Bücher können und wurden mit nützlichen Shell-Einzeilern gefüllt.

xargs – der Text-Exekutor

Manche Befehle nehmen ihren Input direkt von STDIN (dem Standard-Inputkanal), in allen anderen Fällen hilft xargs.

find . -name '*.backup' -print0 | xargs -0 rm

Finde alle Backup-Dateien in allen Unterverzeichnissen und lösche sie. Das -print0 von find und das korrespondierende -0 von xargs bedeutet, dass wir hier mit Null-Byte-terminierten Strings arbeiten. Sehr nützlich, wenn man Leerzeichen in Dateinamen abfangen will.

xargs bringt auch ein paar nette Features mit. Zum Beispiel Parallelisierung.

curl -s 'https://blog.mayflower.de' | pup 'h2.wp-block-post-title a json{}' | jq '.[].href' | xargs -n 1 -P 4 curl -O

Hier beschränken wir uns auf vier Downloads gleichzeitig, mit -P 0 würden wir so viele parallele Prozesse aufmachen wie unser System erlaubt. Das wäre … hüstel …  etwas unhöflich.

Häufig genutzt wird auch -I, was einen Substituierungsstring für den empfangenen Input definiert.

find . -name "*.bak" -type f | xargs -I{} mv {} {}.old

Wem blinde Ausführung zu heikel ist, dem bietet xargs auch einen Prompt.

find . -name '*.backup' -print0 | xargs -p -0 rm

… und wer richtig auf Nummer sicher gehen will, kann den Input auch aus einer spezifizierten Datei lesen.

xargs --arg-file=dateiliste.txt -I{} cp {} /ziel/

Prozesssubstituierung ist nicht so gruselig wie es klingt

Prozesssubstituierung bedeutet, dass der Output eines Kommandos behandelt wird wie der Inhalt einer Datei. Warum ist das nützlich? Was erzähle ich hier? Ich fange ganz langsam an.

diff <(ls ordner/) < (ls backup/)

Vergleiche den Inhalt zweier Ordner.

Nicht so schmerzhaft, oder? Sehr gut kombinierbar mit dem Tool paste. paste kombiniert zwei Dateien, Zeile für Zeile.

$ cat datei1.txt
Apfel
Banane
Kirsche

$ cat datei2.txt
1
2
3

$ paste -d, datei1.txt datei2.txt
Apfel, 1
Banane, 2
Kirsche, 3

Als praktisches Beispiel können wir eine Datei mit Zeilennummern versehen.

paste <(seq $(wc -l < datei.txt)) datei.txt

Der Delimiter ist hier frei wählbar. Man kann also den Output direkt in einen Taschenrechner füttern. Wie hoch ist die Differenz zwischen dem höchsten und dem niedrigsten Wert für ein Feld in einer CSV-Datei?

paste -d- <(cat mitarbeiter.csv | grep -v Gehalt | cut -f 2 -d, | sort -n | tail -1) <(cat mitarbeiter.csv | grep -v Gehalt | cut -f 2 -d, | sort -n | head -1) | bc -l

Ein anderes Tool, dem man häufig in diesem Kontext begegnet, ist comm, das sortierte Dateien braucht.

comm <(cat development.log | grep -i "error" | sort) <(cat production.log | grep -i "error" | sort)

Das hier noch als Schmankerl zum Schluss: Wenn man einen JOIN über zwei CSV-Dateien machen möchte, sollte man statt paste besser join nehmen.

join -t, -1 3 -2 2 <(sort -t, -k3b,3 mitarbeiter.csv) <(sort -t, -k2b,2 aws-accounts.csv)
Shell-Weisheit des Tages
Genau eine Sache richtig machen!

Die Unix-Philosophie – festgehalten von Doug McIlroy im Bell System Technical Journal im Jahre 1978 – beginnt mit zwei einfachen Grundsätzen: „Make each program do one thing well“ und „Expect the output of every program to become the input to another, as yet unknown, program“. Die Philosophie hat sich über jede Iteration von Unix, dann über Linux hinweg bis in die Gegenwart gehalten. Werkzeuge wie sed, grep, awk, cat, find, echo, sort, cut, head, tail, diff sind älter als MS-DOS und werden heute noch (mit kleinen Verbesserungen) genutzt.

Meine Punkte zu diesem Thema sind zwei. Erstens: Es lohnt sich, diese Befehle zu lernen. Nicht nur was sie tun, sondern wirklich ihre Manpages zu lesen, im Internet nach kreativen Verwendungen herumzusuchen und ihre Anwendung zu üben bis sie im Muskelgedächtnis ist. Ein Tool, dass dieser Philosophie folgt, wird für seinen genannten Zweck immer nützlich sein. Es wird immer das beste Werkzeug sein. Es muss keine Kompromisse machen und erforscht thematisch jeden Aspekt seiner Funktion.

Zweitens: Wenn wir unsere eigenen Werkzeuge bauen, sollten wir sie ins Ökosystem einfügen. Sie werden dadurch nützlicher (mehr Tools, die mit ihnen interagieren), universeller (läuft auf jedem Computer, der Text kann) und haben so einen langfristigen Marktvorteil, sollten wir uns entscheiden, sie zu publizieren.

Es gibt gute Gründe, warum in Zeiten von knallharten GPUs Cloud-Anbieter immer noch Kommandozeilentools herausbringen und pflegen. Tools, die auf Mainframes entwickelt wurden, helfen mir heute, Serverless Functions zu deployen und EC2-Instanzen zu konfigurieren.

Avatar von Eric

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.