我正在处理的两个 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.sh
作为参数传递给,script1.sh
但我最初认为管道会是更好的解决方案?
答案1
read -p
两者中的调用和script.sh
从pipe.sh
当前终端读取,并且由于管道中的命令并行运行,因此您不能假设它们中的哪一个首先抢夺用户输入的数据。
from可以发出提示,但用户输入的字符串可以由from读取read -p
,反之亦然。script.sh
read -p
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