9

我需要开始一个过程,让我们说foo。我希望看到 stdout/stderr 正常,但grepstderr for string bar。一旦bar在 stderr 中发现foo应该被杀死。

这可能吗?

4

5 回答 5

5

我最初编写了一种涉及流混合的方法,但它不是很好。一些评论与该版本有关。如果您好奇,请查看历史。

这是一种方法:

(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; })

老式的噩梦燃料。这里发生了什么事?

  1. 最外面的括号把整个东西放在一个子shell中;出于卫生的目的,这限制了变量的范围
  2. 我们使用适用于 GNU 和 BSD 的语法创建一个临时文件,mktemp并将其命名为PIDFILE
  3. 我们设置了一个包罗万象的退出陷阱(当最外层的子shell退出时运行)以删除名为的文件PIDFILE,再次用于 hygeine
  4. 我们跑foo;这是在复合语句中完成的,因此&绑定到foo而不是绑定到整个前面的管道
  5. 我们将foo的标准错误重定向到等待bar出现然后终止的进程替换foo(稍后会更多)
  6. 我们将 的 PID 捕获foo到一个变量中,将其写入以 命名的文件中PIDFILE,然后等待它,这样整个命令foo在自己退出之前等待退出;丢弃发生这种情况时的|| true错误退出状态foo

进程替换中的代码如下工作:

  1. 一、tee输入(foo的标准错误),将tee的标准输出重定向到标准错误,这样foo的标准错误确实出现在标准错误上
  2. 将输入的副本发送到一个文件去另一个进程替换(进程替换的进程替换
  3. 在更深层次的进程替换中,首先grep -q在输入上运行,它查找指定的模式,并在找到它时立即退出(或者当它到达流的末尾时),不打印任何东西,之后(如果它找到字符串并成功退出)外壳继续...
  4. kill在名为的文件中捕获PID的进程PIDFILE,即foo
于 2012-12-30T20:13:33.937 回答
5

汤姆安德森的回答非常好,但kill $(cat $PIDFILE)只有在我的系统foo上自行终止或通过 Ctrl-C 终止时才会发生。以下解决方案对我有用

while read g
do
  if [[ $g =~ bar ]]
  then
    kill $!
  fi
done < <(
  exec foo 2> >(tee /dev/tty)
)
于 2012-12-30T21:26:32.840 回答
4

使用 Expect 监控标准错误

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 脚本也会退出。

于 2012-12-31T22:57:20.843 回答
2

作为另一个答案的替代方案,一种方法是使用 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 的。

于 2012-12-30T20:56:31.677 回答
2

实际上,我设法找到了一种无需 PID 文件或协同例程的方法,并且可以在所有与 POSIX 兼容的 shell 中工作(我已经尝试过bashdash。至少在支持的系统上/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 BF并将标准输出通过B管道传输到F.

在 fd 4 上重复B的管道运行的主体。A

C运行您的实际 foo 命令,其 stderr 连接到来自Cto的管道D,其标准输出从 fd 3 复制;即恢复到全局标准输出。然后将 PID 写入foofd 4;也就是说,到 subshel​​lF在其标准输入上的管道。

D运行一个tee命令,从管道接收foo其 stderr 上打印的任何内容。它将输出复制到/dev/fd/2(为了让它显示在全局标准错误上)和连接到 subshel​​l 的管道E

Egrep 查找 bar,然后在找到时写入finfd 4,即写入F其标准输入的管道。请注意,如果 grep 遇到 EOF 而没有找到,请&&确保写入 no 。finbar

F,然后,从 中读取 PID 并从 中读取C终止finE。如果fin终止符被正确输出,它会杀死foo.

编辑:修复了tee将 foo 的 stderr 复制到真正的 stderr 的缺失。

于 2012-12-30T21:54:29.500 回答