У меня возникли некоторые проблемы с двумя bash-скриптами, над которыми я работаю:
скрипт.sh
#!/bin/bash
while true; do
read -p "script: read: var: " var
echo "script: write: var: $var"
done
труба.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)
При выполнении script.sh
и передаче вывода pipe.sh
я получаю следующий вывод:
$ ./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:
Как видите, все работает, пока не дошел до строки 4. Я ожидал, что строка 4 будет pipe: write: var: 321
от pipe.sh
. Вместо этого я получаю приглашение от script.sh
.
При вводе строки "456" выполняется ожидаемая ранее строка, но с неверной строкой (ожидалось: "321", получено "456"). Кроме того, строка 6 печатает не "456", а "321".
Что-то здесь совсем не так. Есть предложения, как это исправить и почему это происходит?
Обновлять:
По сути, я бы хотел, чтобы труба работала так же, как в коде ниже.
скрипт1.sh
#!/bin/bash
while true; do
read -p "val1: " val1
script2.sh "${val1}"
done
скрипт2.ш
#!/bin/bash
val1="${1}"
read -p "val2: " val2
echo "${val1} ${val2}"
Однако я не хочу жестко кодировать script2.sh
в script1.sh
. Я мог бы передать script2.sh
в качестве аргумента , script1.sh
но изначально я думал, что канал будет лучшим решением?
решение1
Вызовы read -p
в обоих script.sh
случаях pipe.sh
считываются с текущего терминала, и поскольку команды в конвейере выполняются параллельно, вы не можете сделать никаких предположений о том, какая из них первой извлечет данные, введенные пользователем.
Команда read -p
from script.sh
может вывести свою подсказку, но строка, введенная пользователем, может быть прочитана командой read -p
from pipe.sh
и наоборот.
В конвейере, подобном a | b
, b
можно было бы легко заставить его ждать ввода от , a
прежде чем продолжить, но обратное неверно: поскольку конвейеры буферизуются, a
пришлось бы записать много данных, прежде чем было бы заметно, что b
они не считываются.
Одним из способов решения этой проблемы является соединение stdout b
с stdin a
в своего рода «кольцевой конвейер» и изменение a
( script.sh
) так, чтобы он также ожидал ввода со stdin, как это делает b
( pipe.sh
).
Из-за ограничений языка оболочки вам следует использоватьименованный каналдля этого. Простой пример:
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$
Thecircpipe
скрипт можно превратить в более общий инструмент, который будет принимать обычные команды оболочки, и где его «хвост» также сможет выходить из цикла.
В отличие от примера выше, это не «запустит» цикл по умолчанию; для этого -command
следует использовать аргумент. Пример использования:
./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
круговая труба
#! /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