Potok do wielu plików w powłoce

29

Mam aplikację, która wygeneruje dużą ilość danych, których nie chcę przechowywać na dysku. Aplikacja generuje głównie dane, których nie chcę używać, ale zestaw przydatnych informacji, które należy podzielić na osobne pliki. Na przykład biorąc pod uwagę następujące dane wyjściowe:

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

Mógłbym uruchomić aplikację trzy razy tak:

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

To dałoby mi to, czego chcę, ale zajęłoby to zbyt długo. Nie chcę też zrzucać wszystkich danych wyjściowych do jednego pliku i analizować przez to.

Czy jest jakiś sposób na połączenie trzech powyższych operacji w taki sposób, że muszę uruchomić aplikację tylko raz i nadal uzyskać trzy osobne pliki wyjściowe?

sj755
źródło

Odpowiedzi:

78

Jeśli masz koszulkę

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

( stąd )

( o zastępowaniu procesów )

Aurélien Ooms
źródło
4
Niesamowite, można to również ./app | tee >(grep A > A.out) >(grep B > B.out) | grep C > C.out
wytłumaczyć
7
Ta odpowiedź jest obecnie jedyną prawidłową, biorąc pod uwagę oryginalny tytuł pytania „potok do wielu procesów”.
acelent
3
+1. Jest to najczęściej stosowana odpowiedź, ponieważ nie zależy ona od tego, czy konkretna komenda filtrowania była grep.
ruakh
1
Zgodziłbym się, że jest to najlepsza odpowiedź na postawione pytanie i należy ją zaznaczyć. Równoległe jest innym rozwiązaniem (jak opublikowano), ale po przeprowadzeniu porównań czasowych powyższy przykład jest bardziej wydajny. Jeśli zamiast tego operacja wymagała intensywnych procesorów, takich jak kompresja wielu plików lub konwersja wielu plików mp3, bez wątpienia rozwiązanie równoległe powinno okazać się bardziej skuteczne.
AsymLabs,
32

Możesz użyć awk

./app | awk '/A/{ print > "A.out"}; /B/{ print > "B.out"}; /C/{ print > "C.out"}'
Rahul Patil
źródło
6
Tytuł pytania brzmi: potok do wielu procesów , ta odpowiedź dotyczy „potokowania” (wysyłania przez wyrażenie regularne) do wielu plików . Ponieważ odpowiedź została zaakceptowana, tytuł pytania należy odpowiednio zmienić.
acelent
@PauloMadeira Masz rację. Jak myślisz, jaki byłby lepszy tytuł?
sj755,
Zasugerowałem bardzo małą edycję „Pipe do wielu plików w powłoce”, oczekuje na rewizję, sprawdź to. Spodziewałem się usunąć komentarz, jeśli zostanie zaakceptowany.
acelent
@PauloMadeira - Zmieniłem tytuł. Nie widziałem Twojej edycji, ale masz rację, użycie procesów w tytule było niepoprawne, jeśli jest to zaakceptowana odpowiedź.
slm
17

Możesz także użyć zdolności dopasowywania wzoru powłoki :

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

Lub nawet:

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

Bezpieczniejszy sposób radzenia sobie z odwrotnymi ukośnikami i liniami, zaczynając od -:

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

Jak wskazuje @StephaneChazelas w komentarzach, nie jest to zbyt wydajne. Najlepszym rozwiązaniem jest prawdopodobnie @ AurélienOoms ' .

terdon
źródło
Który zakłada, że wejście nie zawiera ukośników ani spacji ani znaków wieloznacznych lub wiersze, które zaczynają się -n, -e... to też będzie strasznie nieskuteczny jako Oznacza to kilka wywołań systemowych na linię (jeden read(2)na jeden znak, plik jest otwarty, pisanie zamknięte dla każdej linii ...). Ogólnie rzecz biorąc, używanie while readpętli do przetwarzania tekstu w powłokach jest złą praktyką.
Stéphane Chazelas
@StephaneChazelas Zredagowałem swoją odpowiedź. Powinno teraz działać z odwrotnymi ukośnikami i -ntym podobne. O ile mogę stwierdzić, że obie wersje działają poprawnie z odstępami, czy się mylę?
terdon
Nie, pierwszym argumentem printfjest format. Nie ma powodu, aby pozostawić tam zmienne niewymienione.
Stéphane Chazelas
Spowoduje to również przerwanie bash (i innych powłok, które używają łańcuchów w podobny sposób), jeśli na wejściu są wartości null.
Chris Down
9

Jeśli masz wiele rdzeni i chcesz, aby procesy były równoległe, możesz:

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

Spowoduje to odrodzenie trzech procesów w równoległych rdzeniach. Jeśli chcesz mieć jakieś wyjście na konsolę lub plik główny, ma tę zaletę, że utrzymuje wyjście w pewnej kolejności, a nie miksuje.

Narzędzie GNU równoległe z Ole Tange można uzyskać z większości repozytoriów pod nazwą równolegle lub moreutils . Źródło można uzyskać z Savannah.gnu.org . Również wprowadzenie film instruktażowy jest tutaj .

Uzupełnienie

Korzystając z nowszej wersji programu równoległego (niekoniecznie wersji w repozytorium dystrybucji), możesz użyć bardziej eleganckiej konstrukcji:

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

Który osiąga wynik uruchomienia jednego ./app i 3 równoległych procesów grep w oddzielnych rdzeniach lub wątkach (jak określono przez sam równoległy, również rozważ -j3 jako opcjonalny, ale podano go w tym przykładzie w celach instruktażowych).

Nowszą wersję programu równoległego można uzyskać, wykonując:

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

Następnie zwykłe rozpakowywanie, cd na równolegle- {data}, ./configure && make, sudo make install. Spowoduje to zainstalowanie równoległego, strony podręcznika równoległego i strony podręcznika równoległego.

AsymLabs
źródło
7

Oto jeden w Perlu:

./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
źródło
1
sed -ne/A/w\ A.out -e/B/w\ B.out -e/C/p <in >C.out

... jeśli <injest czytelny, wszystkie trzy pliki wyjściowe zostaną obcięte, zanim cokolwiek zostanie do nich zapisane.

mikeserv
źródło