Jak mogę wysłać standardowe wyjście do wielu poleceń?


186

Są pewne polecenia, które filtrują lub działają na wejściu, a następnie przekazują je jako dane wyjściowe, myślę, że zwykle stdout- ale niektóre polecenia po prostu biorą stdini robią to, co z nimi robią, i nie generują nic.

Najbardziej znam się na systemie OS X, dlatego od razu przychodzą mi na myśl dwa pbcopyi pbpaste- które umożliwiają dostęp do schowka systemowego.

W każdym razie wiem, że jeśli chcę wziąć standardowe wyjście i wypluć dane wyjściowe, aby przejść zarówno stdoutdo pliku, jak i do pliku, mogę użyć teepolecenia. I wiem trochę xargs, ale nie sądzę, że tego właśnie szukam.

Chcę wiedzieć, jak mogę podzielić, stdoutaby przejść między dwoma (lub więcej) poleceniami. Na przykład:

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

Prawdopodobnie jest lepszy przykład niż ten, ale naprawdę interesuje mnie, jak mogę wysłać polecenie stdout do polecenia, które go nie przekazuje, i jednocześnie powstrzymując się stdoutod „wyciszenia” - nie pytam o to, jak catplik i grepczęść i skopiuj go do schowka - określone polecenia nie są tak ważne.

Ponadto - nie pytam, jak wysłać to do pliku i stdout- może to być pytanie „duplikowane” (przepraszam), ale szukałem i mogłem znaleźć tylko te podobne, które pytały o sposób podziału między stdout a plikiem - i wydawało się tee, że odpowiedzi na te pytania są dla mnie nieskuteczne.

Na koniec możesz zapytać „dlaczego nie uczynić pbcopy ostatnią rzeczą w łańcuchu rur?” a moja odpowiedź brzmi: 1) co jeśli chcę go użyć i nadal widzieć dane wyjściowe w konsoli? 2) co jeśli chcę użyć dwóch poleceń, które nie są wypisywane stdoutpo przetworzeniu danych wejściowych?

Aha, i jeszcze jedno - zdaję sobie sprawę, że mógłbym użyć teei nazwaną potok ( mkfifo), ale liczyłem na sposób, w jaki można to zrobić inline, zwięźle, bez wcześniejszej konfiguracji :)


Odpowiedzi:


239

Możesz użyć teei przetworzyć substytucję:

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

Spowoduje to wysłanie wszystkich danych wyjściowych cat file.txtdo pbcopy, a wynik zostanie wyświetlony tylko grepna konsoli.

W części można umieścić wiele procesów tee:

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

21
Nie troska o pbcopy, ale warto wspomnieć w ogóle: cokolwiek wyjścia podmiany proces jest również postrzegane przez następnego odcinka rury, po pierwotnym wejściem; np .: seq 3 | tee >(cat -n) | cat -e( cat -nnumeruje linie wejściowe, cat -ezaznacza nowe linie za pomocą $; zobaczysz, że cat -ejest to stosowane zarówno do oryginalnego wejścia (najpierw), jak i (następnie) wyjścia z cat -n). Dane wyjściowe z wielu zastąpień procesów będą dostarczane w niedeterministycznej kolejności.
mklement0

49
>(Tylko działa bash. Jeśli spróbujesz użyć tego na przykład, shto nie zadziała. Ważne jest, aby to zrobić.
AAlvz

10
@Alvz: Dobra uwaga: podstawianie procesów nie jest funkcją POSIX; dash, który działa jak shw systemie Ubuntu, nie obsługuje go, a nawet sama Bash dezaktywuje tę funkcję, gdy jest wywoływana w shtrakcie lub w trakcie set -o posixdziałania. Jednak nie tylko Bash obsługuje podstawienia procesów: kshi zshwspiera je także (nie jestem pewien co do innych).
mklement0

2
@ mklement0, który nie wydaje się być prawdą. Na zsh (Ubuntu 14.04) twoja linia drukuje: 1 1 2 2 3 3 1 $ 2 $ 3 $ To smutne, ponieważ naprawdę chciałem, aby ta funkcjonalność była taka, jak mówisz.
Aktau

2
@Aktau: Rzeczywiście, moje przykładowe polecenie działa tylko tak, jak opisano w - bashi najwyraźniej nie wysyła danych wyjściowych z podstawień procesów wyjściowych przez potok (prawdopodobnie jest to preferowane , ponieważ nie zanieczyszcza tego, co jest wysyłane do następnego segmentu potoku - chociaż nadal drukuje ). Jednak we wszystkich wspomnianych powłokach generalnie nie jest dobrym pomysłem posiadanie jednego potoku, w którym mieszane są standardowe wyjścia standardowe i wyjściowe z podstawień procesów - kolejność wyjściowa nie będzie przewidywalna, w sposób, który może pojawiać się rzadko lub z dużą zestawy danych wyjściowych. kshzsh
mklement0

124

Możesz podać wiele nazw plików tee, a ponadto standardowe wyjście można połączyć w jedno polecenie. Aby wysłać dane wyjściowe do wielu poleceń, musisz utworzyć wiele potoków i określić każde z nich jako jedno wyjście tee. Można to zrobić na kilka sposobów.

Zastąpienie procesu

Jeśli twoją powłoką jest ksh93, bash lub zsh, możesz użyć podstawienia procesu. Jest to sposób na przekazanie potoku do polecenia, które oczekuje nazwy pliku. Powłoka tworzy potok i przekazuje nazwę pliku podobną /dev/fd/3do polecenia. Liczba jest deskryptorem pliku , do którego podłączony jest potok. Niektóre warianty uniksowe nie obsługują /dev/fd; na nich używany jest nazwany potok (patrz poniżej).

tee >(command1) >(command2) | command3

Deskryptory plików

W dowolnej powłoce POSIX można jawnie używać wielu deskryptorów plików . Wymaga to obsługiwanego wariantu unix /dev/fd, ponieważ wszystkie wyjścia oprócz jednego teemuszą być określone według nazwy.

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

Nazwane rury

Najbardziej podstawową i przenośną metodą jest użycie nazwanych potoków . Minusem jest to, że musisz znaleźć katalog do zapisu, utworzyć potoki, a następnie wyczyścić.

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
Bardzo dziękuję za udostępnienie dwóch alternatywnych wersji dla tych, którzy nie chcą polegać na bash lub pewnym ksh.
trr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3powinieneś na pewno być command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", jak chcesz stdout z command3rurami tee, nie? Przetestowałem twoją wersję pod dashi teeblokuje się w nieskończoność, czekając na dane wejściowe, ale zmiana kolejności dała oczekiwany rezultat.
Adrian Günter,

1
@ AdrianGünter Nr Wszystkie trzy przykłady odczytu danych ze standardowego wejścia i wysłać go do każdego command, command2i command3.
Gilles

@Gilles Widzę, źle zinterpretowałem zamiar i próbowałem nieprawidłowo użyć tego fragmentu. Dziękuję za wyjaśnienie!
Adrian Günter,

Jeśli nie masz kontroli nad używaną powłoką, ale możesz użyć bash jawnie, możesz to zrobić <command> | bash -c 'tee >(command1) >(command2) | command3'. Pomogło w moim przypadku.
gc5

16

Po prostu graj z zastępowaniem procesów.

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

grepsą dwoma plikami binarnymi, które mają takie same dane wyjściowe mycommand_execjak dane wejściowe specyficzne dla procesu.


16

Jeśli używasz zsh, możesz skorzystać z możliwości MULTIOSfunkcji, tzn. teeCałkowicie pozbyć się poleceń:

uname >file1 >file2

zapisze dane wyjściowe z unamedwóch różnych plików: file1i file2, co jest równoważneuname | tee file1 >file2

Podobnie przekierowanie standardowych wejść

wc -l <file1 <file2

jest równoważne cat file1 file2 | wc -l(należy pamiętać, że nie jest to to samo wc -l file1 file2, co później, zlicza osobno liczbę linii w każdym pliku).

Oczywiście można również użyć MULTIOSdo przekierowania danych wyjściowych nie do plików, ale do innych procesów, stosując podstawianie procesów, np .:

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

3
Dobrze wiedzieć. MULTIOSjest opcją domyślnie WŁĄCZONĄ (i można ją wyłączyć za pomocą unsetopt MULTIOS).
mklement0

6

W przypadku stosunkowo niewielkich danych wyjściowych wygenerowanych przez polecenie możemy przekierować dane wyjściowe do pliku tymczasowego i przesłać ten plik tymczasowy do poleceń w pętli. Może to być przydatne, gdy kolejność wykonywanych poleceń może mieć znaczenie.

Na przykład mógłby to zrobić następujący skrypt:

#!/bin/sh

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

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

Test działa na Ubuntu 16.04 z /bin/shjak dashskorupy:

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

5

Przechwytuj polecenie STDOUTdo zmiennej i ponownie używaj jej tyle razy, ile chcesz:

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

Jeśli też chcesz przechwycić STDERR, użyj 2>&1na końcu polecenia, tak jak:

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

3
Gdzie są przechowywane zmienne? Jeśli miałbyś do czynienia z dużym plikiem lub czymś takim, czy nie pochłonęło by to dużo pamięci? Czy zmienne mają ograniczony rozmiar?
cwd

1
co jeśli $ Commandoutput jest ogromny? lepiej użyć potoków i podstawiania procesów.
Nikhil Mulley,

4
Oczywiście to rozwiązanie jest możliwe tylko wtedy, gdy wiesz, że rozmiar danych wyjściowych z łatwością zmieści się w pamięci, i że możesz buforować całe dane wyjściowe przed uruchomieniem na nim kolejnych poleceń. Rury rozwiązują te dwa problemy, umożliwiając dane o dowolnej długości i przesyłając je w czasie rzeczywistym do odbiornika podczas jego generowania.
trr

2
To dobre rozwiązanie, jeśli masz niewielki wynik i wiesz, że wynik będzie tekstowy, a nie binarny. (zmienne powłoki często nie są bezpieczne binarnie)
Rucent88

1
Nie mogę tego zrobić z danymi binarnymi. Myślę, że jest to coś z echa próbującego zinterpretować bajty zerowe lub jakieś inne nieznakowe dane.
Rolf


0

Oto szybkie i brudne rozwiązanie częściowe, kompatybilne z dowolną powłoką, w tym busybox.

Węższy problem, który rozwiązuje to: wydrukuj cały zestaw stdoutna jednej konsoli i przefiltruj na innym, bez plików tymczasowych lub nazwanych potoków.

  • Rozpocznij kolejną sesję na tym samym hoście. Aby znaleźć nazwę TTY, wpisz tty. Załóżmy /dev/pty/2.
  • W pierwszej sesji uruchom the_program | tee /dev/pty/2 | grep ImportantLog:

Otrzymasz jeden kompletny dziennik i jeden filtrowany.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.