bash:從管道腳本中的 tty 讀取

bash:從管道腳本中的 tty 讀取

我正在處理的兩個 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 行之前,一切似乎都正常pipe: write: var: 321pipe.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.shscript1.sh.我可以script2.sh作為參數傳遞給,script1.sh但我最初認為管道會是更好的解決方案?

答案1

read -p兩者中的呼叫和script.shpipe.sh當前終端讀取,並且由於管道中的命令並行運行,因此您不能假設它們中的哪一個首先搶奪用戶輸入的資料。

fromread -p可能script.sh會發出提示,但使用者輸入的字串可能會被read -pfrom讀取pipe.sh,反之亦然。

在像這樣的管道中a | bb可以輕鬆等待輸入,a然後再繼續進行,但反之則不然:由於管道正在緩衝,因此必須在註意到沒有讀取任何資料a之前寫入大量資料。b

解決這個問題的一種方法是將 stdoutb與 stdin連接a在一種「循環管道」中,並修改a( script.sh) 以等待來自 stdin 的輸入,就像b( pipe.sh) 所做的那樣。

由於 shell 語言的限制,您應該使用命名管道為了那個原因。簡單的例子:

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腳本可以製作成一個更通用的工具,它可以接受常規的 shell 命令,並且它的「尾部」也可以跳出循環。

與上面的範例不同,預設情況下這不會「啟動」循環;為此,-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

相關內容