我有一个用于处理数据的 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
等等。