bash - salida - tuberias en linux ejemplos
Cómo hacer un circuito de tuberías en bash (7)
Supongamos que tengo los programas P0
, P1
, ... P(n-1)
para algunos n > 0
. ¿Cómo puedo redireccionar fácilmente la salida del programa Pi
al programa P(i+1 mod n)
para todo i
( 0 <= i < n
)?
Por ejemplo, digamos que tengo un square
programa, que lee repetidamente un número y luego imprime el cuadrado de ese número, y un programa de calc
, que a veces imprime un número después del cual espera poder leer el cuadrado del mismo. ¿Cómo conecto estos programas de modo que cada vez que calc
imprima un número, los cuadrados lo devuelvan a calc
?
Editar: probablemente debería aclarar lo que quiero decir con "fácilmente". La solución pipe / fifo nombrada es una que realmente funciona (y he utilizado en el pasado), pero en realidad requiere bastante trabajo para hacerlo correctamente si se compara con el uso de una tubería bash. (Debe obtener un nombre de archivo aún no existente, crear una tubería con ese nombre, ejecutar el "loop de tubería", limpiar la tubería con nombre). Imagine que ya no puede escribir prog1 | prog2
prog1 | prog2
y siempre tendrá que usar canalizaciones con nombre para conectar programas.
Estoy buscando algo que es casi tan fácil como escribir un tubo "normal". Por ejemplo, algo así como { prog1 | prog2 } >&0
{ prog1 | prog2 } >&0
sería genial.
Canalizaciones con nombre.
Crea una serie de fifos, usando mkfifo
es decir, fifo0, fifo1
Luego adjunte cada proceso en término a las tuberías que desee:
procesado <fifo (n-1)> fifon
Dudo que sh / bash pueda hacerlo. ZSH sería una mejor opción, con sus funciones MULTIOS y coproc.
Esta es una pregunta muy interesante. Yo (vagamente) recuerdo una tarea muy similar en la universidad hace 17 años. Tuvimos que crear una serie de tuberías, donde nuestro código obtendría manejadores de archivos para la entrada / salida de cada tubería. Entonces el código bifurca y cierra los manejadores de archivos no utilizados.
Estoy pensando que podrías hacer algo similar con tubos con nombre en bash. Use mknod o mkfifo para crear un conjunto de tuberías con nombres únicos a los que pueda hacer referencia y luego bifurque su programa.
Una tubería con nombre podría hacerlo:
$ mkfifo outside
$ <outside calc | square >outside &
$ echo "1" >outside ## Trigger the loop to start
Después de pasar bastante tiempo ayer tratando de redirigir stdout
a stdin
, terminé con el siguiente método. No es realmente agradable, pero creo que lo prefiero sobre la solución pipe / fifo nombrada.
read | { P0 | ... | P(n-1); } >/dev/fd/0
El { ... } >/dev/fd/0
es para redirigir stdout a stdin para la secuencia de tubería como un todo (es decir, redirige la salida de P (n-1) a la entrada de P0). Usar >&0
o algo similar no funciona; esto es probablemente porque bash asume que 0
es de solo lectura mientras que no le importa escribir en /dev/fd/0
.
El canal de read
inicial es necesario porque sin él, tanto el descriptor de archivo de entrada como el de salida son el mismo dispositivo de pts (al menos en mi sistema) y la redirección no tiene ningún efecto. (El dispositivo pts no funciona como una tubería, escribir en él pone cosas en su pantalla.) Al hacer la entrada de { ... }
una tubería normal, la redirección tiene el efecto deseado.
Para ilustrar con mi ejemplo de calc
/ square
:
function calc() {
# calculate sum of squares of numbers 0,..,10
sum=0
for ((i=0; i<10; i++)); do
echo $i # "request" the square of i
read ii # read the square of i
echo "got $ii" >&2 # debug message
let sum=$sum+$ii
done
echo "sum $sum" >&2 # output result to stderr
}
function square() {
# square numbers
read j # receive first "request"
while [ "$j" != "" ]; do
let jj=$j*$j
echo "square($j) = $jj" >&2 # debug message
echo $jj # send square
read j # receive next "request"
done
}
read | { calc | square; } >/dev/fd/0
Ejecutando el código anterior se obtiene el siguiente resultado:
square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285
Por supuesto, este método es bastante pirateo. Especialmente la parte de read
tiene un efecto secundario no deseado: la terminación del circuito de tubería "real" no conduce a la terminación del conjunto. No pude pensar en nada mejor que read
ya que parece que solo puedes determinar que el ciclo de la tubería ha finalizado al tratar de escribir algo sobre él.
Una pila de comandos se puede componer como cadena de una matriz de comandos arbitrarios y se puede evaluar con eval. El siguiente ejemplo da el resultado 65536.
function square ()
{
read n
echo $((n*n))
} # ---------- end of function square ----------
declare -a commands=( ''echo 4'' ''square'' ''square'' ''square'' )
#-------------------------------------------------------------------------------
# build the command stack using pipes
#-------------------------------------------------------------------------------
declare stack=${commands[0]}
for (( COUNTER=1; COUNTER<${#commands[@]}; COUNTER++ )); do
stack="${stack} | ${commands[${COUNTER}]}"
done
#-------------------------------------------------------------------------------
# run the command stack
#-------------------------------------------------------------------------------
eval "$stack"
Mis soluciones usan pipexec (la mayoría de la implementación de funciones proviene de su respuesta):
square.sh
function square() {
# square numbers
read j # receive first "request"
while [ "$j" != "" ]; do
let jj=$j*$j
echo "square($j) = $jj" >&2 # debug message
echo $jj # send square
read j # receive next "request"
done
}
square $@
calc.sh
function calc() {
# calculate sum of squares of numbers 0,..,10
sum=0
for ((i=0; i<10; i++)); do
echo $i # "request" the square of i
read ii # read the square of i
echo "got $ii" >&2 # debug message
let sum=$sum+$ii
done
echo "sum $sum" >&2 # output result to stderr
}
calc $@
El comando
pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] /
"{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}"
El resultado (igual que en tu respuesta)
square(0) = 0
got 0
square(1) = 1
got 1
square(2) = 4
got 4
square(3) = 9
got 9
square(4) = 16
got 16
square(5) = 25
got 25
square(6) = 36
got 36
square(7) = 49
got 49
square(8) = 64
got 64
square(9) = 81
got 81
sum 285
Comentario: pipexec fue diseñado para iniciar procesos y construir tuberías arbitrarias en el medio. Debido a que las funciones de bash no se pueden manejar como procesos, existe la necesidad de tener las funciones en archivos separados y usar un bash separado.