GNU/Linux >> Linux Esercitazione >  >> Linux

Come utilizzare il comando Copoc in varie shell?

Qualcuno può fornire un paio di esempi su come utilizzare coproc ?

Risposta accettata:

i co-processi sono un ksh funzione (già in ksh88 ). zsh ha avuto la funzione dall'inizio (primi anni '90), mentre è stata appena aggiunta solo a bash in 4.0 (2009).

Tuttavia, il comportamento e l'interfaccia sono significativamente diversi tra le 3 shell.

L'idea, però, è la stessa:permette di avviare un lavoro in background e di poterlo inviare in input e leggerne l'output senza dover ricorrere a named pipe.

Questo viene fatto con pipe senza nome con la maggior parte delle shell e delle coppie di prese con versioni recenti di ksh93 su alcuni sistemi.

In a | cmd | b , a invia i dati a cmd e b ne legge l'output. Esecuzione di cmd poiché un co-processo consente alla shell di essere sia a e b .

ksh co-elabora

In ksh , avvii un coprocesso come:

cmd |&

Invii i dati a cmd facendo cose come:

echo test >&p

o

print -p test

E leggi cmd 's output con cose come:

read var <&p

o

read -p var

cmd viene avviato come qualsiasi lavoro in background, puoi usare fg , bg , kill su di esso e referenziarlo con %job-number o tramite $! .

Per chiudere l'estremità di scrittura della pipe cmd sta leggendo, puoi fare:

exec 3>&p 3>&-

E per chiudere l'estremità di lettura dell'altra pipe (quella cmd scrive a):

exec 3<&p 3<&-

Non è possibile avviare un secondo coprocesso a meno che non si salvino prima i descrittori del file pipe in altri fd. Ad esempio:

tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p

zsh coelabora

In zsh , i coprocessi sono quasi identici a quelli in ksh . L'unica vera differenza è che zsh i co-processi vengono avviati con coproc parola chiave.

coproc cmd
echo test >&p
read var <&p
print -p test
read -p var

Fare:

exec 3>&p

Nota:questo non sposta il coproc descrittore di file in fd 3 (come in ksh ), ma lo duplica. Quindi, non esiste un modo esplicito per chiudere il tubo di alimentazione o di lettura, altri iniziando un altro coproc .

Ad esempio, per chiudere l'estremità di alimentazione:

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

Oltre ai coprocessi basati su pipe, zsh (dal 3.1.6-dev19, rilasciato nel 2000) ha costrutti basati su pseudo-tty come expect . Per interagire con la maggior parte dei programmi, i coprocessi in stile ksh non funzioneranno, poiché i programmi iniziano il buffering quando il loro output è una pipe.

Ecco alcuni esempi.

Avvia il co-processo x :

zmodload zsh/zpty
zpty x cmd

(Qui, cmd è un semplice comando Ma puoi fare cose più fantasiose con eval o funzioni.)

Inserisci un coprocesso di dati:

zpty -w x some data

Leggi i dati di co-elaborazione (nel caso più semplice):

zpty -r x var

Come expect , può attendere che l'output del coprocesso corrisponda a un determinato modello.

coprocessi bash

La sintassi bash è molto più recente e si basa su una nuova funzionalità aggiunta di recente a ksh93, bash e zsh. Fornisce una sintassi per consentire la gestione dei descrittori di file allocati dinamicamente superiori a 10.

bash offre una base coproc sintassi e un esteso uno.

Sintassi di base

La sintassi di base per avviare un coprocesso è zsh 's:

coproc cmd

In ksh o zsh , si accede alle pipe da e verso il co-processo con >&p e <&p .

Ma in bash , i descrittori di file della pipe dal coprocesso e dall'altra pipe al coprocesso vengono restituiti nel $COPROC array (rispettivamente ${COPROC[0]} e ${COPROC[1]} . Allora...

Invia i dati al coprocessore:

echo xxx >&"${COPROC[1]}"

Leggi i dati dal co-processo:

read var <&"${COPROC[0]}"

Con la sintassi di base, puoi avviare un solo coprocesso alla volta.

Sintassi estesa

Nella sintassi estesa, puoi nominare i tuoi coprocessi (come in zsh zpty co-processi):

coproc mycoproc { cmd; }

Il comando ha essere un comando composto. (Nota come l'esempio sopra ricorda function f { ...; } .)

Questa volta, i descrittori di file sono in ${mycoproc[0]} e ${mycoproc[1]} .

Puoi avviare più di un coprocesso alla volta, ma fai ricevi un avviso quando avvii un coprocesso mentre uno è ancora in esecuzione (anche in modalità non interattiva).

Puoi chiudere i descrittori di file quando usi la sintassi estesa.

coproc tr { tr a b; }
echo aaa >&"${tr[1]}"

exec {tr[1]}>&-

cat <&"${tr[0]}"

Nota che la chiusura in questo modo non funziona nelle versioni bash precedenti alla 4.3 dove devi invece scriverla:

fd=${tr[1]}
exec {fd}>&-

Come in ksh e zsh , quei descrittori di file pipe sono contrassegnati come close-on-exec.

Ma in bash , l'unico modo per passarli ai comandi eseguiti è duplicarli in fds , 1 o 2 . Ciò limita il numero di coprocessi con cui puoi interagire per un singolo comando. (Vedi sotto per un esempio.)

Correlati:eseguire l'applicazione non attendibile in modo sicuro tramite il comando sandbox-exec?

Processo di yash e reindirizzamento della pipeline

yash non ha una funzione di co-processing di per sé, ma lo stesso concetto può essere implementato con la sua pipeline e processo funzionalità di reindirizzamento. yash ha un'interfaccia per pipe() chiamata di sistema, quindi questo genere di cose può essere fatto a mano in modo relativamente semplice.

Inizierai un co-processo con:

exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-

Che prima crea una pipe(4,5) (5 l'estremità di scrittura, 4 l'estremità di lettura), quindi reindirizza fd 3 a una pipe a un processo che viene eseguito con il suo stdin all'altra estremità e stdout va alla pipe creata in precedenza. Quindi chiudiamo l'estremità di scrittura di quella pipe nel genitore di cui non avremo bisogno. Quindi ora nella shell abbiamo fd 3 collegato allo stdin di cmd e fd 4 collegato allo stdout di cmd con pipe.

Nota che il flag close-on-exec non è impostato su quei descrittori di file.

Per alimentare i dati:

echo data >&3 4<&-

Per leggere i dati:

read var <&4 3>&-

E puoi chiudere gli fd come al solito:

exec 3>&- 4<&-

Ora, perché non sono così popolari

quasi alcun vantaggio rispetto all'utilizzo di named pipe

I co-processi possono essere facilmente implementati con named pipe standard. Non so quando siano state introdotte esattamente le pipe con nome, ma è possibile che fosse dopo ksh ha inventato co-processi (probabilmente a metà degli anni '80, ksh88 è stato "rilasciato" nell'88, ma credo che ksh è stato utilizzato internamente ad AT&T alcuni anni prima) il che spiegherebbe il perché.

cmd |&
echo data >&p
read var <&p

Può essere scritto con:

mkfifo in out

cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4

Interagire con quelli è più semplice, soprattutto se è necessario eseguire più di un coprocesso. (Vedi esempi sotto.)

L'unico vantaggio dell'utilizzo di coproc è che non devi ripulire quelle pipe con nome dopo l'uso.

a rischio di stallo

Le shell usano pipe in alcuni costrutti:

  • tubi a conchiglia: cmd1 | cmd2 ,
  • sostituzione comando: $(cmd) ,
  • e sostituzione del processo: <(cmd) , >(cmd) .

In questi, i dati fluiscono in solo uno direzione tra diversi processi.

Con i co-processi e le named pipe, tuttavia, è facile imbattersi in un deadlock. Devi tenere traccia di quale comando ha quale descrittore di file aperto, per evitare che uno rimanga aperto e mantenga vivo un processo. I deadlock possono essere difficili da indagare, perché possono verificarsi in modo non deterministico; ad esempio, solo quando vengono inviati tanti dati per riempire un tubo.

funziona peggio di expect per quello per cui è stato progettato

Lo scopo principale dei co-processi era fornire alla shell un modo per interagire con i comandi. Tuttavia, non funziona così bene.

La forma più semplice di deadlock menzionata sopra è:

tr a b |&
echo a >&p
read var<&p

Poiché il suo output non va a un terminale, tr bufferizza il suo output. Quindi non produrrà nulla finché non vedrà la fine del file sul suo stdin o ha accumulato un buffer pieno di dati da produrre. Così sopra, dopo che la shell ha prodotto an (solo 2 byte), il read si bloccherà a tempo indeterminato perché tr sta aspettando che la shell gli invii più dati.

In breve, le pipe non sono buone per interagire con i comandi. I coprocessi possono essere utilizzati solo per interagire con comandi che non memorizzano nel buffer il loro output, o comandi a cui si può dire di non bufferizzare il loro output; ad esempio, utilizzando stdbuf con alcuni comandi sui recenti sistemi GNU o FreeBSD.

Ecco perché expect o zpty usa invece pseudo-terminali. expect è uno strumento progettato per interagire con i comandi e lo fa bene.

La gestione del descrittore di file è complicata e difficile da correggere

I co-processi possono essere utilizzati per eseguire impianti idraulici più complessi di quelli consentiti dai semplici tubi a conchiglia.

quell'altra risposta Unix.SE ha un esempio di utilizzo di coproc.

Ecco un esempio semplificato: Immagina di volere una funzione che invii una copia dell'output di un comando ad altri 3 comandi, e quindi l'output di quei 3 comandi venga concatenato.

Tutti con tubi.

Ad esempio:alimenta l'output di printf '%sn' foo bar a tr a b , sed 's/./&&/g' e cut -b2- per ottenere qualcosa come:

foo
bbr
ffoooo
bbaarr
oo
ar

Innanzitutto, non è necessariamente ovvio, ma c'è la possibilità di un deadlock lì e inizierà a verificarsi dopo solo pochi kilobyte di dati.

Quindi, a seconda della tua shell, ti imbatterai in una serie di problemi diversi che devono essere affrontati in modo diverso.

Correlati:come viene interpretato il carattere jolly * come comando?

Ad esempio, con zsh , lo faresti con:

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

Sopra, gli fd co-process hanno il flag close-on-exec impostato, ma non quelli che sono duplicati da loro (come in {o1}<&p ). Quindi, per evitare deadlock, dovrai assicurarti che siano chiusi in tutti i processi che non ne hanno bisogno.

Allo stesso modo, dobbiamo usare una subshell e usare exec cat alla fine, per garantire che non ci sia alcun processo di shell che mente sul tenere aperta una pipa.

Con ksh (qui ksh93 ), dovrebbe essere:

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

(Nota: Non funzionerà su sistemi in cui ksh utilizza socketpairs invece di pipes e dove /dev/fd/n funziona come su Linux.)

In ksh , fd sopra 2 sono contrassegnati con il flag close-on-exec, a meno che non vengano passati esplicitamente sulla riga di comando. Ecco perché non dobbiamo chiudere i descrittori di file inutilizzati come con zsh —ma è anche il motivo per cui dobbiamo fare {i1}>&$i1 e usa eval per quel nuovo valore di $i1 , da passare a tee e cat

In bash questo non può essere fatto, perché non puoi evitare il flag close-on-exec.

Sopra, è relativamente semplice, perché utilizziamo solo semplici comandi esterni. Diventa più complicato quando invece vuoi usare i costrutti della shell lì dentro e inizi a incappare in bug della shell.

Confronta quanto sopra con lo stesso usando le pipe con nome:

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

Conclusione

Se vuoi interagire con un comando, usa expect o zsh 's zpty o pipe con nome.

Se vuoi realizzare un impianto idraulico di fantasia con i tubi, usa i tubi con nome.

I co-processi possono eseguire alcune delle operazioni precedenti, ma preparati a grattarti la testa per qualsiasi cosa non banale.


Linux
  1. Come usare il comando sed di Linux

  2. Come usare il comando Linux grep

  3. Come usare il comando cronologia in Linux

  4. Come utilizzare il comando basename?

  5. Come utilizzare il comando id in Linux

Come usare il comando nmap

Come utilizzare il comando fd sul sistema Linux

Come utilizzare il comando wget in Linux?

Come usare il comando xargs in Linux?

Come utilizzare il comando RPM in Linux

Come utilizzare il comando which in Linux