命名管道停止工作
Named pipe stops working
我有两个 shell scipts:carrier.sh 和 pie.sh 以及两个命名管道:一个文件夹中的 Answer 和 Query。
carrier.sh:
while read a
do
if [ "$a" = 'exit' ] ; then exit 0
fi
echo $a
#echo $a >> troll.txt
done
pie.sh:
( ./carrier.sh > Answer ) < Query &
echo 'Foo' > Query
read x < Answer
echo $x
echo 'Boo' > Query
read x < Answer
echo $x
echo 'exit' > Query
让我们试试:
$ ./pie.sh
Foo
...
<-它在这里等待输入而不是打印 Boo 并以新提示关闭。当我添加注释行时,它工作正常。这是为什么?我认为这可能是由于管道阻塞、缓冲或未冲洗所致。我的初衷是在一个会话中通过 shell 脚本与 mysql-server 通信(并且存在相同的问题)但是为此我可以获得像 "use php" :p 这样的答案。我可以仅在 pie.sh 中更改我的代码吗?
FIFO 是一种奇特的生物。一旦打开用于写入的唯一进程退出,reader 将获得 EOF 并且必须重新打开管道以进行读取以获取更多输入。因此,在您的脚本中,carrier.sh
在读取并回显 Foo
后退出。您的 pie.sh
尝试打开 FIFO 再次写入,它挂起等待 non-existent reader 再次打开它。
我将carrier.sh
修改为:
while read a
do
if [ "$a" = 'exit' ] ; then exit 0
fi
echo $a
#echo $a >> troll.txt
done
echo "[=10=]: out of here" >&2
输出(在 Mac 运行ning macOS High Sierra 10.13.1 和古老的 Bash 3.25 上)是:
$ bash pie.sh
./carrier.sh: out of here
Foo
pie.sh: line 6: Answer: Interrupted system call
Foo
输出冻结。[=29=]
我们该如何解决?这实际上很棘手。名义上,'all' 所需要的是告诉 carrier.sh
它要从哪个 FIFO 读取以及要写入哪个 FIFO,以便它每次都可以打开它们。这意味着您需要这样的代码:
carrier.sh
input=
output=
while true
do
echo "[=12=]: ($$) about to enter inner loop" >&2
while read a
do
if [ "$a" = 'exit' ]
then
echo "[=12=]: ($$) exit" >&2
exit 0
fi
echo $a > $output
echo "[=12=]: ($$) $a echoed back to standard output" >&2
done < $input
echo "[=12=]: ($$) inner loop complete" >&2
done
echo "[=12=]: ($$) out of here" >&2
pie.sh
#( ${SHELL:-bash} ./carrier.sh > Answer ) < Query &
rm -f Query Answer
mkfifo Query Answer
( ${SHELL:-bash} ./carrier.sh Query Answer ) &
echo "[=13=] ($$): at work"
echo 'Foo' > Query
read x < Answer
echo $x
echo 'Boo' > Query
read x < Answer
echo $x
echo 'exit' > Query
echo "[=13=] ($$): finished"
还有一个例子运行:
$ bash pie.sh
pie.sh (65389): at work
./carrier.sh: (65392) about to enter inner loop
./carrier.sh: (65392) Foo echoed back to standard output
./carrier.sh: (65392) inner loop complete
./carrier.sh: (65392) about to enter inner loop
Foo
./carrier.sh: (65392) Boo echoed back to standard output
./carrier.sh: (65392) inner loop complete
Boo
./carrier.sh: (65392) about to enter inner loop
pie.sh (65389): finished
./carrier.sh: (65392) exit
$ ps
PID TTY TIME CMD
782 ttys000 0:03.59 -bash
798 ttys001 0:00.03 -bash
821 ttys002 0:03.27 -bash
$
警告!
我在测试(早期版本的)代码时遇到了一些奇怪的行为,其中有各种进程意外挂起。我在几分钟前编辑的载体脚本版本继续响应,这让我感到困惑。这就是输出中有 ps
列表的原因;它表明我用来测试的 ttys002
shell 没有 children 了。与我之前的(令人困惑的)状态对比:
$ ps
PID TTY TIME CMD
782 ttys000 0:03.59 -bash
798 ttys001 0:00.03 -bash
821 ttys002 0:03.20 -bash
65214 ttys002 0:00.00 bash pie.sh
65258 ttys002 0:00.01 ksh -x carrier.sh
65296 ttys002 0:00.00 /bin/bash -x carrier.sh
65304 ttys002 0:00.00 /bin/bash carrier.sh
65316 ttys002 0:00.00 bash pie.sh
$ kill 65316 65304 65296 65258 65214
$
这种混乱是 carrier.sh
的调试输出包含 PID 的部分原因 — 当我在编辑脚本以包含 PID 后看到没有 PID 的消息时,我最终发现了这个问题。中断不仅会杀死一切。相当 how/why 65316 在我的中断中幸存下来,我不确定;同上 65216。 carrier.sh
的各种变体也许并不那么令人惊讶。在 运行 进行测试之前,请确保您的测试环境是干净的。
另一种解决 'fix' 问题的可能方法是 pie.sh
启动一个脚本,该脚本适当地打开 FIFO,但随后进入休眠状态(没有读取或写入)。它必须是 运行 在后台。这使 FIFO 保持打开状态,并且主进程可以更自由地工作。后台进程在退出时会被 pie.sh
杀死。如果您对此进行调查,则需要仔细考虑后台进程是否打开 FIFO 进行读取、写入或两者。我还没有探究它的来龙去脉,但它应该可以工作——但如果你尝试了,请注意你的设置。 (困难的部分是确保打开操作完成;在有写入器之前,读取打开不会完成,写入打开不会完成,直到有 reader。)确保你没有有杂散的进程意外地徘徊。
我有 Ubuntu 16.04 和 gnu bash 版本 4.3.48(1)-release。我对 carrier.sh 中第一个小变化的回应是:
$ ./pie.sh
Foo
./carrier.sh: out of here
...
<- 等待输入
你后面的脚本(有效)的效果非常荒谬,因为每个 运行 pie.sh 都有另一组来自 ./carrier.sh 的回声。它是无关紧要的,因为总是有 Foo,然后是 Boo。它给了我一个这样的解决方案的提示,我原来的 pie.sh 和 carrier.sh 的变化也有效:
while true
do
while read a
do
if [ "$a" = 'exit' ] ; then exit 0
fi
echo $a
#echo $a >> troll.txt
done
#echo "[=11=]: out of here" >&2
done
文件 carrier.sh 是这样一个黑盒子,代表例如不同的内部 shells 像 mysql shell (在另一个版本的 pie.sh 我有 'mysql -b' 而不是 './carrier.sh')。它的行为方式与 carrier.sh 的错误版本相同。这表明很多依赖于这个黑盒的实现(在shell中不一定完成)。谢谢,您的回答有助于理解命名管道和重定向。每次删除和创建新的fifo很重要吗?
我有两个 shell scipts:carrier.sh 和 pie.sh 以及两个命名管道:一个文件夹中的 Answer 和 Query。 carrier.sh:
while read a
do
if [ "$a" = 'exit' ] ; then exit 0
fi
echo $a
#echo $a >> troll.txt
done
pie.sh:
( ./carrier.sh > Answer ) < Query &
echo 'Foo' > Query
read x < Answer
echo $x
echo 'Boo' > Query
read x < Answer
echo $x
echo 'exit' > Query
让我们试试:
$ ./pie.sh
Foo
...
<-它在这里等待输入而不是打印 Boo 并以新提示关闭。当我添加注释行时,它工作正常。这是为什么?我认为这可能是由于管道阻塞、缓冲或未冲洗所致。我的初衷是在一个会话中通过 shell 脚本与 mysql-server 通信(并且存在相同的问题)但是为此我可以获得像 "use php" :p 这样的答案。我可以仅在 pie.sh 中更改我的代码吗?
FIFO 是一种奇特的生物。一旦打开用于写入的唯一进程退出,reader 将获得 EOF 并且必须重新打开管道以进行读取以获取更多输入。因此,在您的脚本中,carrier.sh
在读取并回显 Foo
后退出。您的 pie.sh
尝试打开 FIFO 再次写入,它挂起等待 non-existent reader 再次打开它。
我将carrier.sh
修改为:
while read a
do
if [ "$a" = 'exit' ] ; then exit 0
fi
echo $a
#echo $a >> troll.txt
done
echo "[=10=]: out of here" >&2
输出(在 Mac 运行ning macOS High Sierra 10.13.1 和古老的 Bash 3.25 上)是:
$ bash pie.sh
./carrier.sh: out of here
Foo
pie.sh: line 6: Answer: Interrupted system call
Foo
输出冻结。[=29=]
我们该如何解决?这实际上很棘手。名义上,'all' 所需要的是告诉 carrier.sh
它要从哪个 FIFO 读取以及要写入哪个 FIFO,以便它每次都可以打开它们。这意味着您需要这样的代码:
carrier.sh
input=
output=
while true
do
echo "[=12=]: ($$) about to enter inner loop" >&2
while read a
do
if [ "$a" = 'exit' ]
then
echo "[=12=]: ($$) exit" >&2
exit 0
fi
echo $a > $output
echo "[=12=]: ($$) $a echoed back to standard output" >&2
done < $input
echo "[=12=]: ($$) inner loop complete" >&2
done
echo "[=12=]: ($$) out of here" >&2
pie.sh
#( ${SHELL:-bash} ./carrier.sh > Answer ) < Query &
rm -f Query Answer
mkfifo Query Answer
( ${SHELL:-bash} ./carrier.sh Query Answer ) &
echo "[=13=] ($$): at work"
echo 'Foo' > Query
read x < Answer
echo $x
echo 'Boo' > Query
read x < Answer
echo $x
echo 'exit' > Query
echo "[=13=] ($$): finished"
还有一个例子运行:
$ bash pie.sh
pie.sh (65389): at work
./carrier.sh: (65392) about to enter inner loop
./carrier.sh: (65392) Foo echoed back to standard output
./carrier.sh: (65392) inner loop complete
./carrier.sh: (65392) about to enter inner loop
Foo
./carrier.sh: (65392) Boo echoed back to standard output
./carrier.sh: (65392) inner loop complete
Boo
./carrier.sh: (65392) about to enter inner loop
pie.sh (65389): finished
./carrier.sh: (65392) exit
$ ps
PID TTY TIME CMD
782 ttys000 0:03.59 -bash
798 ttys001 0:00.03 -bash
821 ttys002 0:03.27 -bash
$
警告!
我在测试(早期版本的)代码时遇到了一些奇怪的行为,其中有各种进程意外挂起。我在几分钟前编辑的载体脚本版本继续响应,这让我感到困惑。这就是输出中有 ps
列表的原因;它表明我用来测试的 ttys002
shell 没有 children 了。与我之前的(令人困惑的)状态对比:
$ ps
PID TTY TIME CMD
782 ttys000 0:03.59 -bash
798 ttys001 0:00.03 -bash
821 ttys002 0:03.20 -bash
65214 ttys002 0:00.00 bash pie.sh
65258 ttys002 0:00.01 ksh -x carrier.sh
65296 ttys002 0:00.00 /bin/bash -x carrier.sh
65304 ttys002 0:00.00 /bin/bash carrier.sh
65316 ttys002 0:00.00 bash pie.sh
$ kill 65316 65304 65296 65258 65214
$
这种混乱是 carrier.sh
的调试输出包含 PID 的部分原因 — 当我在编辑脚本以包含 PID 后看到没有 PID 的消息时,我最终发现了这个问题。中断不仅会杀死一切。相当 how/why 65316 在我的中断中幸存下来,我不确定;同上 65216。 carrier.sh
的各种变体也许并不那么令人惊讶。在 运行 进行测试之前,请确保您的测试环境是干净的。
另一种解决 'fix' 问题的可能方法是 pie.sh
启动一个脚本,该脚本适当地打开 FIFO,但随后进入休眠状态(没有读取或写入)。它必须是 运行 在后台。这使 FIFO 保持打开状态,并且主进程可以更自由地工作。后台进程在退出时会被 pie.sh
杀死。如果您对此进行调查,则需要仔细考虑后台进程是否打开 FIFO 进行读取、写入或两者。我还没有探究它的来龙去脉,但它应该可以工作——但如果你尝试了,请注意你的设置。 (困难的部分是确保打开操作完成;在有写入器之前,读取打开不会完成,写入打开不会完成,直到有 reader。)确保你没有有杂散的进程意外地徘徊。
我有 Ubuntu 16.04 和 gnu bash 版本 4.3.48(1)-release。我对 carrier.sh 中第一个小变化的回应是:
$ ./pie.sh
Foo
./carrier.sh: out of here
...
<- 等待输入
你后面的脚本(有效)的效果非常荒谬,因为每个 运行 pie.sh 都有另一组来自 ./carrier.sh 的回声。它是无关紧要的,因为总是有 Foo,然后是 Boo。它给了我一个这样的解决方案的提示,我原来的 pie.sh 和 carrier.sh 的变化也有效:
while true
do
while read a
do
if [ "$a" = 'exit' ] ; then exit 0
fi
echo $a
#echo $a >> troll.txt
done
#echo "[=11=]: out of here" >&2
done
文件 carrier.sh 是这样一个黑盒子,代表例如不同的内部 shells 像 mysql shell (在另一个版本的 pie.sh 我有 'mysql -b' 而不是 './carrier.sh')。它的行为方式与 carrier.sh 的错误版本相同。这表明很多依赖于这个黑盒的实现(在shell中不一定完成)。谢谢,您的回答有助于理解命名管道和重定向。每次删除和创建新的fifo很重要吗?