Pipe zu mehreren Dateien in der Shell

29

Ich habe eine Anwendung, die eine große Datenmenge erzeugt, die ich nicht auf der Festplatte speichern möchte. Die Anwendung gibt hauptsächlich Daten aus, die ich nicht verwenden möchte, aber eine Reihe nützlicher Informationen, die in separate Dateien aufgeteilt werden müssen. Zum Beispiel mit der folgenden Ausgabe:

JUNK
JUNK
JUNK
JUNK
A 1
JUNK
B 5
C 1
JUNK

Ich könnte die Anwendung dreimal so ausführen:

./app | grep A > A.out
./app | grep B > B.out
./app | grep C > C.out

Das würde mir das bringen, was ich will, aber es würde zu lange dauern. Ich möchte auch nicht alle Ausgaben in einer einzigen Datei speichern und diese analysieren.

Gibt es eine Möglichkeit, die drei oben gezeigten Vorgänge so zu kombinieren, dass ich die Anwendung nur einmal ausführen muss und trotzdem drei separate Ausgabedateien erhalte?

sj755
quelle

Antworten:

78

Wenn Sie Tee haben

./app | tee >(grep A > A.out) >(grep B > B.out) >(grep C > C.out) > /dev/null

(von hier )

( über Prozesssubstitution )

Aurélien Ooms
quelle
4
Genial, das könnte auch so ./app | tee >(grep A > A.out) >(grep B > B.out) | grep C > C.out
aussehen
7
Diese Antwort ist derzeit die einzig zutreffende, da der ursprüngliche Titel der Frage "Pipe zu mehreren Prozessen" lautet.
acelent
3
+1. Dies ist die allgemein gültige Antwort, da dies nicht von der Tatsache abhängt, dass der spezifische Filterbefehl lautete grep.
Ruakh
1
Ich bin damit einverstanden, dass dies die beste Antwort auf die gestellte Frage ist und sollte so markiert werden. Parallel ist eine andere Lösung (wie angegeben), aber nach einigen zeitgesteuerten Vergleichen ist das obige Beispiel effizienter. Wenn die Operation stattdessen sehr CPU-intensive Vorgänge wie die Komprimierung mehrerer Dateien oder die Konvertierung mehrerer MP3-Dateien umfasst, sollte sich die parallele Lösung zweifellos als effektiver erweisen.
AsymLabs
32

Sie können verwenden awk

./app | awk '/A/{ print > "A.out"}; /B/{ print > "B.out"}; /C/{ print > "C.out"}'
Rahul Patil
quelle
6
Der Titel der Frage ist Pipe für mehrere Prozesse . Bei dieser Antwort handelt es sich um "Piping" (Versenden durch Regex) für mehrere Dateien . Da diese Antwort akzeptiert wurde, sollte der Titel der Frage entsprechend geändert werden.
acelent
@PauloMadeira Du hast recht. Was denkst du wäre ein besserer Titel?
sj755
Ich habe eine sehr kleine Änderung vorgeschlagen: "Pipe zu mehreren Dateien in der Shell". Die Überarbeitung steht noch aus. Probieren Sie es aus. Ich hatte erwartet, den Kommentar zu entfernen, wenn er akzeptiert wurde.
aktueller
@PauloMadeira - Ich habe den Titel geändert. Ihre Bearbeitung wurde nicht angezeigt, aber Sie haben Recht. Die Verwendung der Prozesse im Titel war falsch, wenn dies die akzeptierte Antwort ist.
SLM
17

Sie können auch die Pattern Matching-Fähigkeiten Ihrer Shell verwenden :

./app | while read line; do 
     [[ "$line" =~ A ]] && echo $line >> A.out; 
     [[ "$line" =~ B ]] && echo $line >> B.out; 
     [[ "$line" =~ C ]] && echo $line >> C.out; 
 done

Oder auch:

./app | while read line; do for foo in A B C; do 
     [[ "$line" =~ "$foo" ]] && echo $line >> "$foo".out; 
  done; done

Ein sicherer Weg, um mit Backslashes und Zeilen umzugehen, beginnend mit -:

./app | while IFS= read -r line; do for foo in A B C; do 
     [[ "$line" =~ "$foo" ]] && printf -- "$line\n" >> "$foo".out; 
  done; done

Wie @StephaneChazelas in den Kommentaren betont, ist dies nicht sehr effizient. Die beste Lösung ist wahrscheinlich @ AurélienOoms .

terdon
quelle
Das setzt voraus , der Eingang nicht Schrägstriche oder Leerzeichen oder Platzhalter enthalten, oder Zeilen , die mit beginnen -n, -e... Es ist auch schrecklich ineffizient sein würde , da es mehrere Systemaufrufe pro Zeile (ein Mittel read(2)pro Zeichen, die Datei geöffnet ist, schriftlich für jede Zeile geschlossen ...). Im Allgemeinen ist die Verwendung von while readSchleifen zum Verarbeiten von Text in Shells eine schlechte Praxis.
Stéphane Chazelas
@StephaneChazelas Ich habe meine Antwort bearbeitet. Es sollte -njetzt mit Backslashes und etc funktionieren . Soweit ich beurteilen kann, funktionieren beide Versionen mit Leerzeichen in Ordnung, irre ich mich?
Terdon
Nein, das erste Argument printfist das Format. Es gibt keinen Grund, Sie Variablen dort nicht zitiert zu lassen.
Stéphane Chazelas
Dies unterbricht auch Bash (und andere Shells, die Cstrings auf ähnliche Weise verwenden), wenn die Eingabe Nullen enthält.
Chris Down
9

Wenn Sie mehrere Kerne haben und möchten, dass die Prozesse parallel ablaufen, können Sie Folgendes tun:

parallel -j 3 -- './app | grep A > A.out' './app | grep B > B.out' './app | grep C > C.out'

Dadurch werden drei Prozesse in parallelen Kernen erzeugt. Wenn eine Ausgabe auf der Konsole oder in einer Masterdatei erfolgen soll, hat dies den Vorteil, dass die Ausgabe in einer bestimmten Reihenfolge bleibt und nicht gemischt wird.

Das Dienstprogramm gnu parallel von Ole Tange kann von den meisten Repos unter dem Namen parallel oder moreutils bezogen werden . Die Quelle kann von Savannah.gnu.org bezogen werden . Auch ein Einführungsvideo ist hier .

Nachtrag

Mit der neueren Version von parallel (nicht unbedingt der Version in Ihrem Distributions-Repository) können Sie das elegantere Konstrukt verwenden:

./app | parallel -j3 -k --pipe 'grep {1} >> {1}.log' ::: 'A' 'B' 'C'

Dies führt dazu, dass ein ./app- und drei parallele grep-Prozesse in getrennten Kernen oder Threads ausgeführt werden (wie durch parallel selbst bestimmt, betrachten Sie das -j3 ebenfalls als optional, es wird jedoch in diesem Beispiel zu Anleitungszwecken bereitgestellt).

Die neuere Version von parallel erhalten Sie wie folgt:

wget http://ftpmirror.gnu.org/parallel/parallel-20131022.tar.bz2

Dann das übliche entpacken, cd to parallel- {date}, ./configure && make, sudo make installieren. Dadurch werden parallel, man page parallel und man page parallel_tutorial installiert.

AsymLabs
quelle
7

Hier ist eine in Perl:

./app | perl -ne 'BEGIN {open(FDA, ">A.out") and 
                         open(FDB, ">B.out") and 
                         open(FDC, ">C.out") or die("Cannot open files: $!\n")} 
                  print FDA $_ if /A/; print FDB $_ if /B/; print FDC $_ if /C/'
troydj
quelle
1
sed -ne/A/w\ A.out -e/B/w\ B.out -e/C/p <in >C.out

... wenn <inlesbar, werden alle drei Outfiles abgeschnitten, bevor irgendetwas darauf geschrieben wird.

mikeserv
quelle