如何在上一个非终止命令打印特定字符串后执行命令?

如何在上一个非终止命令打印特定字符串后执行命令?

我想执行commandA打印以下内容:

line1
line2
line3
started successfully
line4
...
...

started successfully打印该行时,我想启动另一个命令(并可能复用它们的输出,以便两者都打印在终端上)。

我怎样才能在 bash 中实现这一目标?

答案1

您可以通过以下一种方法来做到这一点:

$ outputfile=output.log
$ commandA | tee "$outputfile" & tail --pid=$! -n+1 -f "$outputfile" |grep -q "started successfully" && commandB; wait

解释:

  1. 使用该tee命令将输出写入 STDOUT 和名为$outputfile.
  2. 您在后台运行该命令,因此您现在可以跟踪其输出。
  3. tail --pid=$! -n+1 -f "$outputfile"- 您运行tail命令$outputfile并从第一行开始跟踪其输出。
    • 需要该--pid=$!标志来确保tail命令一旦完成就终止commandA
  4. grep -q "started successfully"- 将在第一次出现所需字符串时成功(安静地)退出。
  5. && commandB- 如果grep找到该字符串,它将执行commandB
    • 您应该使用&&而不是;确保commandB执行仅有的 如果在 的输出中找到该字符串commandA。如果commandA在未写入此字符串的情况下完成(例如,如果失败),则您不想运行第二个命令。
  6. wait- 如果commandB之前完成,则在返回 shell 之前 commandA继续等待运行。commandA
    • 或者,如果之前完成,您可以运行fg而不是waitcommandA继续在前台运行。commandB

更新

受到 @PaulPazderski 的回答的启发,这是另一种无需日志文件和tail命令的方法:

$ commandA | tee >(grep -q "started successfully" && commandB; cat >/dev/null)

唯一的缺点是,如果commandA完成 commandB,shell 将在完成后立即返回到提示符commandA,同时commandB可能仍在后台运行,甚至将输出写入终端。

为了解决这个问题,只需将最终输出通过管道传输到另一个命令,例如cat,以确保只有当没有任何内容写入 stdout 时 shell 才会返回到提示符(这意味着整个命令已完成,甚至是commandB)。

$ commandA | tee >(grep -q "started successfully" && commandB; cat >/dev/null) | cat

答案2

我会尝试一个脚本“track-output”,例如:

#!/bin/bash

while IFS= read -r line; do
  echo "$line"
  if [[ "$line" == "started successfully" ]]; then
    do_something
    # optional exit but that could disrupt commandA with a SIGPIPE
  fi
done

并将其称为

commandA | track-output

或者如果使用tee和处理替换并且脚本中没有 echo 行,则为

commandA | tee >(track-output)

答案3

可能:

{
  commandA 3<&- | perl -pe '
    exec "commandB <&3 3<&-" if !$pid && /started successfully/ && ($pid = fork) == 0;
    END {if ($pid) {wait; exit($? & 127 ? $? + 128 : $? >> 8)}}'
} 3<&0

如果使用 zsh 而不是 bash 和 GNU tee,您可以执行类似的操作,但警告commandB退出状态将丢失:

{
  commandA 3<&- |
    tee -p >(grep -q 'started successfully' && commandB <&3 3<&-)
} 3<&0

这在 bash 中也适用(尽管你需要exec在前面添加一个commandBasbash并不总是优化那里的 fork),除了commandB不会在那里等待的事实。

在这两种方法中,我们都会注意commandAcommandB的标准输入不受干扰。commandA的 stdout 从原来的变为管道,但如果我们必须started successfully在那里查找,这是必要的,但该输出是由perl -por传递的teecommandB的标准输出不受干扰。

注意一些命令缓冲当 stdout 不是终端设备时它们的输出。所以commandA这里的输出变成了管道,您可能会发现它started successfully仅作为大块的一部分进行打印,并且commandB没有尽早开始。

使用zsh,可以通过使用zpty开始commandA将其输出连接到伪终端来解决这个问题:

zmodload zsh/zpty

# start commandA with only stdout going to the pseudo-terminal
# stdin and stderr restored.
zpty A 'command A <&3 3<&- 2>&4 4>&-' 3<&0 4>&2

pid=
while zpty -r A line; do
  print -rn - $line
  if (( ! pid )) && [[ $line = *"started successfully" ]]; then
    { commandB <&3 3<&- & } 3<&0
    pid=$!
  fi
done
(( ! pid )) || wait $pid

答案4

下面的代码将在打印“Some text”command2 后执行command1(因为匹配 grep 将终止)。之后 command1command2输出都将打印到标准输出:

command1 | tee >(grep -o -m1 "Some text"; command2 )

相关内容