bash: パイプ スクリプトで tty から読み取る

bash: パイプ スクリプトで tty から読み取る

私が取り組んでいる 2 つの bash スクリプトでかなり問題があります:

スクリプト

#!/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.sh

#!/bin/bash
val1="${1}"
read -p "val2: " val2
echo "${val1} ${val2}"

script2.shただし、にハードコードしたくありません。を引数としてscript1.sh渡すことはできますが、当初はパイプの方が良い解決策だと思いました。script2.shscript1.sh

答え1

read -pと の両方の呼び出しは現在の端末からscript.sh読み取りpipe.sh、パイプライン内のコマンドは並列に実行されるため、ユーザーが入力したデータをどちらが最初に取得するかを推測することはできません。

fromread -p側はscript.shプロンプトを表示する場合がありますが、ユーザーが入力した文字列はread -pfrom 側によって読み取られる可能性がありpipe.sh、その逆も同様です。

のようなパイプラインではa | bbからの入力を待ってから先に進むように簡単にできますaが、その逆は成り立ちません。パイプはバッファリングしているため、がデータをまったく読み取っていないaことに気づく前に、 は大量のデータを書き込む必要がありますb

この問題を解決する 1 つの方法は、 の stdout を のbstdin とa一種の「循環パイプライン」で接続し、 のa( ) を変更して、 の( )script.shと同様に stdin からの入力も待機するようにすることです。 bpipe.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$

circpipeスクリプトは、通常のシェル コマンドを受け入れ、その「末尾」でループから抜け出すこともできる、より一般的なツールにすることができます。

上記の例とは異なり、デフォルトではループを「開始」しません。そのためには、引数を-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

関連情報