在 Bash 中使用命名管道 (FIFO)

作者:Mitch Frazier

如果不使用一两个管道,就很难编写重要的 bash 脚本。另一方面,命名管道则很少见。

与未命名/匿名管道一样,命名管道提供了一种 IPC(进程间通信)形式。对于匿名管道,只有一个读取者和一个写入者,但命名管道则不需要——任何数量的读取者和写入者都可以使用该管道。

命名管道在文件系统中可见,可以像其他文件一样进行读取和写入

$ ls -la /tmp/testpipe
prw-r--r-- 1 mitch users 0 2009-03-25 12:06 /tmp/testpipe|

为什么要在 shell 脚本中使用命名管道?一种情况可能是,您有一个通过 cron 运行的备份脚本,并且在它完成后,您想要关闭系统。如果您从备份脚本执行关机操作,cron 永远不会看到备份脚本完成,因此它永远不会发送包含备份作业输出的电子邮件。您可以在备份“应该”完成后通过另一个 cron 作业进行关机,但是您会面临偶尔过早关机的风险,或者您必须使延迟时间比大多数情况下需要的更长。

使用命名管道,您可以同时启动备份和关机 cron 作业,并让关机作业等待备份写入命名管道。当关机作业从管道读取内容时,它会暂停几分钟,以便 cron 电子邮件可以发送出去,然后它会关闭系统。

当然,前面的示例可能可以通过简单地创建一个常规文件来发出备份已完成的信号来相当可靠地完成。一个更复杂的例子可能是,如果您有一个每小时左右唤醒一次的备份,并读取一个命名管道来查看它是否应该运行。然后,您可以在每次对要备份的文件进行大量更改时,将一些内容写入管道。您甚至可以将要备份的文件的名称写入管道,这样备份就不必检查所有内容。

命名管道通过以下方式创建:mkfifo或者mknod:

$ mkfifo /tmp/testpipe
$ mknod /tmp/testpipe p

以下 shell 脚本从管道读取数据。它首先创建管道(如果它不存在),然后在循环中读取,直到它看到“quit”。

#!/bin/bash

pipe=/tmp/testpipe

trap "rm -f $pipe" EXIT

if [[ ! -p $pipe ]]; then
    mkfifo $pipe
fi

while true
do
    if read line <$pipe; then
        if [[ "$line" == 'quit' ]]; then
            break
        fi
        echo $line
    fi
done

echo "Reader exiting"

以下 shell 脚本写入由读取脚本创建的管道。首先,它检查以确保管道存在,然后写入管道。如果脚本有参数,它会将参数写入管道;否则,它会写入“Hello from PID”。

#!/bin/bash

pipe=/tmp/testpipe

if [[ ! -p $pipe ]]; then
    echo "Reader not running"
    exit 1
fi


if [[ "$1" ]]; then
    echo "$1" >$pipe
else
    echo "Hello from $$" >$pipe
fi

运行脚本会产生

$ sh rpipe.sh &
[3] 23842
$ sh wpipe.sh
Hello from 23846
$ sh wpipe.sh
Hello from 23847
$ sh wpipe.sh
Hello from 23848
$ sh wpipe.sh quit
Reader exiting

注意:最初我将read命令直接放在读取脚本的 while 循环中,但是read命令通常在读取两到三次后返回一个非零状态,导致循环终止。

while read line <$pipe
do
    if [[ "$line" == 'quit' ]]; then
        break
    fi
    echo $line
done

Mitch Frazier 是 Emerson Electric Co. 的嵌入式系统程序员。自 2000 年代初以来,Mitch 一直是 Linux Journal 的贡献者和朋友。

加载 Disqus 评论