如何从通过管道接收数据的脚本中退出另一个程序?

如何从通过管道接收数据的脚本中退出另一个程序?

我有一个用于处理数据的 Bash 脚本,该脚本是通过管道从另一个进程发送的。

我正在尝试添加一个在满足特定条件后导致并退出的功能,其简化表示为:

( while true; do date; sleep 1; done ) | ( for ((i = 0; i < 3; i++)); do read -r line; echo LINE: $line; sleep 1; done; exit 1 )

真实案例中:

  • 管道之后的命令包含在脚本中(并被脚本替换);
  • 管道之前的命令是二进制可执行文件,我无法控制其内部结构。

我也尝试关闭标准输入:

( while true; do date; sleep 1; done ) | ( for ((i = 0; i < 3; i++)); do read -r line; echo LINE: $line; sleep 1; done; exec 0<&- )

但是,当条件满足时,不会向 stdout 发送任何内容,但左侧程序仍然运行。

我怎样才能完成任务?

答案1

您的管道不退出的原因是该exit 1语句仅终止右侧子 shell。

右侧退出后,date用于生成数据的命令将开始失败,因为它无法将其输出写入任何地方(它将被信号杀死PIPE)。您没有测试 的退出状态date,因此您看不到这一点。

将左侧重写为

(
    while true; do
        if ! date; then
            exit    # or: break
        fi
        sleep 1
    done
)

或作为

(
    while date; do
        sleep 1
    done
)

将使整个管道的行为符合您的预期。

这表明管道左侧的数据生成二进制文件没有检查写入是否成功(并且它也可能忽略该PIPE信号)。关闭右侧子 shell 中的标准输入不会有帮助(无论如何,当您退出该子 shell 时,这已经完成了)。

这是生成数据的程序中的错误。

你该如何解决这个问题?

一种方法是使用命名管道。这将允许您获取数据生成进程的 PID,稍后您可以使用它来终止它:

#!/bin/bash

rm -f data.pipe
mkfifo data.pipe

( while true; do date; sleep 1; done ) >data.pipe & pid=$!

(
    for ((i = 0; i < 3; i++)); do
        read -r line
        printf 'LINE: %s\n' "$line"
        sleep 1
    done
    kill "$pid"
    exit 1
) <data.pipe

如果程序忽略该PIPE信号,它很可能会忽略其他信号,因此您必须TERM首先尝试发送默认信号,如果这不起作用,请尝试INT等等。

相关内容