在bash脚本中,如何逐行捕获stdout


26

在bash脚本中,我想逐行捕获长命令的标准输出,以便可以在初始命令仍在运行时对其进行分析和报告。这是我可以想象的复杂方法:

# Start long command in a separated process and redirect stdout to temp file
longcommand > /tmp/tmp$$.out &

#loop until process completes
ps cax | grep longcommand > /dev/null
while [ $? -eq 0 ]
do
    #capture the last lines in temp file and determine if there is new content to analyse
    tail /tmp/tmp$$.out

    # ...

    sleep 1 s  # sleep in order not to clog cpu

    ps cax | grep longcommand > /dev/null
done

我想知道是否有更简单的方法。

编辑:

为了澄清我的问题,我将添加此内容。在longcommand由线显示其状态行每秒一次。我想在longcommand完成之前捕获输出。

这样,longcommand如果它没有提供我期望的结果,我可能会杀死它。

我试过了:

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

但是whatever(例如echo)仅在longcommand完成后执行。


Answers:


32

只需将命令传递到while循环中即可。有很多细微差别,但基本上(在bash任何POSIX shell中):

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

另一个主要问题(除了以下IFS内容)是在循环完成后尝试从循环内部使用变量时。这是因为循环实际上是在子外壳程序(只是另一个外壳程序进程)中执行的,您不能从中访问变量(循环也完成了,这时变量完全消失了。要解决此问题,你可以做:

longcommand | {
  while IFS= read -r line
  do
    whatever "$line"
    lastline="$line"
  done

  # This won't work without the braces.
  echo "The last line was: $lastline"
}

设置的Hauke的例子lastpipebash是另一种解决方案。

更新资料

为了确保您正在处理命令“按其发生”的输出,可以使用stdbuf将进程设置stdout为行缓冲。

stdbuf -oL longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done

这将配置该过程以一次向管道中写入一行,而不是在内部将其输出缓冲到块中。请注意,程序可以在内部自行更改此设置。unbuffer(的一部分expect)或可以达到类似的效果script

stdbuf在GNU和FreeBSD系统上可用,它仅影响stdio缓冲,并且仅适用于动态链接的非setuid,非setgid应用程序(因为它使用LD_PRELOAD技巧)。


@Stephane在IFS=中不需要bash,我在上次检查后检查。
Graeme 2014年

2
是的。如果您省略则不需要line(在这种情况下,结果将被$REPLY删除,且前导空格和尾随空格均未修剪)。尝试:echo ' x ' | bash -c 'read line; echo "[$line]"'与比较echo ' x ' | bash -c 'IFS= read line; echo "[$line]"'echo ' x ' | bash -c 'read; echo "[$REPLY]"'
斯特凡Chazelas

@Stephane,好的,从来没有意识到它和命名变量之间是有区别的。谢谢。
Graeme 2014年

@Graeme我可能不清楚我的问题,但我想在longcommand完成之前逐行处理输出(以便在longcommands显示错误消息时迅速做出反应)。我将编辑问题以使内容更清楚
gfrigon 2014年

@gfrigon,已更新。
Graeme 2014年

2
#! /bin/bash
set +m # disable job control in order to allow lastpipe
shopt -s lastpipe
longcommand |
  while IFS= read -r line; do lines[i]="$line"; ((i++)); done
echo "${lines[1]}" # second line
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.