GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo usar el comando Coproc en varios shells?

¿Puede alguien proporcionar un par de ejemplos sobre cómo usar coproc? ?

Respuesta aceptada:

los co-procesos son un ksh característica (ya en ksh88 ). zsh ha tenido la función desde el principio (principios de los 90), mientras que solo se ha agregado a bash en 4.0 (2009).

Sin embargo, el comportamiento y la interfaz son significativamente diferentes entre los 3 proyectiles.

Sin embargo, la idea es la misma:permite iniciar un trabajo en segundo plano y poder enviarlo de entrada y leer su salida sin tener que recurrir a canalizaciones con nombre.

Eso se hace con tuberías sin nombre con la mayoría de los shells y pares de sockets con versiones recientes de ksh93 en algunos sistemas.

En a | cmd | b , a alimenta datos a cmd y b lee su salida. Ejecutando cmd como un co-proceso permite que el shell sea tanto a y b .

ksh co-procesos

En ksh , inicias un coproceso como:

cmd |&

Usted alimenta datos a cmd haciendo cosas como:

echo test >&p

o

print -p test

Y lee cmd La salida con cosas como:

read var <&p

o

read -p var

cmd se inicia como cualquier trabajo en segundo plano, puede usar fg , bg , kill en él y referirlo por %job-number o a través de $! .

Para cerrar el extremo de escritura de la tubería cmd está leyendo, puedes hacer:

exec 3>&p 3>&-

Y para cerrar el extremo de lectura de la otra tubería (la que cmd está escribiendo a):

exec 3<&p 3<&-

No puede iniciar un segundo coproceso a menos que primero guarde los descriptores del archivo de tubería en otros fds. Por ejemplo:

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

co-procesos zsh

En zsh , los coprocesos son casi idénticos a los de ksh . La única diferencia real es que zsh los coprocesos se inician con coproc palabra clave.

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

Haciendo:

exec 3>&p

Nota:Esto no mueve el coproc descriptor de archivo a fd 3 (como en ksh ), pero lo duplica. Por lo tanto, no hay una forma explícita de cerrar la tubería de alimentación o lectura, sino iniciar otra coproc .

Por ejemplo, para cerrar el extremo de alimentació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

Además de los coprocesos basados ​​en tuberías, zsh (desde 3.1.6-dev19, lanzado en 2000) tiene construcciones basadas en pseudo-tty como expect . Para interactuar con la mayoría de los programas, los coprocesos de estilo ksh no funcionarán, ya que los programas comienzan a almacenar en búfer cuando su salida es una canalización.

Estos son algunos ejemplos.

Iniciar el co-proceso x :

zmodload zsh/zpty
zpty x cmd

(Aquí, cmd es un comando simple. Pero puedes hacer cosas más sofisticadas con eval o funciones.)

Alimentar datos de coprocesamiento:

zpty -w x some data

Leer datos de coprocesamiento (en el caso más simple):

zpty -r x var

Me gusta expect , puede esperar a que algún resultado del coproceso coincida con un patrón dado.

coprocesos bash

La sintaxis de bash es mucho más nueva y se basa en una nueva función agregada recientemente a ksh93, bash y zsh. Proporciona una sintaxis para permitir el manejo de descriptores de archivos asignados dinámicamente por encima de 10.

bash ofrece un básico coproc sintaxis y un extendido uno.

Sintaxis básica

La sintaxis básica para iniciar un coproceso se parece a zsh 's:

coproc cmd

En ksh o zsh , se accede a las tuberías hacia y desde el coproceso con >&p y <&p .

Pero en bash , los descriptores de archivo de la tubería del coproceso y la otra tubería al coproceso se devuelven en el $COPROC matriz (respectivamente ${COPROC[0]} y ${COPROC[1]} . Entonces…

Alimentar datos al coproceso:

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

Leer datos del coproceso:

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

Con la sintaxis básica, puede iniciar solo un coproceso a la vez.

Sintaxis extendida

En la sintaxis extendida, puede nombre sus co-procesos (como en zsh co-procesos zpty):

coproc mycoproc { cmd; }

El comando tiene ser un comando compuesto. (Observe cómo el ejemplo anterior recuerda a function f { ...; } .)

Esta vez, los descriptores de archivo están en ${mycoproc[0]} y ${mycoproc[1]} .

Puede iniciar más de un coproceso a la vez, pero hace recibe una advertencia cuando inicia un co-proceso mientras uno todavía se está ejecutando (incluso en modo no interactivo).

Puede cerrar los descriptores de archivo cuando utilice la sintaxis extendida.

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

exec {tr[1]}>&-

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

Tenga en cuenta que cerrar de esa manera no funciona en las versiones de bash anteriores a la 4.3, donde debe escribirlo en su lugar:

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

Como en ksh y zsh , esos descriptores de archivos de tubería están marcados como close-on-exec.

Pero en bash , la única forma de pasarlos a los comandos ejecutados es duplicarlos en fds , 1 , o 2 . Eso limita la cantidad de coprocesos con los que puede interactuar para un solo comando. (Vea a continuación un ejemplo).

Relacionado:¿Ejecutar una aplicación que no es de confianza de forma segura a través del comando sandbox-exec?

proceso yash y redireccionamiento de tubería

yash no tiene una función de coprocesamiento per se, pero el mismo concepto se puede implementar con su canalización y procesar características de redirección. yash tiene una interfaz para el pipe() llamada al sistema, por lo que este tipo de cosas se pueden hacer con relativa facilidad a mano allí.

Comenzarías un co-proceso con:

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

Que primero crea un pipe(4,5) (5 el extremo de escritura, 4 el extremo de lectura), luego redirige fd 3 a una tubería a un proceso que se ejecuta con su stdin en el otro extremo, y stdout va a la tubería creada anteriormente. Luego cerramos el extremo de escritura de esa tubería en el padre que no necesitaremos. Así que ahora en el shell tenemos fd 3 conectado a la entrada estándar de cmd y fd 4 conectado a la salida estándar de cmd con tuberías.

Tenga en cuenta que el indicador close-on-exec no está configurado en esos descriptores de archivo.

Para alimentar datos:

echo data >&3 4<&-

Para leer datos:

read var <&4 3>&-

Y puedes cerrar fds como de costumbre:

exec 3>&- 4<&-

Ahora, por qué no son tan populares

casi ningún beneficio sobre el uso de canalizaciones con nombre

Los coprocesos se pueden implementar fácilmente con canalizaciones con nombre estándar. No sé cuándo se introdujeron exactamente las canalizaciones con nombre, pero es posible que fuera después de ksh surgió con co-procesos (probablemente a mediados de los 80, ksh88 fue "lanzado" en 88, pero creo que ksh se usó internamente en AT&T unos años antes), lo que explicaría por qué.

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

Se puede escribir con:

mkfifo in out

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

Interactuar con ellos es más sencillo, especialmente si necesita ejecutar más de un coproceso. (Vea los ejemplos a continuación).

El único beneficio de usar coproc es que no tiene que limpiar esas canalizaciones con nombre después de su uso.

propenso a interbloqueos

Las conchas usan tuberías en algunas construcciones:

  • tubos de concha: cmd1 | cmd2 ,
  • sustitución de comandos: $(cmd) ,
  • y sustitución de procesos: <(cmd) , >(cmd) .

En esos, los datos fluyen en solo uno dirección entre diferentes procesos.

Sin embargo, con los coprocesos y las canalizaciones con nombre, es fácil encontrarse en un punto muerto. Debe realizar un seguimiento de qué comando tiene qué descriptor de archivo abierto, para evitar que uno permanezca abierto y mantenga vivo un proceso. Los interbloqueos pueden ser complicados de investigar, porque pueden ocurrir de manera no determinista; por ejemplo, solo cuando se envían tantos datos como para llenar una tubería.

funciona peor de lo que expect para lo que ha sido diseñado

El objetivo principal de los coprocesos era proporcionar al shell una forma de interactuar con los comandos. Sin embargo, no funciona tan bien.

La forma más simple de interbloqueo mencionada anteriormente es:

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

Debido a que su salida no va a una terminal, tr amortigua su salida. Por lo tanto, no generará nada hasta que vea el final del archivo en su stdin , o ha acumulado un búfer lleno de datos para generar. Así arriba, después de que el shell haya generado an (solo 2 bytes), el read bloqueará indefinidamente porque tr está esperando que el shell le envíe más datos.

En resumen, las tuberías no son buenas para interactuar con los comandos. Los coprocesos solo se pueden usar para interactuar con comandos que no almacenan en búfer su salida, o comandos a los que se les puede decir que no almacenen su salida; por ejemplo, usando stdbuf con algunos comandos en sistemas GNU o FreeBSD recientes.

Por eso expect o zpty use pseudo-terminales en su lugar. expect es una herramienta diseñada para interactuar con comandos, y lo hace bien.

El manejo del descriptor de archivo es complicado y difícil de hacerlo bien

Los coprocesos se pueden usar para hacer tuberías más complejas que las que permiten las tuberías simples.

esa otra respuesta de Unix.SE tiene un ejemplo de uso de coproc.

Este es un ejemplo simplificado: Imagine que desea una función que envíe una copia de la salida de un comando a otros 3 comandos, y luego haga que la salida de esos 3 comandos se concatene.

Todos usando tuberías.

Por ejemplo:alimenta la salida de printf '%sn' foo bar a tr a b , sed 's/./&&/g' y cut -b2- para obtener algo como:

foo
bbr
ffoooo
bbaarr
oo
ar

En primer lugar, no es necesariamente obvio, pero existe la posibilidad de un interbloqueo, y comenzará a ocurrir después de unos pocos kilobytes de datos.

Luego, dependiendo de su caparazón, se encontrará con una serie de problemas diferentes que deben abordarse de manera diferente.

Relacionado:¿Cómo se interpreta el comodín * como un comando?

Por ejemplo, con zsh , lo harías 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

Arriba, los fds de co-proceso tienen el indicador close-on-exec establecido, pero no los que están duplicados de ellos (como en {o1}<&p ). Por lo tanto, para evitar interbloqueos, deberá asegurarse de que estén cerrados en cualquier proceso que no los necesite.

Del mismo modo, tenemos que usar una subcapa y usar exec cat al final, para asegurarse de que no haya un proceso de shell mintiendo sobre mantener una tubería abierta.

Con ksh (aquí ksh93 ), eso tendría que ser:

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: Eso no funcionará en sistemas donde ksh usa socketpairs en lugar de pipes , y donde /dev/fd/n funciona como en Linux.)

En ksh , fds por encima de 2 están marcados con el indicador close-on-exec, a menos que se pasen explícitamente en la línea de comando. Es por eso que no tenemos que cerrar los descriptores de archivo no utilizados como con zsh —pero también es por eso que tenemos que hacer {i1}>&$i1 y usa eval para ese nuevo valor de $i1 , para pasar a tee y cat

En bash esto no se puede hacer, porque no se puede evitar el indicador de cerrar al ejecutar.

Arriba, es relativamente simple, porque solo usamos comandos externos simples. Se vuelve más complicado cuando desea usar construcciones de shell allí en su lugar, y comienza a encontrarse con errores de shell.

Compare lo anterior con lo mismo usando canalizaciones con nombre:

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

Conclusión

Si desea interactuar con un comando, use expect o zsh 's zpty , o canalizaciones con nombre.

Si quiere hacer una plomería elegante con tuberías, use tuberías con nombre.

Los coprocesos pueden hacer algo de lo anterior, pero prepárate para rascarte la cabeza seriamente por cualquier cosa que no sea trivial.


Linux
  1. Cómo usar el comando sed de Linux

  2. Cómo usar el comando grep de Linux

  3. Cómo usar el comando de historial en Linux

  4. ¿Cómo usar el comando basename?

  5. Cómo usar el comando "pantalla" en Linux

Cómo usar el comando nmap

Cómo usar el comando fd en el sistema Linux

¿Cómo usar el comando wget en Linux?

¿Cómo usar el comando xargs en Linux?

Cómo usar el comando RPM en Linux

Cómo usar el comando which en Linux