如何在标准错误中看到特定字符串时,如何终止进程?

时间:2012-12-30 19:27:43

标签: bash grep stderr

我需要开始一个过程,让我们说foo。我想看到stdout / stderr正常,但是grep字符串bar的stderr。一旦找到bar 应该杀死stderr foo

这可能吗?

5 个答案:

答案 0 :(得分: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. 最外面的括号将整个东西放在子壳中;为了hygeine的目的,这限制了变量的范围
  2. 我们创建一个临时文件,使用适用于GNU和BSD mktemp的语法,并将其称为PIDFILE
  3. 我们设置了一个catch-all出口陷阱(当最外面的子shell退出时运行)以删除PIDFILE命名的文件,再次为hygeine
  4. 我们运行foo;这是在复合语句中完成的,因此&绑定到foo而不绑定到整个前面的管道
  5. 我们将foo的标准错误重定向到流程替换,等待bar出现,然后杀死foo(稍后会更新)
  6. 我们将foo的PID捕获到变量中,将其写入由PIDFILE命名的文件,然后等待它,以便整个命令等待foo退出之前自己退出;当发生这种情况时,|| true会丢弃foo的错误退出状态。
  7. 进程替换中的代码如下:

    1. 首先,tee输入(foo的标准错误),将tee的标准输出重定向到标准错误,以便foo的标准错误确实出现在标准错误
    2. 将输入的副本发送到另一个进程替换(a process substitution within a process substitution
    3. 的文件
    4. 在更深层次的流程替换中,首先在输入上运行grep -q,查找指定的模式,并在找到它时(或当它到达流的末尾时)退出,而不打印任何内容之后(如果找到了字符串并成功退出),shell继续...
    5. killPIDFILE命名的文件中捕获PID的进程,即foo

答案 1 :(得分:5)

Tom Anderson’s answer非常好,但kill $(cat $PIDFILE)只会在foo自行终止或通过Ctrl-C终止时发生在我的系统上。以下解决方案适合我

while read g
do
  if [[ $g =~ bar ]]
  then
    kill $!
  fi
done < <(
  exec foo 2> >(tee /dev/tty)
)

答案 2 :(得分:3)

使用期望监视标准错误

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

答案 3 :(得分: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

但这显然是针对特定的。

答案 4 :(得分: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

解释这里使用的众多子壳:

A的主体与正常的标准输出重复运行到fd 3.它运行子shell BF,其中B标准输出到了标准输入F

B的正文与A的管道在fd 4上重复运行。

C运行你的实际foo命令,其stderr连接到从CD的管道,并且它的标准输出从fd 3复制;也就是说,恢复到全局标准输出。然后它将foo的PID写入fd 4;也就是说,子shell F在其stdin上的管道。

D运行tee命令从管道接收其stderr上的任何foo打印件。它将输出复制到/dev/fd/2(以使其显示在全局stderr上)和连接到子shell E的管道。

E greps for bar然后,当找到时,将fin写入fd 4,即写入F在其stdin上的管道。请注意&&,如果grep在未找到fin的情况下遇到EOF,请确保不会写入bar

然后,

FC读取PID fin和来自E的{​​{1}}终结符。如果正确输出fin终止符,则会终止foo

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