The
echo one; echo two > >(cat); echo three;
polecenie daje nieoczekiwany wynik.
Przeczytałem to: W jaki sposób podstawianie procesów jest realizowane w bash? i wiele innych artykułów na temat zastępowania procesów w Internecie, ale nie rozumiem, dlaczego tak się zachowuje.
Oczekiwany wynik:
one
two
three
Rzeczywista wydajność:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Te dwa polecenia powinny być równoważne z mojego punktu widzenia, ale nie są:
##### first command - the pipe is used.
prompt$ seq 1 5 | cat
1
2
3
4
5
##### second command - the process substitution and redirection are used.
prompt$ seq 1 5 > >(cat)
prompt$ 1
2
3
4
5
Dlaczego myślę, że powinny być takie same? Ponieważ oba łączą seq
wyjście z cat
wejściem przez anonimowy potok - Wikipedia, proces podstawiania .
Pytanie: Dlaczego tak się zachowuje? Gdzie jest mój błąd? Pożądana jest kompleksowa odpowiedź (wraz z wyjaśnieniem, jak bash
to zrobić pod maską).
bash
process-substitution
MiniMax
źródło
źródło
Odpowiedzi:
Tak,
bash
podobnie jak wksh
(skąd pochodzi funkcja), procesy wewnątrz substytucji procesu nie są czekane (przed uruchomieniem następnego polecenia w skrypcie).dla
<(...)
jednego, to zwykle w porządku, jak w:powłoka będzie na nią czekać
cmd1
icmd1
zwykle będzie na nią czekaćcmd2
, czytając ją do końca pliku na potoku, który jest podstawiany, a ten koniec pliku zwykle ma miejsce, gdycmd2
umiera. To z tego samego powodu kilka muszli (niebash
) nie przeszkadza czeka nacmd2
wcmd2 | cmd1
.Na
cmd1 >(cmd2)
ogół jednak tak nie jest, ponieważcmd2
zwykle więcej czeka nacmd1
niego, więc zazwyczaj kończy się później.Zostało to naprawione,
zsh
ponieważ czeka nacmd2
to (ale nie, jeśli napiszesz je jakocmd1 > >(cmd2)
icmd1
nie jest ono wbudowane, użyj{cmd1} > >(cmd2)
zamiast tego jak udokumentowano ).ksh
nie czeka domyślnie, ale pozwala poczekać na to zwait
wbudowanym (udostępnia również pid$!
, choć to nie pomaga, jeśli tak zrobiszcmd1 >(cmd2) >(cmd3)
)rc
(zecmd1 >{cmd2}
składnią), tak samo jakksh
z tym wyjątkiem, że można uzyskać pids wszystkich procesów w tle za pomocą$apids
.es
(również zcmd1 >{cmd2}
) czeka nacmd2
podobne wzsh
, a także czeka na przekierowaniacmd2
w<{cmd2}
procesie.bash
udostępnia pidcmd2
(lub dokładniej podpowłoki, ponieważ działacmd2
w procesie potomnym tej podpowłoki, mimo że jest to ostatnia tam dostępna komenda)$!
, ale nie pozwala na to czekać.Jeśli musisz użyć
bash
, możesz obejść problem za pomocą polecenia, które będzie czekać na oba polecenia za pomocą:To sprawia, że zarówno
cmd1
icmd2
mają fd 3 otwarte na rurze.cat
będzie czekać na EOF na drugim końcu, więc zazwyczaj tylko wyjść, gdy obacmd1
icmd2
są martwe. I powłoka będzie czekać na tocat
polecenie. Można to zobaczyć jako sieć przechwytującą zakończenie wszystkich procesów w tle (możesz użyć jej do innych rzeczy rozpoczętych w tle, takich jak&
, coprocs, a nawet polecenia, które same w tle, pod warunkiem, że nie zamykają wszystkich swoich deskryptorów plików, jak zwykle demony ).Zauważ, że dzięki wspomnianemu wyżej procesowi zmarnowanej podpowłoki działa, nawet jeśli
cmd2
zamyka swój fd 3 (polecenia zwykle tego nie robią, ale niektóre lubiąsudo
lubssh
robią). Przyszłe wersjebash
mogą ostatecznie przeprowadzić tam optymalizację, tak jak w innych powłokach. Wtedy potrzebujesz czegoś takiego:Aby upewnić się, że jest jeszcze dodatkowy proces powłoki z otwartym fd 3 czekającym na to
sudo
polecenie.Zauważ, że
cat
nic nie przeczyta (ponieważ procesy nie piszą na swoim fd 3). Jest tylko do synchronizacji. Wykona tylko jednoread()
wywołanie systemowe, które na końcu nie wróci.Można faktycznie uniknąć uruchamiania
cat
, używając synchronizacji komend do synchronizacji potoku:Tym razem to powłoka zamiast
cat
tego czyta z potoku, którego drugi koniec jest otwarty na fd 3 zcmd1
icmd2
. Używamy przypisania zmiennej, więc status wyjściacmd1
jest dostępny w$?
.Lub możesz ręcznie podstawić proces, a następnie możesz nawet użyć systemu,
sh
ponieważ stałoby się to standardową składnią powłoki:zauważ jednak, jak wspomniano wcześniej, że nie wszystkie
sh
implementacje będą czekaćcmd1
pocmd2
zakończeniu (choć jest to lepsze niż na odwrót). Ten czas$?
zawiera status wyjściacmd2
; Choćbash
andzsh
makecmd1
„s status wyjścia dostępne${PIPESTATUS[0]}
i$pipestatus[1]
odpowiednio (patrz równieżpipefail
opcję w kilku pocisków więc$?
może zgłosić awarię elementów rurowych inne niż ostatnio)Pamiętaj, że
yash
ma podobne problemy z funkcją przekierowywania procesów .cmd1 >(cmd2)
zostaniecmd1 /dev/fd/3 3>(cmd2)
tam napisane . Alecmd2
nie jest czekany i nie można teżwait
na niego czekać, a jego pid również nie jest dostępny w$!
zmiennej. Używałbyś tych samych obejść, co dlabash
.źródło
echo one; { { echo two > >(cat); } 3>&1 >&4 4>&- | cat; } 4>&1; echo three;
, a potem uprościłem goecho one; echo two > >(cat) | cat; echo three;
i wyświetla wartości we właściwej kolejności. Czy wszystkie te manipulacje deskryptorem3>&1 >&4 4>&-
są konieczne? Również tego nie rozumiem>&4 4>&
- następuje przekierowaniestdout
do czwartej fd, następnie zamykamy czwarty fd, a następnie ponownie4>&1
go używamy . Dlaczego to potrzebne i jak działa? Może powinienem utworzyć nowe pytanie na ten temat?cmd1
icmd2
punkt z małym tańca z deskryptora pliku jest przywrócenie oryginalnych i tylko przy użyciu dodatkowej rury dla czekając zamiast kierowania również wyjście poleceń.4>&1
tworzy deskryptor pliku (fd) 4 dla listy poleceń zewnętrznego nawiasu klamrowego i wyrównuje go do standardowego wyjścia nawiasów klamrowych. Wewnętrzne nawiasy klamrowe mają automatycznie skonfigurowane stdin / stdout / stderr, aby połączyć się z zewnętrznymi nawiasami klamrowymi. Jednak3>&1
sprawia , że fd 3 łączy się ze stdinem zewnętrznego nawiasu klamrowego.>&4
łączy stdout wewnętrznych nawiasów z zewnętrznymi nawiasami fd 4 (Ten, który wcześniej stworzyliśmy).4>&-
zamyka fd 4 z wewnętrznych nawiasów klamrowych (Ponieważ stdout wewnętrznych nawiasów klamrowych jest już podłączony do fd 4 nawiasów klamrowych).4>&1
jest wykonywana jako pierwsza, przed innymi przekierowaniami, więc nie „ponownie używasz4>&1
”. Ogólnie rzecz biorąc, wewnętrzne nawiasy klamrowe wysyłają dane do swojego standardowego wyjścia, które zostało zastąpione dowolnym podanym fd 4. Fd 4, które podano wewnętrznym nawiasom, to fd 4 nawiasów zewnętrznych, który jest równy pierwotnemu stopniowi nawiasów.4>5
oznacza „4 idzie do 5”, ale tak naprawdę „fd 4 jest nadpisywany przez fd 5”. I przed wykonaniem, fd 0/1/2 są automatycznie łączone (wraz z dowolnym fd zewnętrznej powłoki) i możesz je zastąpić, jak chcesz. To przynajmniej moja interpretacja dokumentacji bash. Jeśli zrozumiałeś z tego coś innego , lmk.Drugie polecenie możesz potokować do drugiego
cat
, co będzie czekać na zamknięcie jego potoku wejściowego. Dawny:Krótkie i proste.
==========
Choć wydaje się to proste, wiele dzieje się za kulisami. Możesz zignorować resztę odpowiedzi, jeśli nie jesteś zainteresowany tym, jak to działa.
Gdy masz
echo two > >(cat); echo three
,>(cat)
jest rozwidlony przez interaktywną powłokę i działa niezależnie odecho two
. W ten sposóbecho two
kończy się, a następnieecho three
zostaje wykonany, ale przed końcem>(cat)
. Kiedybash
pobiera dane z>(cat)
momentu, gdy się ich nie spodziewał (kilka milisekund później), daje to szybką sytuację, w której musisz nacisnąć nowy wiersz, aby wrócić do terminala (tak samo, jakby inny użytkownikmesg
cię edytował).Biorąc jednak pod uwagę
echo two > >(cat) | cat; echo three
, tworzone są dwie podpowłoki (zgodnie z dokumentacją|
symbolu).Jedna podpowłoka o nazwie A jest przeznaczona dla
echo two > >(cat)
, a jedna podpowłoka o nazwie B jest przeznaczona dlacat
. A jest automatycznie łączone z B (stdout A to stdin B). Następnieecho two
i>(cat)
rozpocznij wykonywanie.>(cat)
Stdout jest ustawiony na stdout A, który jest równy stdin B. Poecho two
zakończeniu A wychodzi, zamykając stdout. Jednak>(cat)
wciąż trzyma odniesienie do standardowego wejścia B.. Drugicat
stdin trzyma stdin B i tocat
nie wyjdzie, dopóki nie zobaczy EOF. EOF jest podawany tylko wtedy, gdy nikt nie ma już otwartego pliku w trybie zapisu, więc standardowe>(cat)
wyjście blokuje drugiecat
. B nadal czeka na tę sekundęcat
. Od czasuecho two
wyjścia, w>(cat)
końcu dostaje EOF, więc>(cat)
opróżnia bufor i wychodzi. Nikt już nie trzymacat
stdin B / sekundy , więc drugicat
czyta EOF (B wcale nie czyta stdin, nie ma znaczenia). Ten EOF powoduje, że drugicat
opróżnia bufor, zamyka stdout i wychodzi, a następnie B wychodzi, ponieważ zostałcat
zakończony, a B czekałcat
.Jedynym zastrzeżeniem jest to, że bash również tworzy podpowłokę
>(cat)
! Z tego powodu to zobaczyszecho two > >(sleep 5) | cat; echo three
nadal będzie czekał 5 sekund przed wykonaniem
echo three
, nawet jeślisleep 5
nie trzyma standardowej wartości B. Wynika to z tego, że ukryta podpowłoka C, odrodzona,>(sleep 5)
czekasleep
, a C trzyma stdin B. Możesz zobaczyć jakecho two > >(exec sleep 5) | cat; echo three
Nie będzie jednak czekać, ponieważ
sleep
nie trzyma standardowego B i nie ma żadnej duchowej podpowłoki C, która trzymałaby standardową B (exec wymusi uśpienie, aby zastąpić C, w przeciwieństwie do rozwidlania i zmuszania C do oczekiwaniasleep
). Niezależnie od tego zastrzeżeniaecho two > >(exec cat) | cat; echo three
będzie nadal poprawnie wykonywać funkcje w kolejności, jak opisano wcześniej.
źródło
A
nie czeka nacat
spawn>(cat)
. Jak już wspomniałem w mojej odpowiedzi, powodem, dla któregoecho two > >(sleep 5 &>/dev/null) | cat; echo three
wynikithree
sąbash
generowane po 5 sekundach, jest to, że obecne wersje marnują dodatkowy proces powłoki,>(sleep 5)
który czeka,sleep
a proces ten wciąż przechodzi do tego,pipe
co uniemożliwia zakończenie drugiegocat
. Jeśli zastąpisz go,echo two > >(exec sleep 5 &>/dev/null) | cat; echo three
aby wyeliminować ten dodatkowy proces, przekonasz się, że natychmiast wraca.echo two > >(sleep 5 &>/dev/null)
że przynajmniej ma własną podpowłokę. Czy jest to nieudokumentowany szczegół implementacji, który powodujesleep 5
również uzyskanie własnej podpowłoki? Jeśli jest to udokumentowane, byłby to uzasadniony sposób, aby zrobić to przy użyciu mniejszej liczby znaków (chyba że istnieje ścisła pętla, nie sądzę, aby ktokolwiek zauważył problemy z wydajnością podpowłoki lub kota) `. Jeśli nie jest to udokumentowane, zgrywaj, ale fajny hack nie będzie działał w przyszłych wersjach.$(...)
,<(...)
rzeczywiście dotyczą podpowłoki, ale ksh93 lub zsh uruchomiłyby ostatnią komendę w tej podpowłoce w tym samym procesie, niebash
dlatego jest jeszcze inny proces utrzymujący potok w pozycji otwartej, podczas gdysleep
uruchomiony i nie utrzymujący potoku w pozycji otwartej. Przyszłe wersjebash
mogą implementować podobną optymalizację.exec
działa on zgodnie z oczekiwaniami.