Zastąpienie procesu i rura

86

Zastanawiałem się, jak zrozumieć następujące kwestie:

Przełożenie standardowej komendy na standardową kolejną jest potężną techniką. Ale co, jeśli chcesz przesłać standardowe sekwencje wielu poleceń? W tym miejscu pojawia się podstawienie procesu.

Innymi słowy, czy podstawienie procesu może zrobić wszystko, co może zrobić potok?

Co może zrobić proces podstawiania, ale potok nie?

Tim
źródło

Odpowiedzi:

133

Dobrym sposobem na sprawdzenie różnicy między nimi jest przeprowadzenie małego eksperymentu w wierszu poleceń. Pomimo wizualnego podobieństwa w użyciu <postaci, robi ona coś zupełnie innego niż przekierowanie lub potok.

Użyjmy datepolecenia do testowania.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Jest to bezcelowy przykład, ale pokazuje, że catzaakceptował wyjście dateSTDIN i wypluł je z powrotem. Te same wyniki można osiągnąć przez podstawienie procesu:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Jednak to, co stało się za kulisami, było inne. Zamiast otrzymać strumień STDIN, catfaktycznie przekazano nazwę pliku, który musiał otworzyć i odczytać. Możesz zobaczyć ten krok, używając echozamiast cat.

$ echo <(date)
/proc/self/fd/11

Gdy kot otrzymał nazwę pliku, odczytał dla nas zawartość pliku. Z drugiej strony echo po prostu pokazało nam nazwę pliku, który został przekazany. Ta różnica staje się bardziej oczywista, jeśli dodasz więcej podstawień:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Możliwe jest połączenie podstawienia procesu (który generuje plik) i przekierowania wejściowego (które łączy plik ze STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Wygląda prawie tak samo, ale tym razem kotowi przekazano strumień STDIN zamiast nazwy pliku. Możesz to zobaczyć, wypróbowując to za pomocą echa:

$ echo < <(date)
<blank>

Ponieważ echo nie odczytuje STDIN i nie przekazano żadnego argumentu, nic nie otrzymujemy.

Potoki i dane wejściowe przekierowują zawartość wtykową do strumienia STDIN. Podstawianie procesów uruchamia polecenia, zapisuje ich dane wyjściowe w specjalnym pliku tymczasowym, a następnie przekazuje tę nazwę pliku zamiast polecenia. Każde używane polecenie traktuje to jako nazwę pliku. Zauważ, że utworzony plik nie jest zwykłym plikiem, ale nazwanym potokiem, który jest automatycznie usuwany, gdy nie jest już potrzebny.

Caleb
źródło
Jeśli dobrze zrozumiałem, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 mówi , że podstawienie procesu tworzy pliki tymczasowe, a nie nazwane potoki. O ile mi wiadomo, nie twórz plików tymczasowych. Zapisywanie do potoku nigdy nie wymaga zapisywania na dysku: stackoverflow.com/a/6977599/788700
Adobe
Wiem, że ta odpowiedź jest uzasadniona, ponieważ używa słowa grok : D
aqn
2
@Adobe można potwierdzić, czy tymczasowe zastąpienie proces produkuje plik jest nazwany potok z: [[ -p <(date) ]] && echo true. Powoduje to, truegdy uruchamiam go z bash 4.4 lub 3.2.
De Novo
24

Przypuszczam, że mówisz o bashjakiejś innej zaawansowanej powłoce, ponieważ powłoka posix nie ma substytucji procesu .

bash raporty strony podręcznika:

Podstawianie procesów Podstawianie
procesów jest obsługiwane w systemach obsługujących nazwane potoki (FIFO) lub metodę / dev / fd nazywania otwartych plików. Ma postać <(lista) lub> (lista). Lista procesów jest uruchamiana z wejściem lub wyjściem podłączonym do FIFO lub jakiegoś pliku w / dev / fd. W wyniku rozszerzenia nazwa tego pliku jest przekazywana jako argument do bieżącego polecenia. Jeśli używany jest formularz> (lista), zapis do pliku zapewni dane wejściowe dla listy. Jeśli używana jest forma <(lista), plik przekazany jako argument powinien zostać odczytany, aby uzyskać wynik listy.

Jeśli jest dostępna, podstawianie procesów odbywa się jednocześnie z rozszerzaniem parametrów i zmiennych, podstawianiem poleceń i rozszerzaniem arytmetycznym.

Innymi słowy i z praktycznego punktu widzenia możesz użyć wyrażenia takiego jak poniżej

<(commands)

jako nazwa pliku dla innych poleceń wymagających pliku jako parametru. Lub możesz użyć przekierowania dla takiego pliku:

while read line; do something; done < <(commands)

Wracając do twojego pytania, wydaje mi się, że podstawianie procesów i rury nie mają ze sobą wiele wspólnego.

Jeśli chcesz przesyłać sekwencyjnie dane wyjściowe wielu poleceń, możesz użyć jednej z następujących form:

(command1; command2) | command3
{ command1; command2; } | command3

ale możesz również użyć przekierowania podczas zastępowania procesu

command3 < <(command1; command2)

na koniec, jeśli command3zaakceptuje parametr pliku (w zamian stdin)

command3 <(command1; command2)
enzotib
źródło
więc <() i <<() dają ten sam efekt, prawda?
solfish,
@solfish: not exacllty: firse może być używane wszędzie tam, gdzie oczekiwana jest nazwa pliku, druga to przekierowanie wejściowe dla tej nazwy pliku
enzotib
23

Oto trzy rzeczy, które możesz zrobić z podstawieniem procesu, które w przeciwnym razie są niemożliwe.

Wiele wejść procesowych

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Po prostu nie da się tego zrobić za pomocą rur.

Zachowanie STDIN

Powiedz, że masz następujące elementy:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

I chcesz uruchomić go bezpośrednio. Następujące zawodzi żałośnie. Bash już używa STDIN do odczytu skryptu, więc inne dane wejściowe są niemożliwe.

curl -o - http://example.com/script.sh | bash 

Ale ten sposób działa idealnie.

bash <(curl -o - http://example.com/script.sh)

Zastąpienie procesu wychodzącego

Należy również pamiętać, że podstawianie procesów działa również w drugą stronę. Możesz więc zrobić coś takiego:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

To trochę skomplikowany przykład, ale wysyła stdout do /dev/null, jednocześnie przesyłając stderr do skryptu sed, aby wyodrębnić nazwy plików, dla których wyświetlony został błąd „Odmowa zezwolenia”, a następnie wysyła wyniki THOSE do pliku.

Należy pamiętać, że pierwsze polecenie i stdout przekierowanie jest w nawiasie ( podpowłoce ), tak, że tylko wynik tego polecenia zostanie wysłana do /dev/nulli nie bałagan z resztą linii.

tylerl
źródło
Warto zauważyć, że w diffprzykładzie warto dbać o przypadku, kiedy cdmoże zakończyć się niepowodzeniem: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk
„while piping stderr”: czy nie chodzi o to, że to nie pipowanie, ale przechodzenie przez plik FIFO?
Gauthier,
@Gauthier no; polecenie zostaje podstawione nie przez fifo, ale przez odwołanie do deskryptora pliku. Zatem „echo <(echo)” powinno dać coś w rodzaju „/ dev / fd / 63”, czyli specjalnego urządzenia znakowego, które czyta lub pisze z FD numer 63.
tylerl
10

Jeśli polecenie pobiera listę plików jako argumenty i przetwarza te pliki jako dane wejściowe (lub wyjściowe, ale niezbyt często), każdy z tych plików może być nazwanym potokiem lub pseudo-plikiem / dev / fd zapewniany w sposób przezroczysty przez substytucję procesu:

$ sort -m <(command1) <(command2) <(command3)

Spowoduje to „potokowanie” danych wyjściowych trzech poleceń do sortowania, ponieważ sortowanie może zająć listę plików wejściowych w wierszu polecenia.

camh
źródło
1
IIRC składnia <(polecenie) jest funkcją tylko bash.
Philomath
@Philomath: To też jest w ZSH.
Caleb
Cóż, ZSH ma wszystko ... (a przynajmniej próbuje).
Philomath
@Philomath: Jak zaimplementowane jest zastępowanie procesów w innych powłokach?
camh
4
@Filomath <(), podobnie jak wiele zaawansowanych funkcji powłoki, był pierwotnie funkcją ksh i został przyjęty przez bash i zsh. psubjest szczególnie cechą ryb, nie ma nic wspólnego z POSIX.
Gilles
3

Należy zauważyć, że podstawienie procesu nie ogranicza się do formy <(command), która wykorzystuje dane wyjściowe commandjako plik. Może być również w formie, >(command)która podaje plik jako dane wejściowe command. Jest to również wspomniane w cytacie podręcznika bash w odpowiedzi na @ enzotib.

W date | catpowyższym przykładzie polecenie, które korzysta z substytucji procesu w >(command)celu uzyskania tego samego efektu, brzmiałoby:

date > >(cat)

Pamiętaj, że >wcześniejsze >(cat)jest konieczne. Można to ponownie wyraźnie zilustrować za pomocą echoodpowiedzi @ Caleb.

$ echo >(cat)
/dev/fd/63

Tak więc, bez dodatkowych >, date >(cat)byłoby to to samo, date /dev/fd/63co wypisze wiadomość do stderr.

Załóżmy, że masz program, który bierze nazwy plików tylko jako parametry i nie przetwarza stdinlub stdout. Wykorzystam uproszczony skrypt, psub.shaby to zilustrować. Zawartość psub.shjest

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Zasadniczo sprawdza, czy oba jego argumenty są plikami (niekoniecznie zwykłymi plikami), a jeśli tak jest, napisz pierwsze pole każdej linii "$1"do "$2"użycia awk. Następnie polecenie, które łączy wszystkie wspomniane do tej pory, to:

./psub.sh <(printf "a a\nc c\nb b") >(sort)

To zostanie wydrukowane

a
b
c

i jest równoważne z

printf "a a\nc c\nb b" | awk '{print $1}' | sort

ale następujące działania nie będą działać i musimy tutaj zastosować podstawianie procesów,

printf "a a\nc c\nb b" | ./psub.sh | sort

lub jego odpowiednik

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Jeśli ./psub.shodczytuje również stdinpoza tym, co wspomniano powyżej, to taka równoważna forma nie istnieje, aw takim przypadku nie możemy nic użyć zamiast zastępowania procesu (oczywiście możesz również użyć nazwanego pliku potoku lub pliku tymczasowego, ale to inny fabuła).

Weijun Zhou
źródło