GNU/Linux >> Znalost Linux >  >> Linux

Výstup procesní substituce je mimo provoz?

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).

Související:Co způsobuje, že soubory ztrácejí oprávnění?

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 .


Linux
  1. Nový rodičovský proces, když rodičovský proces zemře?

  2. Výstup „posledního“ příkazu?

  3. Spusťte proces s výstupem v reálném čase v PHP

  1. Náhrada procesu a potrubí?

  2. Jak zobrazit výstup běžícího procesu v jiné relaci Bash?

  3. Zavření standardního výstupu (>&-)?

  1. Co se stane s výstupem procesu, který byl odmítnut a ztratil svůj terminál?

  2. Co znamená ve výstupu Ps?

  3. Přenosný (posix) způsob, jak dosáhnout substituce procesu?