Die vorhandenen Antworten speichern alle die Ausgabe im Speicher (in einer Variablen) und spielen sie zweimal ab. Dies ist ein Problem, wenn Sie einen generischen Wrapper erstellen möchten, der eine beliebig große Eingabe annehmen und zwei Aufgaben ausführen kann. Stattdessen kann der Ausgabestream dupliziert und in zwei Befehle gestreamt werden.
In meinem Fall besteht der Zweck darin, sowohl den Header (erste Zeile) als auch eine bestimmte (Gruppe von) Zeile (n) in einem Ausgabestream zu filtern, der beliebig lang sein kann. Ein einfaches Beispiel wäre die Anzeige der Speicherplatznutzung:
$ df -h | tee >(head -1 >&2) | grep '/$'
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 202G 145G 57G 72% /
Ersetzen Sie df -h
mit dem Befehl , den Sie verwenden möchten, und ersetzen head -1
und grep '/$'
mit den beiden Befehlen möchten Sie sie anzuwenden. Die Ausgabe von beiden wird in Ihrem Terminal angezeigt, obwohl möglicherweise die Ausgabe des ersteren Befehls nach dem letzteren angezeigt wird.
Wie funktioniert das?
- Das Programm
tee
"[kopiert] die Standardeingabe in jedes [Argument] und auch in die Standardausgabe." So kann es die Ausgabe von stdin sowohl an stdout als auch an stderr senden, indem es verwendet command | tee /dev/stderr
.
- Die
command >(command2)
Syntax wird durch ein Argument durch bash ersetzt und command /dev/fd/63
wird ausgeführt. Wenn command
versucht wird, darauf zu schreiben /dev/fd/63
, landet es in der Eingabe (stdin) von command2
. Dies wird als Prozesssubstitution bezeichnet (siehe man bash
).
- Da
tee
sowohl in das Argument (wir übergeben eine Befehlssubstitution als Argument) als auch in stdout geschrieben wird, können wir einfach eine weitere Pipe hinzufügen und einen weiteren Befehl ausführen. Also jetzt haben wir command | tee >(command2) | command3
.
- Da Befehl2 an stdout ausgegeben wird und stdout an weitergeleitet wird
command3
, würden wir (in meinem Beispiel) die Kopfzeile erfassen. Das wollen wir nicht: Wir wollen es anzeigen. Da wir stderr nicht durchleiten, ist die Umleitung der Ausgabe zu stderr eine einfache Möglichkeit, sie in unserem Terminal anzuzeigen, dh wir fügen hinzu >&2
, was dazu führt command | tee >(command2 >&2) | command3
.
Es gibt ein Problem: Die Ausgabe kann in beliebiger Reihenfolge erfolgen. Abhängig von der kosmischen Strahlung können wir entweder das Obige oder das Folgende sehen:
$ df -h | tee >(head -1 >&2) | grep '/$'
/dev/sda1 202G 145G 57G 72% /
Filesystem Size Used Avail Use% Mounted on
Eine hackige, aber zuverlässige Möglichkeit, dies zu beheben (anstelle einer überentwickelten Methode, die nicht hackig ist), besteht darin, dem zweiten Befehl einen kurzen Ruhezustand hinzuzufügen. etwas wie:
$ df -h | tee >(head -1 >&2) | sleep 1; grep '/$'
Aber warten Sie , das bricht den zweiten Befehl ( grep
), weil jetzt die Ausgabe von geleitet wird tee
zu sleep
und grep
wird für die Eingabe auf unbestimmte Zeit warten. Um dies zu beheben, fügen wir eine Unterschale hinzu:
$ df -h | tee >(head -1 >&2) | (sleep 0.01; grep '/$')
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 202G 145G 57G 72% /
Jetzt wird die Ausgabe nicht zu, grep
sondern zu unserer Subshell umgeleitet . Da sleep
es nicht daraus liest (es verbraucht den Stream nicht), steht es weiterhin grep
zum Lesen zur Verfügung. Jetzt funktioniert es zuverlässig, solange die head
Ausgabe innerhalb von 0,01 Sekunden erfolgt (plus ein wenig Overhead auf der Grep-Seite). Dies ist eine faire Wette auf ein modernes System und kurz genug, um für den Benutzer nicht erkennbar zu sein.
Da ich etwas machen wollte, das sowohl den Header als auch die Ausgabe eines Befehls benötigt, können wir dies verallgemeinern auf:
function grabheader {
tee >(head -1 >&2)
}
Da der tee
Befehl in der Funktion nur von stdin liest und an stdout ausgibt, funktioniert dies genauso wie unser früherer Befehl außerhalb der Reihenfolge, wenn Sie ihn als verwenden df -h | grabheader | grep '/$'
. Aber da wir wollen, dass es in Ordnung ist, müssen wir es verzögern, es über den Standard zu senden:
function grabheader {
tee >(head -1 >&2) | (sleep 0.01; cat)
}
cat
hier wird nur sichergestellt, dass alles, was an den stdin übergeben wird, wieder auf den stdout gelangt. Wenn Sie keine Argumente übergeben und keine Umleitungen hinzufügen, wird genau das getan. Verwendungszweck:
$ df -h | grabheader | grep '/$'
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 202G 145G 57G 72% /
Im speziellen Fall von df
kann dies natürlich viel einfacher gemacht werden:
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 202G 145G 57G 72% /
Aber jetzt haben wir eine allgemeine Möglichkeit, dies mit jedem Befehl zu tun.