Estou tendo alguns problemas com dois scripts bash nos quais estou trabalhando:
script.sh
#!/bin/bash
while true; do
read -p "script: read: var: " var
echo "script: write: var: $var"
done
tubo.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)
Ao executar script.sh
e canalizar a saída, pipe.sh
obtenho a seguinte saída:
$ ./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 você pode ver, tudo parece funcionar até chegar à linha 4. Eu esperava que a linha 4 fosse pipe: write: var: 321
de pipe.sh
. Em vez disso, recebo o prompt de script.sh
.
Ao inserir a string "456" a linha anteriormente esperada é executada, mas com a string errada (esperado: "321", obtido "456"). Além disso, a linha 6 não imprime "456", mas sim "321".
Algo está totalmente errado aqui. Alguma sugestão sobre como corrigir isso e por que isso está acontecendo?
Atualizar:
Essencialmente, gostaria que o pipe funcionasse da mesma maneira que o código abaixo.
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}"
No entanto, não quero codificar script2.sh
em script1.sh
. Eu poderia passar script2.sh
como argumento, script1.sh
mas inicialmente pensei que um cano seria uma solução melhor.
Responder1
As read -p
chamadas em ambos script.sh
são pipe.sh
lidas no terminal atual e, como os comandos em um pipeline são executados em paralelo, você não pode fazer suposições sobre qual deles será o primeiro a capturar os dados inseridos pelo usuário.
O read -p
from script.sh
pode exibir seu prompt, mas a string inserida pelo usuário pode ser lida pelo read -p
from pipe.sh
e vice-versa.
Em um pipeline como a | b
, b
poderia ser facilmente feito esperar pela entrada a
antes de prosseguir, mas o inverso não é verdadeiro: como os pipes estão armazenando em buffer, a
seria necessário gravar muitos dados antes de perceber que b
não está lendo nada deles.
Uma maneira de sair disso é conectar o stdout of b
com o stdin of a
em uma espécie de "pipeline circular" e modificar a
( script.sh
) para também aguardar a entrada do stdin, assim como b
( pipe.sh
) faz.
Devido às limitações da linguagem shell, você deve usar umtubo nomeadopor isso. Exemplo simples:
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$
Ocircpipe
script poderia ser transformado em uma ferramenta mais geral, que aceitaria comandos shell regulares e onde sua "cauda" também poderia sair do loop.
Ao contrário do exemplo acima, isso não irá "iniciar" o loop por padrão; para isso, um -command
argumento deve ser usado. Exemplo de uso:
./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
circuito
#! /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