Může někdo poskytnout pár příkladů, jak používat coproc
?
Přijatá odpověď:
koprocesy jsou ksh
funkce (již v ksh88
). zsh
má tuto funkci od začátku (začátek 90. let), zatímco byla právě přidána do bash
v 4.0
(2009).
Nicméně chování a rozhraní se mezi těmito 3 shelly výrazně liší.
Myšlenka je však stejná:umožňuje spuštění úlohy na pozadí a možnost posílat jí vstup a číst její výstup, aniž byste se museli uchýlit k pojmenovaným kanálům.
To se provádí pomocí nepojmenovaných kanálů s většinou shellů a párů zásuvek s nejnovějšími verzemi ksh93 na některých systémech.
V a | cmd | b
, a
odešle data do cmd
a b
přečte jeho výstup. Spuštění cmd
jako koproces umožňuje shell být jak a
a b
.
spoluprocesy ksh
V ksh
, spustíte koproces jako:
cmd |&
Data odešlete do cmd
děláním věcí jako:
echo test >&p
nebo
print -p test
A přečtěte si cmd
výstup s věcmi jako:
read var <&p
nebo
read -p var
cmd
se spouští jako jakákoli úloha na pozadí, můžete použít fg
, bg
, kill
na něj a odkazujte jej pomocí %job-number
nebo prostřednictvím $!
.
Pro uzavření zapisovacího konce roury cmd
čte z, můžete:
exec 3>&p 3>&-
A k uzavření čtecího konce druhého kanálu (ten cmd
píše):
exec 3<&p 3<&-
Nemůžete spustit druhý společný proces, pokud nejprve neuložíte deskriptory souboru roura do jiných fds. Například:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
zsh spoluzpracovává
V zsh
, koprocesy jsou téměř totožné s těmi v ksh
. Jediný skutečný rozdíl je v tom, že zsh
koprocesy se spouští pomocí coproc
klíčové slovo.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Dělám:
exec 3>&p
Poznámka:Toto nepřesune coproc
deskriptor souboru na fd 3
(jako v ksh
), ale duplikuje to. Neexistuje tedy žádný explicitní způsob, jak uzavřít přívodní nebo čtecí potrubí, jiný začíná jiným coproc
.
Chcete-li například zavřít konec podávání:
coproc tr a b
echo aaaa >&p # send some data
exec 4<&p # preserve the reading end on fd 4
coproc : # start a new short-lived coproc (runs the null command)
cat <&4 # read the output of the first coproc
Kromě společných procesů založených na potrubí, zsh
(od 3.1.6-dev19, vydané v roce 2000) má konstrukty založené na pseudo-tty jako expect
. Pro interakci s většinou programů nebudou koprocesy ve stylu ksh fungovat, protože programy se začnou ukládat do vyrovnávací paměti, když je jejich výstup roura.
Zde je několik příkladů.
Spusťte společný proces x
:
zmodload zsh/zpty
zpty x cmd
(Zde cmd
je jednoduchý příkaz. Ale s eval
můžete dělat mnohem lepší věci nebo funkce.)
Zasílejte údaje o společném zpracování:
zpty -w x some data
Čtení údajů o společném zpracování (v nejjednodušším případě):
zpty -r x var
Jako expect
, může čekat na nějaký výstup ze společného procesu odpovídající danému vzoru.
spoluprocesy bash
Syntaxe bash je mnohem novější a staví na nové funkci nedávno přidané do ksh93, bash a zsh. Poskytuje syntaxi umožňující manipulaci s dynamicky alokovanými deskriptory souborů nad 10.
bash
nabízí základní coproc
syntaxe a rozšířená jeden.
Základní syntaxe
Základní syntaxe pro zahájení společného procesu vypadá jako zsh
's:
coproc cmd
V ksh
nebo zsh
, kanály do az společného procesu jsou přístupné pomocí >&p
a <&p
.
Ale v bash
, deskriptory souboru kanálu ze společného procesu a druhého kanálu do společného procesu jsou vráceny v $COPROC
pole (respektive ${COPROC[0]}
a ${COPROC[1]}
. Takže…
Předat data do společného procesu:
echo xxx >&"${COPROC[1]}"
Čtení dat ze společného procesu:
read var <&"${COPROC[0]}"
Se základní syntaxí můžete v daném okamžiku spustit pouze jeden společný proces.
Rozšířená syntaxe
V rozšířené syntaxi můžete pojmenovat vaše společné procesy (jako v zsh
zpty co-processes):
coproc mycoproc { cmd; }
Příkaz has být složeným příkazem. (Všimněte si, jak výše uvedený příklad připomíná function f { ...; }
.)
Tentokrát jsou deskriptory souborů v ${mycoproc[0]}
a ${mycoproc[1]}
.
Můžete spustit více než jeden společný proces najednou – ale to uděláte zobrazí se upozornění, když spustíte společný proces, zatímco jeden stále běží (i v neinteraktivním režimu).
Při použití rozšířené syntaxe můžete deskriptory souborů zavřít.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
Všimněte si, že zavírání tímto způsobem nefunguje ve verzích bash starších než 4.3, kde to musíte místo toho napsat:
fd=${tr[1]}
exec {fd}>&-
Stejně jako v ksh
a zsh
, tyto popisovače souborů kanálu jsou označeny jako close-on-exec.
Ale v bash
, jediný způsob, jak je předat provedeným příkazům, je duplikovat je do fds ,
1
, nebo 2
. To omezuje počet společných procesů, se kterými můžete pracovat pro jeden příkaz. (Příklad viz níže.)
yash proces a přesměrování potrubí
yash
nemá samo o sobě funkci co-process, ale stejný koncept lze implementovat pomocí jeho potrubí a zpracovat funkce přesměrování. yash
má rozhraní k pipe()
systémové volání, takže takové věci lze relativně snadno provést ručně.
Zahájili byste společný proces s:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Který nejprve vytvoří pipe(4,5)
(5 konec zápisu, 4 konec čtení), pak přesměruje fd 3 do roury na proces, který běží se svým stdin na druhém konci, a stdout přejde na rouru vytvořenou dříve. Poté zavřeme konec pro psaní této roury v nadřazeném prvku, který nebudeme potřebovat. Takže nyní v shellu máme fd 3 připojený k stdin cmd a fd 4 připojený k stdout cmd pomocí potrubí.
Všimněte si, že u těchto deskriptorů souborů není nastaven příznak close-on-exec.
Zdroj dat:
echo data >&3 4<&-
Čtení dat:
read var <&4 3>&-
A můžete fds zavřít jako obvykle:
exec 3>&- 4<&-
Proč nejsou tak populární
téměř žádná výhoda oproti používání pojmenovaných kanálů
Společné procesy lze snadno implementovat pomocí standardních pojmenovaných kanálů. Nevím, kdy byly přesně pojmenované roury představeny, ale je možné, že to bylo po ksh
přišel se společnými procesy (pravděpodobně v polovině 80. let byl ksh88 „vydán“ v roce 88, ale věřím, že ksh
byl interně používán v AT&T několik let před tím), což by vysvětlovalo proč.
cmd |&
echo data >&p
read var <&p
Lze psát pomocí:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
Interakce s nimi je jednodušší – zvláště pokud potřebujete spustit více než jeden společný proces. (Viz příklady níže.)
Jediná výhoda použití coproc
spočívá v tom, že po použití nemusíte tyto pojmenované trubky čistit.
náchylný k uváznutí
Shelly používají potrubí v několika konstrukcích:
- shell pipes:
cmd1 | cmd2
, - záměna příkazů:
$(cmd)
, - a zpracovat náhradu:
<(cmd)
,>(cmd)
.
V těch tečou data pouze v jednom směr mezi různými procesy.
Díky společným procesům a pojmenovaným kanálům je však snadné se dostat do slepé uličky. Musíte sledovat, který příkaz má který deskriptor souboru otevřený, abyste předešli tomu, že jeden zůstane otevřený a udrží proces při životě. Vyšetřování uváznutí může být složité, protože k nim může docházet nedeterministicky; například pouze v případě, že je odesláno tolik dat, aby se zaplnilo jedno potrubí.
funguje hůř, než expect
pro to, k čemu byl navržen
Hlavním účelem koprocesů bylo poskytnout shellu způsob interakce s příkazy. Nicméně to nefunguje tak dobře.
Nejjednodušší forma uváznutí zmíněná výše je:
tr a b |&
echo a >&p
read var<&p
Protože jeho výstup nejde do terminálu, tr
vyrovnává svůj výstup. Takže nevypíše nic, dokud buď neuvidí konec souboru na svém stdin
nebo nashromáždil vyrovnávací paměť plnou dat pro výstup. Takže výše, poté, co shell vypíše an
(pouze 2 bajty), read
bude blokovat na dobu neurčitou, protože tr
čeká, až mu shell pošle další data.
Stručně řečeno, roury nejsou dobré pro interakci s příkazy. Společné procesy lze použít pouze k interakci s příkazy, které neukládají svůj výstup do vyrovnávací paměti, nebo příkazy, kterým lze říci, aby svůj výstup nevyrovnávaly; například pomocí stdbuf
s některými příkazy na nejnovějších systémech GNU nebo FreeBSD.
Proto expect
nebo zpty
místo toho použijte pseudoterminály. expect
je nástroj určený pro interakci s příkazy a dělá to dobře.
Manipulace s deskriptorem souboru je nešikovná a je těžké ji správně nastavit
Společné procesy lze použít k provádění složitějších instalatérských prací, než jaké umožňují jednoduché skořepinové trubky.
že další odpověď Unix.SE má příklad použití coproc.
Zjednodušený příklad: Představte si, že chcete funkci, která předá kopii výstupu příkazu 3 dalším příkazům, a poté necháte výstup těchto 3 příkazů zřetězit.
Vše pomocí potrubí.
Například:vložte výstup printf '%sn' foo bar
na tr a b
, sed 's/./&&/g'
a cut -b2-
získat něco jako:
foo
bbr
ffoooo
bbaarr
oo
ar
Za prvé, není to nutně zřejmé, ale existuje možnost uváznutí, které se začne dít již po několika kilobajtech dat.
Potom, v závislosti na vašem shellu, narazíte na řadu různých problémů, které je třeba řešit odlišně.
Související:Jak je zástupný znak * interpretován jako příkaz?
Například pomocí zsh
, udělali byste to pomocí:
f() (
coproc tr a b
exec {o1}<&p {i1}>&p
coproc sed 's/./&&/g' {i1}>&- {o1}<&-
exec {o2}<&p {i2}>&p
coproc cut -c2- {i1}>&- {o1}<&- {i2}>&- {o2}<&-
tee /dev/fd/$i1 /dev/fd/$i2 >&p {o1}<&- {o2}<&- &
exec cat /dev/fd/$o1 /dev/fd/$o2 - <&p {i1}>&- {i2}>&-
)
printf '%sn' foo bar | f
Výše uvedené koprocesní fds mají nastavený příznak close-on-exec, ale ne ty, které jsou z nich duplikovány (jako v {o1}<&p
). Abyste se vyhnuli uváznutí, musíte se ujistit, že jsou uzavřeny ve všech procesech, které je nepotřebují.
Podobně musíme použít subshell a použít exec cat
na konci, aby se zajistilo, že žádný skořápkový proces nebude lhát o držení potrubí otevřené.
Pomocí ksh
(zde ksh93
), to by muselo být:
f() (
tr a b |&
exec {o1}<&p {i1}>&p
sed 's/./&&/g' |&
exec {o2}<&p {i2}>&p
cut -c2- |&
exec {o3}<&p {i3}>&p
eval 'tee "/dev/fd/$i1" "/dev/fd/$i2"' >&"$i3" {i1}>&"$i1" {i2}>&"$i2" &
eval 'exec cat "/dev/fd/$o1" "/dev/fd/$o2" -' <&"$o3" {o1}<&"$o1" {o2}<&"$o2"
)
printf '%sn' foo bar | f
(Poznámka: To nebude fungovat na systémech, kde je ksh
používá socketpairs
místo pipes
a kde /dev/fd/n
funguje jako na Linuxu.)
V ksh
, fds nad 2
jsou označeny příznakem close-on-exec, pokud nejsou předány explicitně na příkazovém řádku. Proto nemusíme zavírat nepoužívané deskriptory souborů jako u zsh
—ale to je také důvod, proč musíme udělat {i1}>&$i1
a použijte eval
pro tuto novou hodnotu $i1
, které budou předány tee
a cat
…
V bash
to nelze provést, protože se nemůžete vyhnout příznaku close-on-exec.
Výše je to relativně jednoduché, protože používáme pouze jednoduché externí příkazy. Je to složitější, když tam místo toho chcete použít konstrukce shellu a začnete narážet na chyby v shellu.
Porovnejte výše uvedené se stejným pomocí pojmenovaných kanálů:
f() {
mkfifo p{i,o}{1,2,3}
tr a b < pi1 > po1 &
sed 's/./&&/g' < pi2 > po2 &
cut -c2- < pi3 > po3 &
tee pi{1,2} > pi3 &
cat po{1,2,3}
rm -f p{i,o}{1,2,3}
}
printf '%sn' foo bar | f
Závěr
Pokud chcete pracovat s příkazem, použijte expect
, nebo zsh
's zpty
nebo pojmenované kanály.
Chcete-li provést nějaké efektní instalatérské práce s potrubím, použijte pojmenované potrubí.
Co-process může provést některé z výše uvedených, ale buďte připraveni udělat vážné škrábání hlavou pro cokoli netriviálního.