我需要开始一个过程,让我们说foo
。我希望看到 stdout/stderr 正常,但grep
stderr for string bar
。一旦bar
在 stderr 中发现foo
应该被杀死。
这可能吗?
我最初编写了一种涉及流混合的方法,但它不是很好。一些评论与该版本有关。如果您好奇,请查看历史。
这是一种方法:
(PIDFILE=$(mktemp /tmp/foo.XXXXXX) && trap "rm $PIDFILE" 0 \
&& { foo \
2> >(tee >(grep -q bar && kill $(cat $PIDFILE)) >&2) \
& PID=$! && echo $PID >$PIDFILE ; wait $PID || true; })
老式的噩梦燃料。这里发生了什么事?
mktemp
并将其命名为PIDFILE
PIDFILE
,再次用于 hygeinefoo
;这是在复合语句中完成的,因此&
绑定到foo
而不是绑定到整个前面的管道foo
的标准错误重定向到等待bar
出现然后终止的进程替换foo
(稍后会更多)foo
到一个变量中,将其写入以 命名的文件中PIDFILE
,然后等待它,这样整个命令foo
在自己退出之前等待退出;丢弃发生这种情况时的|| true
错误退出状态foo
。进程替换中的代码如下工作:
tee
输入(foo
的标准错误),将tee
的标准输出重定向到标准错误,这样foo
的标准错误确实出现在标准错误上grep -q
在输入上运行,它查找指定的模式,并在找到它时立即退出(或者当它到达流的末尾时),不打印任何东西,之后(如果它找到字符串并成功退出)外壳继续...kill
在名为的文件中捕获PID的进程PIDFILE
,即foo
汤姆安德森的回答非常好,但kill $(cat $PIDFILE)
只有在我的系统foo
上自行终止或通过 Ctrl-C 终止时才会发生。以下解决方案对我有用
while read g
do
if [[ $g =~ bar ]]
then
kill $!
fi
done < <(
exec foo 2> >(tee /dev/tty)
)
Expect 旨在根据流程的输出采取行动。最简单的解决方案是让 Expect 启动进程,然后在看到预期输出时退出。例如:
expect -c 'set msg {Saw "foo" on stderr. Exiting process.}
spawn /bin/bash -c "echo foo >&2; sleep 10"
expect "foo" { puts $msg; exit }'
如果生成的进程正常结束(例如,在看到“foo”之前),那么 Expect 脚本也会退出。
作为另一个答案的替代方案,一种方法是使用 bash 的coproc
工具:
{coproc FOO { foo; } 2>&1 1>&3; } 3>&1
CHILD=$!
while read line <&${FOO[0]}; do
if echo "$line" | grep -q bar; then
kill $CHILD
else
echo "$line"
fi
done
不过,这显然是特定于 bash 的。
实际上,我设法找到了一种无需 PID 文件或协同例程的方法,并且可以在所有与 POSIX 兼容的 shell 中工作(我已经尝试过bash
)dash
。至少在支持的系统上/dev/fd/
,但这应该是几乎所有的系统。
不过,这有点令人费解,所以我不确定它是否符合您的喜好。
( # A
( # B
( /tmp/foo 2>&1 1>&3 & echo $! >&4 ) | # C
( tee /dev/fd/2 | ( grep -q bar && echo fin >&4 ) ) # D and E
) 4>&1 | ( # F
read CHILD
read STATUS
if [ "$STATUS" = fin ]; then
kill $CHILD
fi
)
) 3>&1
解释这里使用的众多子shell:
使用复制到 fd 3 的正常标准输出运行的主体A
。它运行子shell B
,F
并将标准输出通过B
管道传输到F
.
在 fd 4 上重复B
的管道运行的主体。A
C
运行您的实际 foo 命令,其 stderr 连接到来自C
to的管道D
,其标准输出从 fd 3 复制;即恢复到全局标准输出。然后将 PID 写入foo
fd 4;也就是说,到 subshellF
在其标准输入上的管道。
D
运行一个tee
命令,从管道接收foo
其 stderr 上打印的任何内容。它将输出复制到/dev/fd/2
(为了让它显示在全局标准错误上)和连接到 subshell 的管道E
。
E
grep 查找 bar,然后在找到时写入fin
fd 4,即写入F
其标准输入的管道。请注意,如果 grep 遇到 EOF 而没有找到,请&&
确保写入 no 。fin
bar
F
,然后,从 中读取 PID 并从 中读取C
终止fin
符E
。如果fin
终止符被正确输出,它会杀死foo
.
编辑:修复了tee
将 foo 的 stderr 复制到真正的 stderr 的缺失。