Zastanawiałem się, jak zrozumieć następujące kwestie:
Przesunięcie standardowego wyjścia polecenia do wejścia standardowego innego jest potężną techniką. Ale co, jeśli chcesz potokować standardowe wyjście wielu poleceń? I tu właśnie pojawia się podstawianie procesów.
Innymi słowy, czy podstawianie procesów może zrobić wszystko, co potok?
Co może zrobić podstawianie procesu, ale potok nie może?
Odpowiedź
Dobry sposób na zrozumienie różnica między nimi polega na wykonaniu małego eksperymentu w wierszu poleceń. Pomimo wizualnego podobieństwa w użyciu znaku <
, robi coś zupełnie innego niż przekierowanie lub kreska.
Użyjmy date
polecenie do testowania.
$ date | cat Thu Jul 21 12:39:18 EEST 2011
To bezsensowny przykład, ale pokazuje, że cat
zaakceptował wyjście date
na STDIN 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 wydarzyło się za kulisami, było inne. Zamiast otrzymać strumień STDIN, do cat
przekazano nazwę pliku, który musiał zostać otwarty i przeczytaj. Możesz zobaczyć ten krok, używając echo
zamiast cat
.
$ echo <(date) /proc/self/fd/11
Gdy cat otrzymał nazwę pliku, odczytywał za nas zawartość pliku. Z drugiej strony, echo właśnie pokazało nam nazwę pliku, który został przekazany. Ta różnica stanie 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
To można łączyć podstawianie procesu (co generuje plik) i przekierowanie wejścia (co łączy plik ze STDIN):
$ cat < <(date) Thu Jul 21 12:46:22 EEST 2011
Wygląda prawie tak samo, ale tym razem do cat został przekazany strumień STDIN zamiast nazwy pliku. Możesz to zobaczyć, próbując to z echo:
$ echo < <(date) <blank>
Ponieważ echo nie czyta STDIN i żaden argument nie został przekazany, nic nie otrzymujemy.
Potoki i przekierowania wejściowe wypychają zawartość do strumienia STDIN. Podstawianie procesu uruchamia polecenia, zapisuje ich dane wyjściowe w specjalnym pliku tymczasowym, a następnie przekazuje tę nazwę pliku w miejsce polecenia. Jakiekolwiek polecenie, którego używasz, traktuje je jako nazwę pliku. Zauważ, że utworzony plik nie jest zwykłym plikiem, ale nazwanym potokiem, który jest usuwany automatycznie, gdy nie jest już potrzebny.
Komentarze
Odpowiedź
Oto trzy rzeczy, które możesz zrobić z podstawianiem procesów, które w innym przypadku są niemożliwe.
Wiele wejść procesu
diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)
Tam po prostu nie da się tego zrobić za pomocą potoków.
Zachowanie STDIN
Powiedzmy, że masz:
curl -o - http://example.com/script.sh #/bin/bash read LINE echo "You said ${LINE}!"
I chcesz go uruchomić bezpośrednio. Następujące zawodzi żałośnie. Bash używa już 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 doskonale.
bash <(curl -o - http://example.com/script.sh)
Podstawianie procesów wychodzących
Należy również zauważyć, ż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ę zawiły przykład, ale wysyła stdout do /dev/null
, jednocześnie przesyłając stderr do skryptu seda w celu wyodrębnienia nazw plików, dla których ” Odmowa uprawnień ” błąd, a następnie wysyła TE wyniki do pliku.
Zwróć uwagę, że pierwsze polecenie i przekierowanie stdout są w nawiasach ( podpowłoka ) tak, że tylko wynik tego polecenia zostanie wysłany do /dev/null
i nie zadziała z resztą wiersza.
Komentarze
- Warto ' zauważyć, że w
diff
przykład, warto zająć się przypadkiem, w którymcd
może zawieść:diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls)
. - ” podczas przetwarzania stderr „: isn ' t zwróć uwagę, że to nie potok, ale przechodzenie przez plik FIFO?
- @Gauthier no; polecenie zostanie zastąpione nie przez kolejkę FIFO, ale przez odniesienie do deskryptora pliku. Więc ” echo < (echo) ” powinno dać coś takiego jak ” / dev / fd / 63 „, czyli urządzenie znaków specjalnych, które czyta lub zapisuje z FD numer 63.
Odpowiedź
Przypuszczam, że mówisz o bash
lub innej zaawansowanej powłoce, ponieważ posix powłoka nie ma podstawiania procesów .
bash
raporty stron 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. Przybiera 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 rozwinięcia nazwa tego pliku jest przekazywana jako argument do aktualnego polecenia. Jeśli używana jest forma> (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ępny, podstawianie procesu jest wykonywana jednocześnie z interpretacją parametrów i zmiennych, podstawianiem poleceń i interpretacją wyrażeń arytmetycznych.
Innymi słowy, z praktycznego punktu widzenia, możesz użyć wyrażenie takie jak poniższe
<(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 pytania, wydaje mi się, że podstawianie procesów i potoki nie mają wiele wspólnego.
Jeśli chcesz potokować sekwencyjnie wyjście wielu poleceń, możesz użyć jednej z następujących form:
(command1; command2) | command3 { command1; command2; } | command3
ale może również użyć przekierowania przy podstawianiu procesu
command3 < <(command1; command2)
na koniec, jeśli command3
akceptuje parametr pliku (w zastępstwie stdin )
command3 <(command1; command2)
Komentarze
- so < ( ) i < < () daje ten sam efekt, prawda?
- @solfish: not exacllty: the first can być używane wszędzie tam, gdzie oczekiwana jest nazwa pliku, druga to przekierowanie wejściowe dla tej nazwy pliku
Odpowiedź
Jeśli polecenie przyjmuje listę plików jako argumenty i przetwarza te pliki jako dane wejściowe (lub dane wyjściowe, ale rzadko), każdy z tych plików może być nazwanym potokiem lub pseudo-plikiem / dev / fd dostarczanym w sposób przezroczysty przez podstawianie procesu:
$ sort -m <(command1) <(command2) <(command3)
To potokuje dane wyjściowe trzech poleceń do sortowania, ponieważ sort może pobrać listę plików wejściowych w wierszu poleceń.
Komentarze
- IIRC składnia < (polecenia) jest funkcją tylko dla basha.
- @Philomath: It ' jest w ZSH też.
- Cóż, ZSH ma wszystko … (lub przynajmniej próbuje).
- @Philomath: Jak zaimplementowano podstawianie procesów w innych powłokach?
- @Philomath
<()
, podobnie jak wiele zaawansowanych funkcji powłoki, pierwotnie był funkcją ksh i został przyjęty przez bash i zsh.psub
to konkretnie funkcja ryb, nie ma to nic wspólnego z POSIX.
Odpowiedź
Należy zauważyć, że podstawianie procesu nie jest ograniczone do postaci <(command)
, która używa wyniku command
jako plik. Może mieć postać >(command)
, która również dostarcza plik jako dane wejściowe do command
. Jest to również wspomniane w cytacie z podręcznika bash w odpowiedzi @enzotib.
W powyższym przykładzie date | cat
, polecenie, które używa podstawienia procesu form >(command)
, aby osiągnąć ten sam efekt, byłoby
date > >(cat)
Zwróć uwagę, że >
przed >(cat)
jest konieczne. Można to ponownie jasno zilustrować za pomocą echo
, jak w odpowiedzi @Caleb.
$ echo >(cat) /dev/fd/63
Zatem bez dodatkowego >
date >(cat)
byłoby to samo co date /dev/fd/63
, który wypisze komunikat na stderr.
Załóżmy, że masz program, który przyjmuje tylko nazwy plików jako parametry i nie przetwarza stdin
lub stdout
.Aby to zilustrować, użyję nadmiernie uproszczonego skryptu psub.sh
. Treść psub.sh
to
#!/bin/bash [ -e "$1" -a -e "$2" ] && awk "{print $1}" "$1" > "$2"
Zasadniczo sprawdza, czy oba argumenty są plikami (niekoniecznie zwykłymi plików) i jeśli tak jest, zapisz pierwsze pole każdego wiersza "$1"
do "$2"
używając awk. Następnie polecenie, które łączy w sobie wszystko, co do tej pory zostało wspomniane, to
./psub.sh <(printf "a a\nc c\nb b") >(sort)
Spowoduje to wydrukowanie
a b c
i jest równoważne z
printf "a a\nc c\nb b" | awk "{print $1}" | sort
, ale poniższe nie będą działać i musimy tutaj użyć podstawiania 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.sh
również czyta stdin
poza tym, co zostało wspomniane powyżej, to taka równoważna forma nie istnieje iw takim przypadku nie ma nic, czego możemy użyć zamiast podstawienia procesu (oczywiście można też użyć nazwany potok lub plik tymczasowy, ale to już inna historia).
[[ -p <(date) ]] && echo true
. Daje totrue
, gdy uruchamiam go z bash 4.4 lub 3.2.