bash: чтение с tty в конвейерном скрипте

bash: чтение с tty в конвейерном скрипте

У меня возникли некоторые проблемы с двумя 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 -pfrom script.shможет вывести свою подсказку, но строка, введенная пользователем, может быть прочитана командой read -pfrom 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

Связанный контент