The
echo one; echo two > >(cat); echo three;
příkaz poskytuje neočekávaný výstup.
Četl jsem toto:Jak je substituce procesů implementována v bash? a mnoho dalších článků o substituci procesů na internetu, ale nerozumím tomu, proč se to chová tímto způsobem.
Očekávaný výstup:
one
two
three
Skutečný výstup:
prompt$ echo one; echo two > >(cat); echo three;
one
three
prompt$ two
Také tyto dva příkazy by měly být z mého pohledu ekvivalentní, ale nejsou:
##### 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
Proč si myslím, že by měly být stejné? Protože obojí spojuje seq
výstup do cat
vstup přes anonymní rouru – Wikipedie, Záměna procesu.
Otázka: Proč se to tak chová? Kde je moje chyba? Vyžaduje se vyčerpávající odpověď (s vysvětlením, jak bash
dělá to pod kapotou).
Přijatá odpověď:
Ano, v bash
jako v ksh
(odkud tato funkce pochází), na procesy uvnitř substituce procesu se nečeká (před spuštěním dalšího příkazu ve skriptu).
pro <(...)
jeden, to je obvykle v pořádku jako v:
cmd1 <(cmd2)
shell bude čekat na cmd1
a cmd1
bude obvykle čekat na cmd2
díky tomu, že čte až do konce souboru na rouře, která je nahrazena, a tento konec souboru obvykle nastane, když cmd2
zemře. To je stejný důvod, proč několik shellů (ne bash
) neobtěžujte se čekáním na cmd2
v cmd2 | cmd1
.
Pro cmd1 >(cmd2)
, ale obecně tomu tak není, protože je to spíše cmd2
který obvykle čeká na cmd1
tam se tedy obecně ukončí po.
To je opraveno v zsh
který čeká na cmd2
tam (ale ne, pokud to napíšete jako cmd1 > >(cmd2)
a cmd1
není vestavěný, použijte {cmd1} > >(cmd2)
místo toho, jak je zdokumentováno).
ksh
ve výchozím nastavení nečeká, ale nechá vás čekat pomocí wait
vestavěný (také zpřístupňuje pid v $!
, i když to nepomůže, pokud uděláte cmd1 >(cmd2) >(cmd3)
)
rc
(pomocí cmd1 >{cmd2}
syntaxe), stejně jako ksh
kromě toho můžete získat pid všech procesů na pozadí pomocí $apids
.
es
(také pomocí cmd1 >{cmd2}
) čeká na cmd2
jako v zsh
, a také čeká na cmd2
v <{cmd2}
proces přesměrování.
bash
dělá pid z cmd2
(nebo přesněji subshell, protože spouští cmd2
v podřízeném procesu tohoto subshell, i když je to tam poslední příkaz) dostupný v $!
, ale nenechá vás na to čekat.
Pokud musíte použít bash
, můžete problém obejít pomocí příkazu, který bude čekat na oba příkazy s:
{ { cmd1 >(cmd2); } 3>&1 >&4 4>&- | cat; } 4>&1
To dělá oba cmd1
a cmd2
mít jejich fd 3 otevřené do potrubí. cat
bude čekat na konec souboru na druhém konci, takže se obvykle ukončí, pouze když obě cmd1
a cmd2
jsou mrtví. A shell počká na tu cat
příkaz. Mohli byste to vidět jako síť, která zachytí ukončení všech procesů na pozadí (můžete ji použít pro jiné věci spuštěné na pozadí, jako je &
, coprocs nebo dokonce příkazy, které jsou na pozadí, za předpokladu, že neuzavřou všechny své deskriptory souborů, jako to obvykle dělají démoni).
Všimněte si, že díky výše zmíněnému zbytečnému subshell procesu funguje, i když cmd2
zavře svůj fd 3 (příkazy to obvykle nedělají, ale některé jako sudo
nebo ssh
dělat). Budoucí verze bash
může tam nakonec provést optimalizaci jako v jiných shellech. Pak byste potřebovali něco jako:
{ { cmd1 >(sudo cmd2; exit); } 3>&1 >&4 4>&- | cat; } 4>&1
Abychom se ujistili, že stále existuje další proces shellu s tím otevřeným fd 3, který čeká na to sudo
příkaz.
Všimněte si, že cat
nic nepřečte (protože procesy nezapisují na jejich fd 3). Je tam jen kvůli synchronizaci. Provede pouze jedno read()
systémové volání, které se vrátí a na konci nebude nic.
Ve skutečnosti se můžete vyhnout spuštění cat
pomocí substituce příkazu k provedení synchronizace potrubí:
{ unused=$( { cmd1 >(cmd2); } 3>&1 >&4 4>&-); } 4>&1
Tentokrát je to shell místo cat
to je čtení z roury, jejíž druhý konec je otevřený na fd 3 cmd1
a cmd2
. Používáme přiřazení proměnné, takže stav ukončení cmd1
je k dispozici v $?
.
Nebo můžete provést náhradu procesu ručně a pak můžete dokonce použít systémový sh
protože by se to stalo standardní syntaxí shellu:
{ cmd1 /dev/fd/3 3>&1 >&4 4>&- | cmd2 4>&-; } 4>&1
i když si všimněte, jak bylo uvedeno dříve, že ne všechny sh
implementace by čekaly na cmd1
za cmd2
skončil (i když je to lepší než naopak). Tenkrát, $?
obsahuje stav ukončení cmd2
; i když bash
a zsh
vytvořit cmd1
Stav ukončení je dostupný v ${PIPESTATUS[0]}
a $pipestatus[1]
respektive (viz také pipefail
možnost v několika shellech, takže $?
může hlásit selhání jiných než posledních součástí potrubí)
Všimněte si, že yash
má podobné problémy s přesměrováním procesu Vlastnosti. cmd1 >(cmd2)
bude zapsáno cmd1 /dev/fd/3 3>(cmd2)
tam. Ale cmd2
se nečeká a nemůžete použít wait
čekat na něj a jeho pid není k dispozici v $!
buď variabilní. Použili byste stejná řešení jako pro bash
.