2008-09-02 10 views
19

Supongo que tengo programas P0, P1, ... P(n-1) para unos n > 0. ¿Cómo puedo redireccionar fácilmente la salida del programa Pi al programa P(i+1 mod n) para todos los i (0 <= i < n)?Cómo hacer un lazo de tubo en bash

Por ejemplo, digamos que tengo un programa square, que lee repetidamente un número y que imprime el cuadrado de ese número, y un programa calc, que a veces se imprime un número después de lo cual se espera que sea capaz de leer la plaza de eso ¿Cómo conecto estos programas de manera que siempre que calc imprima un número, square cuadrados, lo devuelva 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. (Necesitará obtener un nombre de archivo aún no existente, crear un conducto con ese nombre, ejecutar el "bucle de tubería", limpiar el conducto nombrado). Imagine que ya no puede escribir prog1 | prog2 y que siempre tendrá que usar conductos con nombre para conectar programas .

Estoy buscando algo que sea tan fácil como escribir un tubo "normal". Por ejemplo, algo como { prog1 | prog2 } >&0 sería genial.

Respuesta

5

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 named pipes in 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.

-1

Dudo que sh/bash pueda hacerlo. ZSH sería una mejor opción, con sus funciones MULTIOS y coproc.

+1

¿Podría dar un ejemplo sobre Zsh? Estoy interesado en eso. –

1

Canalizaciones con nombre.

crear una serie de fifos, utilizando mkfifo

es decir FIFO0, FIFO1

luego coloque cada proceso en el plazo a los tubos que desee:

processn < fifo (n-1)> fifon

13

una tubería con nombre podría hacerlo:

$ mkfifo outside 
$ <outside calc | square >outside & 
$ echo "1" >outside ## Trigger the loop to start 
+0

¿Podría explicar la línea " outside &"? No estoy seguro de afuera. –

+0

Son redirecciones de shell estándar: lectura desde 'afuera' y salida a 'afuera'. afuera es un fifo, entonces todo lo que está escrito sale del lado de lectura. –

+0

Intenté este código, pero no funciona. Parece que esta línea: ' outside & 'termina inmediatamente. – RnMss

24

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 redirigir stdout a la entrada estándar para la secuencia de tubo como un todo (es decir, se vuelve a dirigir 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 conducto inicial read es necesario porque sin él, tanto el descriptor de archivo de entrada como el de salida son el mismo dispositivo 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 que la entrada de la { ... } sea una tubería normal, la redirección tiene el efecto deseado.

Para ilustrar con mi ejemplo 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 

ejecutar el código anterior muestra 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 un poco de un truco. Especialmente la parte 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 se me ocurrió nada mejor que read, ya que parece que solo se puede determinar que el ciclo de tuberías ha finalizado al tratar de escribir algo sobre él.

+1

Buena solución. Tuve que hacer algo similar usando netcat dentro de un bucle y trabajé alrededor del efecto secundario 'leer' al 'cerrar' su entrada con un 'eco'. Al final fue algo como esto: echo | leer | {P0 | ... | P (n-1); }>/dev/fd/0 –

+0

En lugar de 'echo | leer' uno podría usar un comando que termina inmediatamente, como': '(' true'), p. ': | {cmd | cmd>/dev/fd/0} '.Ejemplo: ': | {nc -lp 5000>/dev/fd/0; } 'es un servidor de eco simple que finaliza correctamente en EOF del cliente. – regnarg

-2

Una pila de comandos puede componerse como una cadena de una matriz de comandos arbitrarios y evaluarse con eval. El siguiente ejemplo se 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" 
+1

No creo que estés respondiendo la pregunta. – reinierpost

3

Mis soluciones utiliza pipexec (La mayor parte de la implementación de la función 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 [email protected] 

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 [email protected] 

El comando

pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] \ 
    "{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}" 

La salida (igual que en su 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.

Cuestiones relacionadas