Bash:捕获命令在后台运行的输出

我想写一个bash脚本,将得到在后台运行的命令的输出。 不幸的是我不能得到它的工作,我分配输出的variables是空的 – 如果我用一个回声命令replace分配一切正常,但预期。

#!/bin/bash function test { echo "$1" } echo $(test "echo") & wait a=$(test "assignment") & wait echo $a echo done 

这段代码产生输出:

 echo done 

将分配更改为

 a=`echo $(test "assignment") &` 

作品,但似乎应该有一个更好的方式来做到这一点。

Bash确实有一个叫做进程替代的function来完成这个function。

 $ echo <(yes) /dev/fd/63 

这里,expression式<(yes)被replace为连接到asynchronous作业yes的标准输出的(伪设备)文件的path名(它在无限循环中打印stringy )。

现在我们来尝试阅读它:

 $ cat /dev/fd/63 cat: /dev/fd/63: No such file or directory 

这里的问题是yes进程在此期间终止,因为它收到一个SIGPIPE(它没有标准输出读取器)。

解决scheme是以下构造

 $ exec 3< <(yes) # Save stdout of the 'yes' job as (input) fd 3. 

这将在后台作业启动之前以inputfd 3打开文件。

您现在可以随时从后台作业中读取。 对于一个愚蠢的例子

 $ for i in 1 2 3; do read <&3 line; echo "$line"; done y y y 

请注意,这与将后台作业写入驱动器支持文件的语义略有不同:当缓冲区已满时(通过从fd中读取空白缓冲区)后台作业将被阻止。 相比之下,只有在硬盘驱动器没有响应的情况下写入驱动器支持的文件才会被阻止。

过程replace不是POSIX shfunction。

下面是一个简单的黑客程序,它给出了一个asynchronous作业驱动器的支持(几乎),而不用指定文件名:

 $ yes > backingfile & # Start job in background writing to a new file. Do also look at `mktemp(3)` and the `sh` option `set -o noclobber` $ exec 3< backingfile # open the file for reading in the current shell, as fd 3 $ rm backingfile # remove the file. It will disappear from the filesystem, but there is still a reader and a writer attached to it which both can use it. $ for i in 1 2 3; do read <&3 line; echo "$line"; done y y y 

Linux最近还添加了O_TEMPFILE选项,这使得这样的黑客可能没有可见的文件。 我不知道bash是否已经支持它。

更新

@rthur,如果你想从fd 3捕获整个输出,那么使用

 output=$(cat <&3) 

但是请注意,通常不能捕获二进制数据:如果输出是POSIX意义上的文本,则只是定义的操作。 我知道的实现只是过滤掉所有的NUL字节。 此外,POSIX指定所有尾随的换行符必须被删除。

(请注意,如果作者从不停止(即从不停止),捕获输出将导致OOM。但是,如果行分隔符从未被另外写入,那么自然,即使对于read也存在问题)

在Bash中处理coprocesses的一个非常可靠的方法是使用coproc builtin。

假设你有一个脚本或函数叫做banana你希望在后台运行,捕获所有的输出,同时做一些stuff并等待完成。 我会用这个来做模拟:

 banana() { for i in {1..4}; do echo "gorilla eats banana $i" sleep 1 done echo "gorilla says thank you for the delicious bananas" } stuff() { echo "I'm doing this stuff" sleep 1 echo "I'm doing that stuff" sleep 1 echo "I'm done doing my stuff." } 

然后,你会用coproc来运行banana

 coproc bananafd { banana; } 

这就像运行banana &但有以下额外:它创build两个文件描述符在数组bananafd (在索引0为输出和索引1为input)。 你将捕获内置read banana的输出:

 IFS= read -r -d '' -u "${bananafd[0]}" banana_output 

尝试一下:

 #!/bin/bash banana() { for i in {1..4}; do echo "gorilla eats banana $i" sleep 1 done echo "gorilla says thank you for the delicious bananas" } stuff() { echo "I'm doing this stuff" sleep 1 echo "I'm doing that stuff" sleep 1 echo "I'm done doing my stuff." } coproc bananafd { banana; } stuff IFS= read -r -d '' -u "${bananafd[0]}" banana_output echo "$banana_output" 

警告:在banana结束之前,你必须做好准备。 如果大猩猩比你快

 #!/bin/bash banana() { for i in {1..4}; do echo "gorilla eats banana $i" done echo "gorilla says thank you for the delicious bananas" } stuff() { echo "I'm doing this stuff" sleep 1 echo "I'm doing that stuff" sleep 1 echo "I'm done doing my stuff." } coproc bananafd { banana; } stuff IFS= read -r -d '' -u "${bananafd[0]}" banana_output echo "$banana_output" 

在这种情况下,你会得到这样一个错误:

 ./banana: line 22: read: : invalid file descriptor specification 

你可以检查是否太迟了(也就是说你是否花了太长的时间来完成你的工作),因为在完成coproc之后,bash删除了数组bananafd中的值,这就是为什么我们获得了以前的错误。

 #!/bin/bash banana() { for i in {1..4}; do echo "gorilla eats banana $i" done echo "gorilla says thank you for the delicious bananas" } stuff() { echo "I'm doing this stuff" sleep 1 echo "I'm doing that stuff" sleep 1 echo "I'm done doing my stuff." } coproc bananafd { banana; } stuff if [[ -n ${bananafd[@]} ]]; then IFS= read -r -d '' -u "${bananafd[0]}" banana_output echo "$banana_output" else echo "oh no, I took too long doing my stuff..." fi 

最后,如果你真的不想错过任何大猩猩的举动,即使你的stuff花费太长时间,你也可以把banana的文件描述符复制到另外一个fd,例如3 ,做你的东西,然后从3

 #!/bin/bash banana() { for i in {1..4}; do echo "gorilla eats banana $i" sleep 1 done echo "gorilla says thank you for the delicious bananas" } stuff() { echo "I'm doing this stuff" sleep 1 echo "I'm doing that stuff" sleep 1 echo "I'm done doing my stuff." } coproc bananafd { banana; } # Copy file descriptor banana[0] to 3 exec 3>&${bananafd[0]} stuff IFS= read -d '' -u 3 output echo "$output" 

这将工作得很好! 上次read也会起到wait的作用,这样output将会包含banana的完整输出。

这太棒了:没有临时文件来处理(bash处理所有事情),100%纯粹的bash!

希望这可以帮助!

捕获后台命令输出的一种方法是将其输出redirect到文件中,并在后台进程结束后从文件中捕获输出:

 test "assignment" > /tmp/_out & wait a=$(</tmp/_out)