103.4 Lektion 2
Zertifikat: |
LPIC-1 (101) |
---|---|
Version: |
5.0 |
Thema: |
103 GNU- und Unix-Befehle |
Lernziel: |
103.4 Ströme, Pipes und Umleitungen verwenden |
Lektion: |
2 von 2 |
Einführung
Ein Aspekt der Unix-Philosophie besagt, dass jedes Programm einen spezifischen Zweck haben sollte und nicht versuchen sollte Funktionalitäten außerhalb seines Geltungsbereichs zu integrieren. Aber die Dinge einfach zu halten bedeutet nicht, dass die Ergebnisse weniger ausgefeilt sind, denn verschiedene Programme können miteinander verkettet werden, um ein kombiniertes Resultat zu erzeugen. Der senkrechte Strich |
, auch als Pipesymbol bekannt, kann verwendet werden, um eine Pipeline zu erstellen, welche die Ausgabe eines Programms direkt mit der Eingabe eines anderen Programms verbindet, während Befehlssubstitution es erlaubt, die Ausgabe eines Programms in einer Variablen zu speichern oder diese direkt als Argument für einen anderen Befehl zu verwenden.
Pipes
Im Gegensatz zu Umleitungen fließen bei Pipes die Daten in der Befehlszeile von links nach rechts, und das Ziel ist ein anderer Prozess und nicht ein Dateisystempfad, ein Dateideskriptor oder ein Here-Dokument. Das Pipe-Symbol |
weist die Shell an, alle verschiedenen Befehle gleichzeitig zu starten und die Ausgabe des vorhergehenden Befehls mit der Eingabe des folgenden Befehls von links nach rechts zu verbinden. Anstatt beispielsweise Umleitungen zu verwenden, kann der Inhalt der Datei /proc/cpuinfo
, die von cat
an die Standardausgabe gesendet wird, mit dem folgenden Befehl an stdin von wc
gepiped werden:
$ cat /proc/cpuinfo | wc 208 1184 6096
Fehlt ein Pfad zu einer Datei, zählt wc
die Anzahl der Zeilen, Wörter und Zeichen, die es auf seiner stdin erhält, wie es im obigen Beispiel der Fall ist. In einem zusammengesetzten Befehl können viele Pipes vorhanden sein. Im folgenden Beispiel werden zwei Pipes verwendet:
$ cat /proc/cpuinfo | grep 'model name' | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
Der Inhalt der Datei /proc/cpuinfo
, die von cat /proc/cpuinfo
erzeugt wird, wird an den Befehl grep 'model name'
weitergeleitet, der dann nur die Zeilen ausgibt, welche den Begriff model name
enthalten. Die Maschine, auf der das Beispiel läuft, hat viele CPUs, daher gibt es mehrfach Zeilen mit dem String model name
. Die letzte Pipe verbindet grep 'model name'
mit dem Befehl uniq
, der dafür verantwortlich ist jede Zeile, die gleich der vorherigen ist, zu überspringen.
Pipes können mit Umleitungen in derselben Befehlszeile kombiniert werden. Das vorherige Beispiel kann in folgende vereinfachte Form umgeschrieben werden:
$ grep 'model name' </proc/cpuinfo | uniq model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
Die Eingabeumleitung für grep
ist nicht unbedingt notwendig, da grep
einen Dateipfad als Argument akzeptiert, aber das Beispiel zeigt, wie solche kombinierten Befehle erstellt werden können.
Pipes und Weiterleitungen sind exklusiv, d.h. eine Quelle kann nur einem Ziel zugeordnet werden. Es ist jedoch möglich, eine Ausgabe in eine Datei umzuleiten und diese trotzdem mit dem Programm tee
auf dem Bildschirm anzuzeigen. Dazu sendet das erste Programm seine Ausgabe an stdin von tee
. Zum Speichern der Daten wird tee
zusätzlich noch ein Dateiname übergeben:
$ grep 'model name' </proc/cpuinfo | uniq | tee cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz $ cat cpu_model.txt model name : Intel(R) Xeon(R) CPU X5355 @ 2.66GHz
Die Ausgabe des letzten Programms in der Kette, die von uniq
erzeugt wurde, wird angezeigt und in der Datei cpu_model.txt
gespeichert. Um den Inhalt der bereitgestellten Datei nicht zu überschreiben, sondern Daten an diese anzuhängen, muß tee
mit der Option -a
kombiniert werden.
Nur die Standardausgabe eines Prozesses wird durch eine Pipe erfasst. Nehmen wir an, Sie müssen einen langwierigen Kompilierungsprozess auf dem Bildschirm durchlaufen und gleichzeitig sowohl die Standardausgabe als auch den Standardfehler in einer Datei zur späteren Überprüfung speichern. Angenommen Ihr aktuelles Verzeichnis hat kein Makefile, dann wird der folgende Befehl einen Fehler verursachen:
$ make | tee log.txt make: *** No targets specified and no makefile found. Stop.
Obwohl auf dem Bildschirm angezeigt, wurde die von make
erzeugte Fehlermeldung nicht von tee
erfasst und die Datei log.txt wurde zwar erstellt, blieb aber leer. Es muss eine Umleitung durchgeführt werden, bevor eine Pipe stderr erfassen kann:
$ make 2>&1 | tee log.txt make: *** No targets specified and no makefile found. Stop. $ cat log.txt make: *** No targets specified and no makefile found. Stop.
In diesem Beispiel wurde stderr von make
auf stdout umgeleitet, so dass tee
in der Lage war, ihn mit der Pipe zu erfassen, auf dem Bildschirm anzuzeigen und in der Datei log.txt
zu speichern. In Fällen wie diesem kann es nützlich sein, die Fehlermeldungen für eine spätere Überprüfung zu speichern.
Befehlssubstitution
Eine andere Methode zur Erfassung der Ausgabe eines Befehls ist die Befehlssubstitution. Indem ein Befehl in Anführungszeichen gesetzt wird, ersetzt die Bash ihn durch seine Standardausgabe. Das folgende Beispiel zeigt, wie stdout eines Programms als Argument für ein anderes Programm verwendet werden kann:
$ mkdir `date +%Y-%m-%d` $ ls 2019-09-05
Die Ausgabe des Programms date
, das aktuelle Datum formatiert als Jahr-Monat-Tag, wurde als Argument benutzt, um ein Verzeichnis mittels mkdir
zu erstellen. Ein identisches Ergebnis erhält man durch die Verwendung von $()
anstelle von Backquotes:
$ rmdir 2019-09-05 $ mkdir $(date +%Y-%m-%d) $ ls 2019-09-05
Dieselbe Methode kann verwendet werden, um die Ausgabe eines Befehls als Variable zu speichern:
$ OS=`uname -o` $ echo $OS GNU/Linux
Der Befehl uname -o
gibt den generischen Namen des aktuellen Betriebssystems aus, der in der Sessionvariablen OS
gespeichert ist. Die Zuweisung der Ausgabe eines Befehls an eine Variable ist in Skripten sehr nützlich und ermöglicht es, die Daten auf viele verschiedene Arten zu speichern und auszuwerten.
Abhängig von der Ausgabe, die durch den ersetzten Befehl erzeugt wird, ist die eingebaute Befehlssubstitution möglicherweise nicht geeignet. Eine ausgefeiltere Methode, die Ausgabe eines Programms als Argument eines anderen Programms zu verwenden, benutzt eine Zwischenstufe namens xargs
. Das Programm xargs
verwendet den Inhalt, den es über stdin erhält, um einen bestimmten Befehl mit dem Inhalt als Argument auszuführen. Das folgende Beispiel zeigt, wie xargs
das Programm identify
mit den vom Programm find
gelieferten Argumenten ausführt:
$ find /usr/share/icons -name 'debian*' | xargs identify -format "%f: %wx%h\n" debian-swirl.svg: 48x48 debian-swirl.png: 22x22 debian-swirl.png: 32x32 debian-swirl.png: 256x256 debian-swirl.png: 48x48 debian-swirl.png: 16x16 debian-swirl.png: 24x24 debian-swirl.svg: 48x48
Das Programm identify
ist Teil von ImageMagick, einem Satz von Kommandozeilentools zum Prüfen, Konvertieren und Bearbeiten der meisten Bilddateitypen. Im Beispiel nimmt xargs
alle Pfade, die von find
aufgelistet wurden, und setzt diese als Argumente für identify
ein, das dann die Informationen für jede Datei anzeigt, die entsprechend der Option -format
formatiert wurden. Die von find
im Beispiel gefundenen Dateien sind Bilder, die das Logo der Distribution in einem Debiandateisystem enthalten. -format
ist ein Parameter von identify
, nicht von xargs
.
Die Option -n 1
veranlasst xargs
, den gegebenen Befehl mit nur einem Argument gleichzeitig auszuführen. Anstatt alle von find
gefundenen Pfade als Liste von Argumenten für identify
zu übergeben, würde im Fall des Beispiels die Verwendung von xargs -n 1
den Befehl identify
für jeden Pfad einzeln ausführen. Die Verwendung von -n 2
würde den Befehl identify
mit zwei Pfaden als Argument ausführen, -n 3
mit drei Pfaden als Argument und so weiter. In ähnlicher Weise kann bei der Verarbeitung mehrzeiliger Inhalte durch xargs
— wie es bei der Eingabe durch find
der Fall ist — die Option -L
verwendet werden, um zu begrenzen, wie viele Zeilen als Argumente pro Befehlsausführung verwendet werden.
Note
|
Die Verwendung von |
Wenn die Pfade Leerzeichen enthalten, ist es wichtig, find
mit der Option -print0
auszuführen. Diese Option weist find
an, ein Null-Zeichen zwischen jedem Eintrag zu verwenden, damit die Liste von xargs
korrekt geparst werden kann (die Ausgabe wurde unterdrückt):
$ find . -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 du | sort -n
Die Option -0
weist xargs
an, dass das Null-Zeichen als Trennzeichen verwendet werden soll. Auf diese Weise werden die von find
angegebenen Dateipfade korrekt geparst, auch wenn diese Leer- oder andere Sonderzeichen enthalten. Das vorherige Beispiel zeigt, wie man den Befehl du
benutzt, um die Plattennutzung jeder gefundenen Datei herauszufinden und dann die Ergebnisse nach Größe zu sortieren. Die Ausgabe wurde aus Gründen der Übersichtlichkeit vernachlässigt. Beachten Sie, dass es für jedes Suchkriterium notwendig ist, die Option -print0
für find
zu benutzen.
Standardmäßig setzt xargs
die Argumente des ausgeführten Befehls an die letzte Stelle. Um dieses Verhalten zu ändern, kann die Option -I
verwendet werden:
$ find . -mindepth 2 -name '*avi' -print0 -o -name '*mp4' -print0 -o -name '*mkv' -print0 | xargs -0 -I PATH mv PATH ./
Im letzten Beispiel wird jede von find
gefundene Datei in das aktuelle Verzeichnis verschoben. Da der/die Quellpfad(e) mv
vor dem Zielpfad mitgeteilt werden muß/müssen, wird der Option -I
von xargs
ein Ersatzbegriff übergeben, der dann entsprechend neben mv
gesetzt wird. Durch die Verwendung des Null-Zeichens als Trennzeichen ist es nicht notwendig, den Substitutionsterm in Anführungszeichen zu setzen.
Geführte Übungen
-
Es ist üblich das Ausführungsdatum von Aktionen zu speichern, die von automatisierten Skripten ausgeführt werden. Der Befehl
date +%Y-%m-%d
zeigt das aktuelle Datum im Format Jahr-Monat-Tag. Wie kann die Ausgabe eines solchen Befehls mittels Befehlssubstitution in einer Shellvariablen namensTODAY
gespeichert werden? -
Wie kann der Inhalt der Variablen
TODAY
mittels des Befehlsecho
an die Standardeingabe des Befehlssed s/-/./g
übergeben werden? -
Wie könnte die Ausgabe des Befehls
date +%Y-%m-%d
als Here-String verwendet werden, umsed s/-/./g
zu instruieren? -
Der Befehl
convert image.jpeg -resize 25% small/image.jpeg
erzeugt eine kleinere Version vonimage.jpeg
und legt das resultierende Bild in einer gleichnamigen Datei des Unterverzeichnissmall
ab. Wie ist es mitxargs
möglich, für jedes in der Dateifilelist.txt
aufgeführte Bild den gleichen Befehl auszuführen?
Offene Übungen
-
Eine einfache Sicherungsroutine erstellt periodisch ein Abbild der Partition
/dev/sda1
mittelsdd < /dev/sda1 > sda1.img
. Um zukünftige Datenintegritätsprüfungen durchzuführen, erzeugt die Routine auch einen SHA1-Hash der Datei mittelssha1sum < sda1.img > sda1.sha1
. Wie würden diese beiden Befehle durch Hinzufügen von Pipes und dem Befehltee
zu einem Einzigen kombiniert werden? -
Der Befehl
tar
wird verwendet, um viele Dateien in einer einzigen Datei zu archivieren, wobei die Verzeichnisstruktur erhalten bleibt. Die Option-T
erlaubt es, eine Datei anzugeben, welche die zu archivierenden Pfade enthält. Zum Beispiel erzeugtfind /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
eine komprimierte Datei im Format von tar namensetc.tar.xz
aus der durch den Befehlfind
bereitgestellten Liste (Option-T -
gibt die Standardeingabe als Pfadliste an). Um mögliche Parsingfehler aufgrund von Pfaden, die Leerzeichen enthalten, zu vermeiden, sollten welche Befehlsoptionen fürfind
undtar
genutzt sein? -
Anstatt eine neue Remoteshellsitzung zu eröffnen, kann der Befehl
ssh
einfach einen Befehl ausführen, der als Argument angegeben wird:ssh user@storage "remote command"
. Dassh
auch erlaubt die Standardausgabe eines lokalen Programms auf die Standardeingabe des entfernten Programms umzuleiten, wie würde der Befehlcat
eine lokale Datei mit dem Namenetc.tar.gz
an/srv/backup/etc.tar.gz
unteruser@storage
durchssh
umleiten?
Zusammenfassung
Diese Lektion behandelt traditionelle Interprozesskommunikationstechniken, die von Linux verwendet werden. Befehlspipelining erzeugt einen Einwegkommunikationskanal zwischen zwei Prozessen und Befehlssubstitution erlaubt es, die Ausgabe eines Prozesses in einer Shellvariablen zu speichern. Die Lektion behandelt folgenden Punkte:
-
Wie Pipes verwendet werden können, um den Output eines Prozesses an den Input eines anderen Prozesses zu streamen.
-
Der Zweck der Befehle
tee
undxargs
. -
Erfassung von Prozessausgaben mittels Befehlssubstitution, Speicherung dieser in Variablen oder Verwendung dieser direkt als Parameter für einen anderen Befehl.
Die behandelten Befehle und Verfahren lauten:
-
Pipelines benutzen mittels
|
. -
Befehlssubstitution mit Backticks und
$()
. -
Die Befehle
tee
,xargs
undfind
.
Lösungen zu den geführten Übungen
-
Es ist üblich das Ausführungsdatum von Aktionen zu speichern, die von automatisierten Skripten ausgeführt werden. Der Befehl
date +%Y-%m-%d
zeigt das aktuelle Datum im Format Jahr-Monat-Tag. Wie kann die Ausgabe eines solchen Befehls mittels Befehlssubstitution in einer Shellvariablen namensTODAY
gespeichert werden?$ TODAY=`date +%Y-%m-%d`
oder
$ TODAY=$(date +%Y-%m-%d)
-
Wie kann der Inhalt der Variablen
TODAY
mittels des Befehlsecho
an die Standardeingabe des Befehlssed s/-/./g
übergeben werden?$ echo $TODAY | sed s/-/./g
-
Wie könnte die Ausgabe des Befehls
date +%Y-%m-%d
als Here-String verwendet werden, umsed s/-/./g
zu instruieren?$ sed s/-/./g <<< `date +%Y-%m-%d`
oder
$ sed s/-/./g <<< $(date +%Y-%m-%d)
-
Der Befehl
convert image.jpeg -resize 25% small/image.jpeg
erzeugt eine kleinere Version vonimage.jpeg
und legt das resultierende Bild in einer gleichnamigen Datei des Unterverzeichnissmall
ab. Wie ist es mitxargs
möglich, für jedes in der Dateifilelist.txt
aufgeführte Bild den gleichen Befehl auszuführen?$ xargs -I IMG convert IMG -resize 25% small/IMG < filelist.txt
oder
$ cat filelist.txt | xargs -I IMG convert IMG -resize 25% small/IMG
Lösungen zu den offenen Übungen
-
Eine einfache Sicherungsroutine erstellt periodisch ein Abbild der Partition
/dev/sda1
mittelsdd < /dev/sda1 > sda1.img
. Um zukünftige Datenintegritätsprüfungen durchzuführen, erzeugt die Routine auch einen SHA1-Hash der Datei mittelssha1sum < sda1.img > sda1.sha1
. Wie würden diese beiden Befehle durch Hinzufügen von Pipes und dem Befehltee
zu einem Einzigen kombiniert werden?# dd < /dev/sda1 | tee sda1.img | sha1sum > sda1.sha1
-
Der Befehl
tar
wird verwendet, um viele Dateien in einer einzigen Datei zu archivieren, wobei die Verzeichnisstruktur erhalten bleibt. Die Option-T
erlaubt es, eine Datei anzugeben, welche die zu archivierenden Pfade enthält. Zum Beispiel erzeugtfind /etc -type f | tar -cJ -f /srv/backup/etc.tar.xz -T -
eine komprimierte Datei im Format von tar namensetc.tar.xz
aus der durch den Befehlfind
bereitgestellten Liste (Option-T -
gibt die Standardeingabe als Pfadliste an). Um mögliche Parsingfehler aufgrund von Pfaden, die Leerzeichen enthalten, zu vermeiden, sollten welche Befehlsoptionen fürfind
undtar
genutzt sein?Options
-print0
und--null
:$ find /etc -type f -print0 | tar -cJ -f /srv/backup/etc.tar.xz --null -T -
-
Anstatt eine neue Remote-Shell-Sitzung zu eröffnen, kann der Befehl
ssh
einfach einen Befehl ausführen, der als sein Argument angegeben ist:ssh user@storage "remote command"
. Dassh
auch erlaubt die Standardausgabe eines lokalen Programms auf die Standardeingabe des entfernten Programms umzuleiten, wie würde dascat
eine lokale Datei mit dem Namenetc.tar.gz
über die Pipe an/srv/backup/etc.tar.gz
unteruser@storage
durchssh
weiterleiten?$ cat etc.tar.gz | ssh user@storage "cat > /srv/backup/etc.tar.gz"
oder
$ ssh user@storage "cat > /srv/backup/etc.tar.gz" < etc.tar.gz