Tengo bastantes problemas con dos scripts bash en los que estoy trabajando:
guión.sh
#!/bin/bash
while true; do
read -p "script: read: var: " var
echo "script: write: var: $var"
done
pipa.sh
#!/bin/bash
while read line; do
echo "pipe: read: line: (( $line ))"
read -p "pipe: read var: " var < /dev/tty
echo "pipe: write: var: $var"
done< <(cat)
Al ejecutar script.sh
y canalizar la salida, pipe.sh
obtengo el siguiente resultado:
$ ./script.sh | ./pipe.sh
1: script: read: var: 123 # user entering '123'
2: script: read: var: pipe: read: line: (( script: write: var: 123 ))
3: pipe: read var: 321 # user entering '321'
4: script: read: var: 456 # user entering '456'
5: pipe: write: var: 456
6: pipe: read: line: (( script: write: var: 321 ))
7: pipe: read var:
Como puede ver, todo parece funcionar hasta llegar a la línea 4. Esperaba que la línea 4 fuera pipe: write: var: 321
de pipe.sh
. En lugar de eso, recibo el mensaje de script.sh
.
Al ingresar la cadena "456", se ejecuta la línea esperada anteriormente, pero con la cadena incorrecta (esperada: "321", obtuve "456"). Además, la línea 6 no imprime "456" sino "321".
Algo anda totalmente mal aquí. ¿Alguna sugerencia sobre cómo solucionar este problema y por qué sucede?
Actualizar:
Básicamente, me gustaría que la tubería funcione de la misma manera que el siguiente código.
script1.sh
#!/bin/bash
while true; do
read -p "val1: " val1
script2.sh "${val1}"
done
script2.sh
#!/bin/bash
val1="${1}"
read -p "val2: " val2
echo "${val1} ${val2}"
Sin embargo, no quiero codificar script2.sh
en script1.sh
. Podría pasarlo script2.sh
como argumento, script1.sh
pero inicialmente pensé que una tubería sería una mejor solución.
Respuesta1
Las read -p
llamadas en ambos script.sh
se pipe.sh
leen desde la terminal actual, y dado que los comandos en una canalización se ejecutan en paralelo, no se pueden hacer suposiciones sobre cuál de ellos es el primero en arrebatar los datos ingresados por el usuario.
El read -p
remitente script.sh
puede mostrar su mensaje, pero el remitente puede leer la cadena ingresada por el usuario read -p
y pipe.sh
viceversa.
En una tubería como a | b
, b
se podría hacer fácilmente esperar la entrada a
antes de continuar, pero lo contrario no es cierto: dado que las tuberías están almacenando en búfer, a
tendría que escribir una gran cantidad de datos antes de darse cuenta de que b
no está leyendo ninguno de ellos.
Una forma de solucionar esto es conectar la salida estándar de b
con la entrada estándar de a
en una especie de "tubería circular" y modificar a
( script.sh
) para que también espere la entrada de la entrada estándar, tal como lo hace b
( pipe.sh
).
Debido a las limitaciones del lenguaje shell, debes usar untubería con nombrepara eso. Ejemplo sencillo:
cat > circpipe <<'EOT'; chmod 755 circpipe
fifo=$(mktemp -u)
mkfifo "$fifo" || exit 1
exec 3<>"$fifo" >"$fifo" <"$fifo"
rm "$fifo"
echo trigger
"$1" | "$2"
EOT
cat > pipe-head <<'EOT'; chmod 755 pipe-head
while read next; do
read -p "HEAD's prompt>> " var </dev/tty || exit
echo "$var"
done
EOT
cat > pipe-tail <<'EOT'; chmod 755 pipe-tail
while read input; do
echo >&2 " TAIL'input: $input"
read -p " TAIL's prompt>> " var </dev/tty
echo >&2 " TAIL processing <$var>"
echo next # trigger the head of the pipeline
done
EOT
./circpipe ./pipe-head ./pipe-tail
HEAD's prompt>> foo
TAIL'input: foo
TAIL's prompt>> bar
TAIL processing <bar>
HEAD's prompt>> baz
TAIL'input: baz
TAIL's prompt>> quux
TAIL processing <quux>
HEAD's prompt>> ^D$
Elcircpipe
El script podría convertirse en una herramienta más general, que aceptaría comandos de shell regulares y donde su "cola" también podría salirse del bucle.
A diferencia del ejemplo anterior, esto no "pondrá en marcha" el ciclo de forma predeterminada; para eso -command
se debe utilizar un argumento. Uso de ejemplo:
./circpipe -echo './pipe-head | stdbuf -oL sed s/e/o/g | ./pipe-tail'
HEAD's prompt>> pee
TAIL'input: poo
TAIL's prompt>> lol
TAIL processing <lol>
HEAD's prompt>>^D
tubo de escape
#! /bin/sh
# usage: circpipe [-start_command] shell_command
# run 'shell_command' with its stdin connected to its stdout
# via a FIFO
# if 'start_command' is given, run it before `shell_command`
# with its stdout redirected to the same FIFO
[ "$#" -gt 0 ] || exit 0
fifo=$(mktemp -u)
mkfifo "$fifo" || exit 1
exec 3<>"$fifo" >"$fifo" 3<"$fifo"
rm "$fifo"
case $1 in -*) eval "${1#-}"; shift; esac
IFS='|'; eval "<&3 $* &"
exec >&-
wait