Wie kann ich stdout an mehrere Befehle senden?


186

Es gibt einige Befehle, die Eingaben filtern oder bearbeiten und sie dann als Ausgabe weitergeben, denke ich normalerweise stdout- aber einige Befehle nehmen einfach das stdinund tun, was sie damit tun, und geben nichts aus.

Ich kenne mich am besten mit OS X aus und so gibt es zwei, die mir sofort einfallen pbcopyund pbpaste- die Mittel zum Zugriff auf die Systemzwischenablage.

Jedenfalls weiß ich, dass ich stdoutden teeBefehl verwenden kann , wenn ich stdout nehmen und die Ausgabe spucken möchte, um auf beide und eine Datei zuzugreifen. Und ich weiß ein wenig darüber xargs, aber ich glaube nicht, dass ich danach suche.

Ich möchte wissen, wie ich stdoutzwischen zwei (oder mehr) Befehlen aufteilen kann . Zum Beispiel:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Es gibt wahrscheinlich ein besseres Beispiel als dieses, aber ich bin wirklich daran interessiert zu wissen, wie ich stdout an einen Befehl senden kann, der es nicht weiterleitet, und dabei stdoutnicht "stumm" geschaltet wird - ich frage nicht, wie catund grepTeil davon und kopieren Sie es in die Zwischenablage - die spezifischen Befehle sind nicht so wichtig.

Außerdem - ich frage nicht, wie ich das an eine Datei senden soll, und stdout- dies ist möglicherweise eine "doppelte" Frage (sorry), aber ich habe einige gesucht und konnte nur ähnliche finden, die gefragt haben, wie man zwischen stdout und einer Datei aufteilt - und die Antworten auf diese Fragen schienen zu sein tee, von denen ich nicht glaube, dass sie für mich funktionieren.

Zum Schluss fragen Sie sich vielleicht, warum Sie pbcopy nicht zum letzten Element in der Pipe-Kette machen sollten. und meine Antwort ist 1) was ist, wenn ich es verwenden und trotzdem die Ausgabe in der Konsole sehen möchte? 2) Was ist, wenn ich zwei Befehle verwenden möchte, die nicht ausgegeben werden, stdoutnachdem sie die Eingabe verarbeitet haben?

Oh, und noch etwas - mir ist klar, dass ich teeeine Named Pipe ( mkfifo) verwenden kann, aber ich hatte gehofft, dass dies ohne vorherige Einrichtung inline erfolgen kann :)


Antworten:


239

Sie können dafür die teeSubstitution verwenden und verarbeiten:

cat file.txt | tee >(pbcopy) | grep errors

Dadurch wird die gesamte Ausgabe von cat file.txtan pbcopygesendet, und Sie erhalten nur das Ergebnis von grepauf Ihrer Konsole.

Sie können mehrere Prozesse in das teeTeil einfügen:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors

21
Keine Sorge pbcopy, aber generell erwähnenswert: Unabhängig von den Prozesssubstitutionsausgaben wird diese auch vom nächsten Rohrsegment nach der ursprünglichen Eingabe angezeigt. Beispiel: seq 3 | tee >(cat -n) | cat -e( cat -nnummeriert die Eingabezeilen, cat -emarkiert die neuen Zeilen mit $; Sie werden sehen, dass dies cat -esowohl auf die ursprüngliche Eingabe (zuerst) als auch auf die Ausgabe von angewendet wird cat -n). Die Ausgabe mehrerer Prozessersetzungen erfolgt in nicht deterministischer Reihenfolge.
mklement0

49
Das >(funktioniert nur in bash. Wenn Sie das zum Beispiel mit versuchen, shfunktioniert es nicht. Es ist wichtig, dies zu bemerken.
AAlvz

10
@AAlvz: Guter Punkt: Die Prozessersetzung ist keine POSIX-Funktion. dash, die sich wie shUbuntu verhalten, unterstützen es nicht und selbst Bash deaktiviert die Funktion, wenn sie aufgerufen wird shoder wenn sie set -o posixin Kraft ist. Allerdings ist es nicht nur Bash , die Prozess Ersetzungen unterstützt: kshund zshunterstützen sie auch (nicht sicher über andere).
mklement0

2
@ mklement0 das scheint nicht wahr zu sein. Auf zsh (Ubuntu 14.04) wird Ihre Zeile gedruckt: 1 1 2 2 3 3 1 $ 2 $ 3 $ Das ist traurig, weil ich wirklich wollte, dass die Funktionalität so ist, wie Sie es sagen.
Aktau

2
@Aktau: In der Tat, nur mein Beispielbefehl , wie beschrieben arbeiten bashund ksh- zshscheinbar Ausgabe von Ausgangsprozess Ersetzungen durch die Pipeline nicht (wohl schicken, das ist vorzuziehen , weil sie nicht verschmutzen , was auf das nächste Pipeline - Segment gesendet wird - obwohl es wird noch gedruckt ). In allen genannten Shells ist es jedoch im Allgemeinen keine gute Idee, eine einzelne Pipeline zu haben, in der die reguläre Standardausgabe und die Ausgabe von Prozessersetzungen gemischt werden Ausgabedatensätze.
mklement0

124

Sie können mehrere Dateinamen angeben tee, und zusätzlich kann die Standardausgabe in einen Befehl umgeleitet werden. Um die Ausgabe an mehrere Befehle zu senden, müssen Sie mehrere Pipes erstellen und diese jeweils als eine Ausgabe von angeben tee. Hierfür gibt es verschiedene Möglichkeiten.

Prozessersetzung

Wenn Ihre Shell ksh93, bash oder zsh ist, können Sie die Prozessersetzung verwenden. Auf diese Weise können Sie eine Pipe an einen Befehl übergeben, der einen Dateinamen erwartet. Die Shell erstellt die Pipe und übergibt /dev/fd/3dem Befehl einen Dateinamen . Die Nummer ist der Dateideskriptor , mit dem die Pipe verbunden ist. Einige Unix-Varianten unterstützen dies nicht /dev/fd. Auf diesen wird stattdessen eine Named Pipe verwendet (siehe unten).

tee >(command1) >(command2) | command3

Dateideskriptoren

In jeder POSIX-Shell können Sie mehrere Dateideskriptoren explizit verwenden. Dies setzt eine unterstützte Unix-Variante voraus /dev/fd, da bis auf eine alle Ausgaben teevon namentlich angegeben werden müssen.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Named Pipes

Die einfachste und portabelste Methode ist die Verwendung von Named Pipes . Der Nachteil ist, dass Sie ein beschreibbares Verzeichnis finden, die Pipes erstellen und anschließend bereinigen müssen.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2

10
Vielen Dank für die Bereitstellung der beiden alternativen Versionen für diejenigen, die sich nicht auf bash oder ein bestimmtes ksh verlassen möchten.
Trr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3sollte doch sein command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", wie du willst, stdout of command3piped to tee, no? Ich habe Ihre Version unter getestet dashund teeblockiert das Warten auf Eingaben auf unbestimmte Zeit, aber das Umschalten der Reihenfolge ergab das erwartete Ergebnis.
Adrian Günter

1
@ AdrianGünter Nein. Alle drei Beispiele lesen Daten von der Standardeingabe und senden sie an command, command2und command3.
Gilles

@ Gilles Ich verstehe, ich habe die Absicht falsch interpretiert und versucht, das Snippet falsch zu verwenden. Danke für die Klarstellung!
Adrian Günter

Wenn Sie keine Kontrolle über die verwendete Shell haben, aber bash explizit verwenden können, können Sie dies tun <command> | bash -c 'tee >(command1) >(command2) | command3'. Es hat in meinem Fall geholfen.
GC5

16

Spielen Sie einfach mit der Prozessersetzung.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepsind zwei Binärdateien, deren Ausgabe mycommand_execmit der prozessspezifischen Eingabe übereinstimmt .


16

Wenn Sie verwenden zsh, können Sie die Leistungsfähigkeit der MULTIOSFunktion nutzen, dh den teeBefehl vollständig loswerden :

uname >file1 >file2

schreibt nur die Ausgabe von unamezwei verschiedenen Dateien: file1und file2, was ist gleichbedeutend mituname | tee file1 >file2

Ebenso Umleitung von Standardeingaben

wc -l <file1 <file2

entspricht cat file1 file2 | wc -l(bitte beachten Sie, dass dies nicht dasselbe ist, da wc -l file1 file2die spätere Anzahl der Zeilen in jeder Datei separat gezählt wird).

Natürlich können Sie MULTIOSdie Ausgabe auch verwenden , um sie nicht in Dateien, sondern in andere Prozesse umzuleiten, indem Sie die Prozessersetzung verwenden, z.

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')

3
Gut zu wissen. MULTIOSist eine Option , die standardmäßig aktiviert ist (und mit deaktiviert werden kann unsetopt MULTIOS).
mklement0

6

Für eine relativ kleine Ausgabe, die durch einen Befehl erzeugt wird, können wir die Ausgabe in eine temporäre Datei umleiten und diese temporäre Datei in einer Schleife an Befehle senden. Dies kann nützlich sein, wenn die Reihenfolge der ausgeführten Befehle eine Rolle spielt.

Das folgende Skript könnte dies beispielsweise tun:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Testlauf auf Ubuntu 16.04 mit /bin/shals dashShell:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash

5

Erfassen Sie den Befehl STDOUTin einer Variablen und verwenden Sie ihn so oft Sie möchten:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Wenn Sie erfassen müssen STDERRauch dann verwenden , 2>&1am Ende des Befehls, etwa so:

commandoutput="$(command-to-run 2>&1)"

3
Wo werden Variablen gespeichert? Wenn Sie es mit einer großen Datei oder Ähnlichem zu tun hätten, würde das nicht viel Speicher belegen? Sind Variablen in ihrer Größe begrenzt?
KWD

1
Was ist, wenn $ commandoutput sehr groß ist? Es ist besser, Pipes und Prozessersetzungen zu verwenden.
Nikhil Mulley

4
Offensichtlich ist diese Lösung nur möglich, wenn Sie wissen, dass die Größe der Ausgabe problemlos in den Speicher passt und Sie die gesamte Ausgabe puffern können, bevor Sie die nächsten Befehle ausführen. Pipes lösen diese beiden Probleme, indem sie Daten beliebiger Länge zulassen und sie in Echtzeit an den Empfänger streamen, sobald sie generiert werden.
Trr

2
Dies ist eine gute Lösung, wenn Sie eine kleine Ausgabe haben und wissen, dass die Ausgabe Text und nicht binär ist. (Shell-Variablen sind oft nicht binär sicher)
Rucent88

1
Ich kann dies nicht mit Binärdaten arbeiten. Ich denke, dass es etwas mit Echo ist, das versucht, null Bytes oder einige andere Nichtzeichendaten zu interpretieren.
Rolf


0

Hier ist eine schnelle und schmutzige Teillösung, die mit jeder Shell einschließlich kompatibel ist busybox.

Das engere Problem, das es löst, ist: Drucken Sie das komplette stdoutDokument auf einer Konsole und filtern Sie es auf einer anderen, ohne temporäre Dateien oder Named Pipes.

  • Starten Sie eine weitere Sitzung auf demselben Host. Geben Sie Folgendes ein, um den TTY-Namen zu ermitteln tty. Nehmen wir an /dev/pty/2.
  • Führen Sie in der ersten Sitzung aus the_program | tee /dev/pty/2 | grep ImportantLog:

Sie erhalten ein vollständiges und ein gefiltertes Protokoll.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.