Bash: Filterkommandos

Die Filterbefehle Grep, Awk, Xargs & Co sind unentbehrlich, um den Textoutput von Terminalbefehlen zielsicher zu filtern. Der Beitrag erklärt die wichtigsten Filter und die Prinzipien der Benutzung.

Grep: Der horizontale Zeilenfilter

Grep ist der häufigste Textfilter, um eine eventuell seitenlange Liste auf die relevante(n) Zeile(n) abzukürzen. Grep ist relativ eingängig, weil man einfach den Textstring angibt, den die benötigten Zeilen enthalten müssen (oder nicht enthalten dürfen).

Im einfachsten Fall geht es im interaktiven Terminal nur darum, die Ausgabe übersichtlich zu halten. So zeigt etwa der mount-Befehl auf Ubuntu-Systemen mit zahlreichen Snaps überwiegend Mountpunkte, die für den Benutzer irrelevant sind. Der Befehl

mount | grep "/dev/sd"

verkürzt auf die physischen Laufwerke. In manchen Fällen ist es sinnvoll, den Filter mit „-v“ umzudrehen, um nur die Zeilen zu erhalten, wo der String nicht vorkommt:

apt list --installed | grep -v "automatisch"

Dies zeigt alle installierten Software-Pakete, die manuell nachinstalliert wurden, also nicht zum „automatisch“ mitgelieferten Umfang gehören.

Für Scripts ist es oft erforderlich, genau eine einzige Ausgabezeile eines Standardbefehls auszuwerten. Der Suchstring muss dann entsprechend eindeutig ausfallen:

free -m | grep "Speicher:"

Die weitere Auswertung der verbleibenden Zeile kann dann das Werkzeug Awk übernehmen (siehe unten). Für Scripts ist ferner die Tatsache wichtig, dass Grep den Errorlevel „1“ zurückmeldet (abzufragen mit „$?“), wenn der Textstring nicht gefunden wird:

echo $file | grep -i ".7z"
if [ $? -eq 0 ] then …

Im Prinzip eignet sich Grep auch für die Textrecherche in vielen Dateien. Das Kommando

grep -r "Bunsenlabs" .

durchsucht ab dem aktuellen Verzeichnis rekursiv alle Dateien nach dem Suchstring. Da es sich aber um reine Textdateien handeln muss, wenn die Ausgabe lesbar sein soll, bleibt der Nutzwert von Grep als Desktopsuche begrenzt. Textprofis werden dafür eher zu Recoll oder Docfetcher greifen. Die Parameter „–after-context=“ und „–before-context=“, die auf Wunsch Zeilen nach und vor dem Suchtreffer ausgeben, zielen ebenfalls auf solche Textrecherche und seien hier zumindest erwähnt.

Hinweis: Die Varianten Egrep, Fgrep, Rgrep bleiben hier unberücksichtigt. Sie sind allesamt über Grep-Schalter zu erreichen.

Grep macht Datenfluten übersichtlich: Hier kontrolliert der String „deleting“, was ein rsync-Befehl bei tatsächlicher Ausführung löschen würde.

Sort: Zeilenreihenfolge umstellen

Sort ist ein Filter, der die Datenmenge insgesamt unverändert lässt (Ausnahme Schalter „-u“). Das Sortieren kann aber die Daten in völlig neue Zusammenhänge setzen, die besseren Überblick und schnelle Auswertung ermöglichen.

Sort sortiert die Ausgaben anderer Befehle alphabetisch (Standard) oder numerisch (Schalter „-n“), bei Bedarf auch nach der gewünschten Spalte (Schalter „-k“). Vor allem die Sortierung von Listen nach einer beliebigen Spalte bringt rückt Informationen zusammen, die aus den ursprünglichen Daten nie ersichtlich wären: Der Befehl tree -isa listet alle Dateien unterhalb des aktuellen Verzeichnisses mit Bytezahl auf. Mit

tree -isa |sort -k2 -n

wird die Liste numerisch („-n“) nach dieser Größenangabe sortiert, sodass die größten Dateien am Ende erscheinen. Sortiert werden muss – etwas irritierend – nach Spalte 2, weil die beginnende Klammer „[ “ als erste Spalte zählt.

Als weiteres Beispiel sei der Befehl ps –A angeführt, der alle laufenden Prozesse zeigt. Die Ausgabe ist standardmäßig nach der PID (Prozess-ID) sortiert, was bei der Suche nach einem bestimmten Programm unübersichtlich ist. Sort macht daraus mit

ps –A | sort –k4

eine alphabetische Liste:

Sort kann nicht nur nach einer bestimmten Spalte sortieren („-k“), sondern dies auch dort, wo ungewöhnliche Spalten- und Feldbegrenzer vorliegen (Schalter „-t“). Typisch ist etwa das Semikolon in CSV-Dateien:

cat Buchungen.csv | sort -t ";" -n -k7

Wenn Spalte 7 die Buchungsbeträge enthält, wird die komplette Tabelle numerisch nach diesen Werten sortiert.

Mit Schalter „-u“, der ausschließlich horizontal und zeilenbezogen arbeitet, filtert Sort bei der Ausgabe langer Listen oder Dateien doppelte, identische Zeilen weg:

cat .bash_history | sort -u

Wer diesen Räumdienst direkt in der Originaldatei übernehmen will, nutzt am besten Sponge:

cat .bash_history | sort -u | sponge

Dies erspart den lästigen Umweg über eine sekundäre, temporäre Datei.

Hinweis Uniq: „sort -u“ macht das kleine Hilfsprogramm Uniq weitgehend überflüssig. Der Mehrwert gegenüber „sort -u“ liegt allenfalls bei der Analyse, zumal Uniq grundsätzlich nur funktioniert, wenn ein Sort-Filter vorangestellt wird:

cat .bash_history | sort | uniq -c

Dies zeigt alle Zeilen mit der Anzahl eventueller Wiederholungen. Jeder Wert größer als „1“ bedeutet einen doppelten oder vielfachen Eintrag.

Sort schafft Ordnung: Wichtige Eigenschaften sind die Sortierung nach Spalten (-k) und der Schalter für numerische Werte (-n).

Awk: Der vertikale Spaltenfilter

Awk ist das umfangreichste Filterwerkzeug und zerlegt die Textausgabe von Terminalkommandos in Spalten und Felder. Damit können Sie Spalten einer Liste ausblenden oder umstellen oder in einer Ausgabezeile genau einen bestimmten, einzelnen Wert ermitteln und weiterverarbeiten. Awk kann außerdem vorgefundene Werte gleich mathematisch verrechnen und etwa Additionen oder Divisionen erledigen.

Awk ist ein selbständiges Programmiertool. Die folgenden Beispiele können diesen Umfang nicht annähernd wiedergeben und fokussieren sich auf wichtige Aufgaben im interaktiven Einsatz oder für einfache Bash-Alias oder Funktionen. Die Arbeitsweise von Awk zeigt ein ganz simples Beispiel:

echo Anna Sepp Hans Berta | awk '{print $1 $4 $3 $2}'

Die Felder (hier Namen) werden umgestellt, sind aber ohne Trenner schlecht lesbar – also:

echo Anna Sepp Hans Berta | awk '{print $1 "\t" $4 "\t" $3 "\t" $2}'

Dies setzt einen Tabulator zwischen die Felder.

Nun wird es ernster: Awk soll aus dem Standardbefehl Top die aktuelle CPU-Last abgreifen und nur diesen einzigen Wert anzeigen:

top -n1 | grep "CPU(s)" | awk '{print 100-$8"%"}'

Feld 8 in der Zeile „CPU(s)“, die Grep vorfiltert, enthält den aktuellen Idle-Wert. Von 100 subtrahiert, ergibt sich die aktuelle CPU-Last.

Eigentlich ist Awk in diesem Fall gar nicht auf Grep angewiesen: Wenn eine Befehlsausgabe wie der Header von Top genau normiert ist, also von vornherein klar, in welcher Zeile welche Information erscheint, dann kann Awk auf die Nachhilfe von horizontalen Filtern verzichten. Mit „NR==“ lässt sich die exakte Zeilennummer einstellen und nachfolgend auswerten. Das folgende Beispiel ermittelt die aktuelle RAM-Belegung aus Zeile 4 des Top-Kommandos:

top -n1 | awk 'NR==4 {print int($8) " MB belegt von " int($4) " MB"}'

Dies wird ein Ergebnis wie „524 MB belegt von 3732 MB“ erzeugen. Die Funktion „int“ ist nicht zwingend, bereinigt aber auf gut lesbare Ganzzahlen.

Auf diese Weise wird es relativ einfach, gesuchte Werte nicht nur auszugeben, sondern als Bash-Variable zur Weiterverarbeitung abzulegen:

RAM_used=$(top -n1 | awk 'NR==4 {print int($8)}')

Danach steht die Variable $RAM_used für Scripts oder Funktionen zur Verfügung.

Awk kann auch eine Zeilennummerierung ausführen:

find . | awk '{print NR "\t" $0}'

Hier will offenbar jemand wissen, wie viele Dateien sich im aktuellen Ordner befinden: Awk nummeriert die Zeilen und hängt nach Tabulator die komplette Zeile an, wie sie Find anliefert („$0“ bedeutet die gesamte Zeile ohne Feldauswahl).

Wenn statt einer klaren Tabellenstruktur die Felder durch Trenner wie Semikolon, Doppelpunkt oder Slash definiert sind, dann hilft der Awk-Schalter „-F“ („Field“):

cat /etc/passwd | awk -F: '{print $3 "\t" $1}'

Die Datei /etc/passwd nutzt den Doppelpunkt als Feldtrenner, daher „-F:“. Die Ausgabe wird dann auf Username und UID reduziert, die Spalten umgedreht (Name zuerst) und zur besseren Lesbarkeit ein Tabulator zwischengeschaltet (Tipp: Der Backslash ist das typische Signal für Sonderzeichen in Bash-Filtern – hier „\t“ für den Tabulator).

Das letzte Beispiel zeigt zugleich noch einmal, dass Awk die Spalten eines Ausgangbefehls beliebig umstellen und unnötige Spalten ausblenden kann.

Awk pickt sich jeden Wert aus Zeilen und Spalten. Dieser Filter ist für Scripts und Aliases absolut unentbehrlich.

Column: Spaltenzerlegung

Dieses Tool verbessert die Lesbarkeit unformatierter Listen signifikant. Wer Awk beherrscht, wird Column zwar nicht oft brauchen, aber das Tool ist handlich und setzt passende Tabulatoren nach Berechnung aller Daten. Es kann auch als Vorbereitung dienen, um eine weitere Zerlegung durch Awk zu erleichtern.

Vergleichen Sie die überaus schlecht lesbare Ausgabe von mount mit dieser Variante

mount | column -t

oder den Befehl cat /etc/passwd mit dieser Tabellendarstellung:

cat /etc/passwd | column -s: -t

Column ersetzt hier das Trennzeichen „:“ (Schalter „-s“ steht für Separator) durch Tabulatoren. Sortiert man das Ganze dann noch mit

cat /etc/passwd | column -s: -t | sort -n -k3

numerisch nach der User-ID in Spalte 3, ist das Resultat mit der Originaldatei kaum noch zu vergleichen. Anders als Awk oder Sed, die ebenfalls Tabulatoren einsetzen können, errechnet Column eine passende Tabulatorweite für alle Spalten der gesamten Datenmenge.

Column schafft aber auch Felder, wo vorher keine waren. Der schon früher genannte Befehl

apt list --installed

trennt die Paketnamen durch einen „/“-Slash von der nachfolgenden Paketquelle. Mit

apt list --installed | column -s/ -t

wandeln Sie den Slash-Trenner um in eine gut lesbare Tabelle und mit

apt list --installed | column -s/ -t | awk '{print $1}'

bleiben nur noch die Paketnamen der ersten Spalte übrig, die sich dann für eine Masseninstallation verwenden lässt. Hierfür ist das Tool Xargs einschlägig (siehe unten).

Hinweis: Für genau dasselbe Ergebnis des letzten Befehls reicht mit

apt list --installed | awk -F/ '{print $1}'

auch das begabte Awk allein.

Column macht Listen mit seltsamen Trennzeichen lesbar und ist vor allem zur Darstellung größerer Tabellen die optisch ansprechendste Lösung.

Cut: Spalten definieren und entfernen

Column und vor allem Awk bieten alle Möglichkeiten, um vertikale Spalten und Felder darzustellen, zu analysieren oder bei Bedarf durch Umwandlung von Trennzeichen erst herzustellen. Das Tool Cut ist technisch weitaus einfacher, aber gerade deswegen oft die unkompliziertere Alternative.

Cut eignet sich ideal, um eine bestimmte Spalte aus einer Liste zu filtern, die ihre Felder mit einem Delimiter-Zeichen wie Semikolon, Komma, Doppelpunkt, Bindestrich oder Slash trennt. Die wichtigsten Schalter von Cut sind „-d“ (Delimiter), der das Trennzeichen definiert, und „-f“ für die Angabe der gewünschten Spalten (Fields). Der Befehl

echo Anna Sepp Hugo | cut -d" " -f2

wird folglich mit „Sepp“ antworten. Das letzte Beispiel oben, das mit Column und Awk eine Paketliste erstellte, ist daher mit dem Tool Cut noch einfacher zu realisieren:

apt list --installed | cut -d/ -f1

Mit dem Schalter „-d“ wird hier der Slash „/“ als Spaltentrenner definiert und mit „-f1“ nur noch die erste Spalte angezeigt, also die puren Paketnamen. Im folgenden Beispiel

cut -d: -f1,3 /etc/passwd

reduziert Cut die genannte Datei auf die zwei Felder Usernamen und UID.

Cut ist ein relativ einfaches Tool, um gezielt eine oder mehrere Spalte(n) einer Liste zu isolieren.

Sed: Zeichen ersetzen oder löschen

Der Streameditor Sed eignet sich sowohl zur Dateibearbeitung als auch zur Korrektur des Terminaloutputs. Er ersetzt einzelne Zeichen oder Zeichenfolgen durch einen anderen String, löscht störende Zeichen oder fügt zusätzliche ein.

Sed kann automatische Textersetzungen oder Löschungen in allen Dateien des aktuellen Verzeichnisses erledigen. Ferner kann es auch als Kosmetik für schlecht lesbare Terminalbefehle dienen. Wenn das mächtige Werkzeug viele Dateien bearbeiten soll, ist Vorsicht geboten und zumindest ein vorhergehender Test zu empfehlen, der nur im Terminal angezeigt wird:

sed 's/domain.org/neue.domain.de/g' *.html

Jedes Vorkommen von „domain.org“ wird durch die neue Adresse korrigiert. Mit zusätzlichem Schalter „-i“ (oder „–in-place“) erledigt Sed die Aktion tatsächlich.

Hocheffizient, aber auch riskant sind Löschkommandos mit „d“:

sed -i '/bind /d' ~/.bashrc

Hier wird jede Zeile der angegebenen Datei gelöscht, die mit dem Kommando „bind “ beginnt.

Sed ist aber auch für die interaktive Kosmetik am Terminaloutput nützlich. Die Ausgabe des Systempfads mit

echo $PATH

trennt die Ordner bekanntlich schlecht lesbar mit Doppelpunkt. Folgende Alternative

echo $PATH | sed 's/:/\n/g'

ersetzt alle Doppelpunkte durch einen Zeilenumbruch („\n“) und macht die Ausgabe übersichtlich. Wie schon bei Awk angemerkt, ist der Backslash wieder das typische Signal für ein nachfolgendes Sonderzeichen.

Streameditor Sed: Das Tool ist ein phänomenaler Zeitsparer, erfordert aber sehr präzise und eindeutige Angaben.

Xargs: Andere Befehle füttern

Das Tool Xargs gehört in diesen Kontext, obwohl es selbst keine Filterleistung bietet. Aber es gibt vorher gefilterte Argumente oder den Inhalt einer Dateiliste direkt an einen anderen Befehl weiter. Damit ist es eine nützliche Abkürzung, die eine weitaus umständlichere Scriptverarbeitung erspart.

Im ersten einfachen Beispiel wird eine vorher angelegte Paketliste

apt-mark showmanual > liste.txt

später an diesem oder auf einem anderen System mit

cat liste.txt | xargs apt install

in einem Rutsch installiert. Xargs übergibt einfach den kompletten Inhalt der Textdatei an den Paketmanager.

Diese beiden nachfolgenden Befehle sind funktionsidentisch:

xargs -a liste.txt -n 1 apt install
cat liste.txt | xargs -n 1 apt install

Das zeilenweise Abarbeiten mit „-n 1“ ist notwendig, wenn die Listendatei für jedes Argument eine eigene Zeile nutzt.
Xargs kann auch für eine besser lesbarere oder ergänzte Terminalausgabe sorgen:

free -m | grep "Speicher" | awk '{print int(($3/$2)*100)}' | xargs -i echo {}% RAM belegt

Die Ausgabe von free wird auf die Zeile mit „Speicher“ gekürzt. Awk filtert dort die Werte für den Gesamtspeicher ($2) und den benutzten Speicher ($3) und berechnet den belegten Prozentwert (bereinigt durch „int“ auf eine Ganzzahl). Damit nicht die bloße Zahl geliefert wird, übergibt Xargs den Wert an Echo. Weil der vorher ermittelte Wert nicht am Ende des Echo-Befehls steht, muss Xargs mit Schalter „-i“ verwendet werden. Die Klammern „{}“ definieren dann die Stelle für den Übergabewert.

Xargs ist ein Wertelieferant und gibt die von Bash-Filtern gesammelten Infos an einen anderen Befehl weiter (hier nur an Echo).

Bash-Filter: More & Less

Es gibt eine Reihe weiterer (Mengen-) Filter wie More und Less zum zeilenweisen Blättern in der Ausgabe, Head und Tail zur Reduktion der Ausgabe auf die ersten oder letzten Zeilen. Dies nur der Vollständigkeit halber – denn dieser letzte Abschnitt ist eher ein Fazit.

In der Bash-Shell geht mit den genannten Tools im Prinzip alles. Wie bei allen textbasierten Shells ist es aber kein Vergnügen. Die Erbsenzählerei zum benötigten Feld für eine Awk-Auswertung ist mühsam, aber immerhin lohnend. Richtig ärgerlich ist, dass Bash-Tools keine einheitlichen Schalter haben und etwa einen Spaltentrennzeichen mal mit „-F“, mit „-d“ oder mit „-t“ definieren. Eine weitere Herausforderung ist es, dass Tools ähnliche Funktionen enthalten, die unterschiedliche Lösungen für ein und dasselbe Problem bieten. Unterm Strich sollten Grep, Sort und Awk alle Aufgaben erledigen – sofern man sie richtig beherrscht. Sed ist ein Editierautomat, der sich vornehmlich an Script- und Webentwickler richtet.