我想执行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
解释:
- 使用该
tee
命令将输出写入 STDOUT 和名为$outputfile
. - 您在后台运行该命令,因此您现在可以跟踪其输出。
tail --pid=$! -n+1 -f "$outputfile"
- 您运行tail
命令$outputfile
并从第一行开始跟踪其输出。- 需要该
--pid=$!
标志来确保tail
命令一旦完成就终止commandA
。
- 需要该
grep -q "started successfully"
- 将在第一次出现所需字符串时成功(安静地)退出。&& commandB
- 如果grep
找到该字符串,它将执行commandB
。- 您应该使用
&&
而不是;
确保commandB
执行仅有的 如果在 的输出中找到该字符串commandA
。如果commandA
在未写入此字符串的情况下完成(例如,如果失败),则您不想运行第二个命令。
- 您应该使用
wait
- 如果commandB
之前完成,则在返回 shell 之前commandA
继续等待运行。commandA
- 或者,如果之前完成,您可以运行
fg
而不是wait
让commandA
继续在前台运行。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
在前面添加一个commandB
asbash
并不总是优化那里的 fork),除了commandB
不会在那里等待的事实。
在这两种方法中,我们都会注意commandA
和commandB
的标准输入不受干扰。commandA
的 stdout 从原来的变为管道,但如果我们必须started successfully
在那里查找,这是必要的,但该输出是由perl -p
or传递的tee
。commandB
的标准输出不受干扰。
注意一些命令缓冲当 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 将终止)。之后 command1
和command2
输出都将打印到标准输出:
command1 | tee >(grep -o -m1 "Some text"; command2 )