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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cat data.csv | cut -d',' -f1 | sort | uniq -c
cat data.csv | cut -d',' -f1 | sort | uniq -c
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
awk -F, 'BEGIN {OFS=","} {$3=($2==12?100:$3)} 1' data.csv > temp.csv && mv temp.csv data.csv
awk -F, 'BEGIN {OFS=","} {$3=($2==12?100:$3)} 1' data.csv > temp.csv && mv temp.csv data.csv
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
sponge-Werkzeug aus den moreutils könnte uns das Leben hier leichter machen.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
awk -F, 'BEGIN {OFS=","} {if($1 ~ /John/) $2=23} 1' | sponge data.csv
awk -F, 'BEGIN {OFS=","} {if($1 ~ /John/) $2=23} 1' | sponge data.csv
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.“

Goodies von Mayflower

Keine Sorge – Hilfe ist nah! Melde Dich unverbindlich bei uns und wir schauen uns gemeinsam an, ob und wie wir Dich unterstützen können.

sponge sammelt den Input und schreibt dann in die Datei.

Eine Tasse tee?

Pipes lassen sich beliebig verlängern … und abzweigen.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -s http://json-api.example.com/user/21.json | tee debug.txt | jq .foo
curl -s http://json-api.example.com/user/21.json | tee debug.txt | jq .foo
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
top -n 1 | tee

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cat data.csv | pee 'wc -l' 'sort | uniq -c'
cat data.csv | pee 'wc -l' 'sort | uniq -c'
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?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cat access.log | cut -d ' ' -f 1 | sort | uniq -c | sort -nr | head
cat access.log | cut -d ' ' -f 1 | sort | uniq -c | sort -nr | head
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
grep -rl 'ftpserver.mayflower.de' ./ | xargs sed -i 's/ftpserver.mayflower.de/mondbasis.mayflower.de/g'
grep -rl 'ftpserver.mayflower.de' ./ | xargs sed -i 's/ftpserver.mayflower.de/mondbasis.mayflower.de/g'
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
find . -name '*.png' | xargs -I {} mogrify -format jpg {}
find . -name '*.png' | xargs -I {} mogrify -format jpg {}
find . -name '*.png' | xargs -I {} mogrify -format jpg {}

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
tail -f /var/log/syslog | grep -i error
tail -f /var/log/syslog | grep -i error
tail -f /var/log/syslog | grep -i error

Welche Prozesse fressen gerade den meisten Speicher?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
ps aux | sort -nrk 4 | head
ps aux | sort -nrk 4 | head
ps aux | sort -nrk 4 | head

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
history | cut -d ' ' -f 3 | sort | uniq -c | sort -nr | head
history | cut -d ' ' -f 3 | sort | uniq -c | sort -nr | head
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
find . -name '*.backup' -print0 | xargs -0 rm
find . -name '*.backup' -print0 | xargs -0 rm
find . -name '*.backup' -print0 | xargs -0 rm

Finde alle Backup-Dateien in allen Unterverzeichnissen und lösche sie. Das

-print0
-print0 von find und das korrespondierende
-0
-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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -s 'https://blog.mayflower.de' | pup 'h2.wp-block-post-title a json{}' | jq '.[].href' | xargs -n 1 -P 4 curl -O
curl -s 'https://blog.mayflower.de' | pup 'h2.wp-block-post-title a json{}' | jq '.[].href' | xargs -n 1 -P 4 curl -O
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
-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
-I, was einen Substituierungsstring für den empfangenen Input definiert.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
find . -name "*.bak" -type f | xargs -I{} mv {} {}.old
find . -name "*.bak" -type f | xargs -I{} mv {} {}.old
find . -name "*.bak" -type f | xargs -I{} mv {} {}.old

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
find . -name '*.backup' -print0 | xargs -p -0 rm
find . -name '*.backup' -print0 | xargs -p -0 rm
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
xargs --arg-file=dateiliste.txt -I{} cp {} /ziel/
xargs --arg-file=dateiliste.txt -I{} cp {} /ziel/
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
diff <(ls ordner/) < (ls backup/)
diff <(ls ordner/) < (ls backup/)
diff <(ls ordner/) < (ls backup/)

Vergleiche den Inhalt zweier Ordner.

Nicht so schmerzhaft, oder? Sehr gut kombinierbar mit dem Tool

paste
paste.
paste
paste kombiniert zwei Dateien, Zeile für Zeile.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ cat datei1.txt
Apfel
Banane
Kirsche
$ cat datei2.txt
1
2
3
$ paste -d, datei1.txt datei2.txt
Apfel, 1
Banane, 2
Kirsche, 3
$ cat datei1.txt Apfel Banane Kirsche $ cat datei2.txt 1 2 3 $ paste -d, datei1.txt datei2.txt Apfel, 1 Banane, 2 Kirsche, 3
$ 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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
paste <(seq $(wc -l < datei.txt)) datei.txt
paste <(seq $(wc -l < datei.txt)) datei.txt
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?

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
comm <(cat development.log | grep -i "error" | sort) <(cat production.log | grep -i "error" | sort)
comm <(cat development.log | grep -i "error" | sort) <(cat production.log | grep -i "error" | sort)
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
paste besser
join
join nehmen.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
join -t, -1 3 -2 2 <(sort -t, -k3b,3 mitarbeiter.csv) <(sort -t, -k2b,2 aws-accounts.csv)
join -t, -1 3 -2 2 <(sort -t, -k3b,3 mitarbeiter.csv) <(sort -t, -k2b,2 aws-accounts.csv)
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.

Goodies von Mayflower

Keine Sorge – Hilfe ist nah! Melde Dich unverbindlich bei uns und wir schauen uns gemeinsam an, ob und wie wir Dich unterstützen können.

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.