PAYATHON 2026

如何在终端或 shell 中持续读取 pipe、socket 或 FIFO 而不退出?

技术老齐

在 Linux 上,可以用 FIFO 让读取命令在外部写入进程退出后继续等待。先用 mkfifo 创建 FIFO,再让 cat 以读写方式打开它:

cat <> /run/thing

<> 是 shell 的读写重定向。外部写入进程退出后,cat 自己仍持有 FIFO 的写端,因此会继续等待后续数据,不会随这次写入结束而退出。

使用前,/run/thing 必须已经是 FIFO,且当前用户有读写权限。这种打开方式在 Linux 上可用,但 POSIX 没有规定其行为,不能假定所有 Unix 系统都支持。

为什么普通的 cat 会退出?

运行下面的命令时,cat 只打开 FIFO 的读端:

cat /run/thing

在另一个终端执行:

echo "Text." > /run/thing

shell 会打开 FIFO 的写端,写入文本,然后关闭写端。

FIFO 中的数据读完,且所有写端都关闭后,读取操作会返回 EOF,cat 随之正常退出。如果仍有写端保持打开,阻塞读取就会继续等待后续数据。cat <> /run/thing 利用的就是这个机制。

两个终端的完整示例

普通用户通常不能直接在 /run 下创建文件。可以先在用户主目录下试验,确认行为后再换成实际路径。

在终端 A 中执行:

mkfifo "$HOME/thing.fifo" && cat <> "$HOME/thing.fifo"

命令会保持运行,等待数据。

在终端 B 中依次执行:

printf '%s\n' '第一条消息' > "$HOME/thing.fifo"
printf '%s\n' '第二条消息' > "$HOME/thing.fifo"

终端 A 会显示:

第一条消息
第二条消息

每个 printf 执行完都会关闭自己的写端,终端 A 中的 cat 则会继续等待,不需要额外的循环或定期轮询。

要停止读取,可以在终端 A 按 Ctrl-C,或向 cat 进程发送终止信号。确认不再使用该 FIFO 后,可以删除它:

rm "$HOME/thing.fifo"

如果 FIFO 已经存在,mkfifo 会报错,上面的 && 会阻止 cat 启动。确认现有路径确实是 FIFO 后,可以直接运行:

cat <> "$HOME/thing.fifo"

首次使用时,要先创建 FIFO。路径不存在时,<> 可能创建普通文件,而普通文件不具有这里需要的 FIFO 行为。

如果需要使用 Unix domain socket

Unix domain socket 也可以满足需求,但发送方必须通过 socket 建立连接,不能直接用 shell 重定向写入:

printf 'Text.\n' > /path/to/socket

安装了 socat 后,可以在终端 A 中运行:

socat -u UNIX-LISTEN:"$HOME/thing.sock",fork STDOUT

在终端 B 中发送数据:

printf '%s\n' '第一条消息' |
  socat -u STDIN UNIX-CONNECT:"$HOME/thing.sock"

printf '%s\n' '第二条消息' |
  socat -u STDIN UNIX-CONNECT:"$HOME/thing.sock"

UNIX-LISTEN 创建监听 socket,fork 让子进程处理每次连接,父进程继续接受后续连接,因此一次发送结束后,监听进程仍会运行。

这个方案要求 socket 路径可用。如果上次运行留下了路径,应先确认没有进程仍在使用,再清理并重新启动。如果需要让其他进程通过 shell 重定向写入、在终端持续显示数据,使用 FIFO 更直接。

使用时需要注意什么?

  • FIFO 不保存历史数据。它的内核缓冲区容量有限;缓冲区满时,阻塞写入会等待读取方读走数据。
  • FIFO 是字节流,没有消息边界。多个进程同时写入时,较大的写入可能交错。如果需要保证一次写入不被其他写入穿插,应遵守 PIPE_BUF 的原子写入限制。
  • 多个读取进程会分摊 FIFO 中的数据。如果希望多个终端都收到完整副本,需要额外的广播机制。
  • cat 会原样输出字节。二进制数据和终端控制序列不一定显示为可读文本,也可能改变终端的显示状态。

这种方式解决了外部写入者关闭后读取命令随之退出的问题。权限错误、I/O 错误或终止信号仍可能结束进程;单纯写入某段普通文本不会产生 FIFO 的 EOF。

备注:内容仅供参考。