我正在處理的兩個 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: 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.sh
作為參數傳遞給,script1.sh
但我最初認為管道會是更好的解決方案?
答案1
read -p
兩者中的呼叫和script.sh
從pipe.sh
當前終端讀取,並且由於管道中的命令並行運行,因此您不能假設它們中的哪一個首先搶奪用戶輸入的資料。
fromread -p
可能script.sh
會發出提示,但使用者輸入的字串可能會被read -p
from讀取pipe.sh
,反之亦然。
在像這樣的管道中a | b
,b
可以輕鬆等待輸入,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