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

gfr*_*gon 35 bash process stdout

在 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
Run Code Online (Sandbox Code Playgroud)

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

编辑:

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

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

我试过了:

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done
Run Code Online (Sandbox Code Playgroud)

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

Gra*_*eme 44

只需将命令通过管道传输到while循环中即可。这有许多细微差别,但基本上(在bash或任何 POSIX shell 中):

longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done
Run Code Online (Sandbox Code Playgroud)

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

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"
}
Run Code Online (Sandbox Code Playgroud)

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

更新

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

stdbuf -oL longcommand |
  while IFS= read -r line
  do
    whatever "$line"
  done
Run Code Online (Sandbox Code Playgroud)

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

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

  • 是的。如果省略`line`,则不需要它(在这种情况下,结果放在`$REPLY` 中,前导和尾随空格没有被修剪)。试试:`echo ' x ' | bash -c '读行; echo "[$line]"'` 并与 `echo ' x ' 进行比较 | bash -c 'IFS=读行;echo "[$line]"'` 或 `echo ' x ' | bash -c '读; echo "[$REPLY]"'` (3认同)

Hau*_*ing 5

#! /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
Run Code Online (Sandbox Code Playgroud)