命名管道停止工作

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很重要吗?