为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

守着一只汪 2019-11-14 15:09:20

我正在尝试编写一个bash脚本,该脚本将获取在后台运行的命令的输出。不幸的是,我无法使其正常工作,我将输出分配给的变量为空-如果我用echo命令替换该分配,则一切都会按预期工作。


#!/bin/bash


function test {

    echo "$1"

}


echo $(test "echo") &

wait


a=$(test "assignment") &

wait


echo $a


echo done

此代码产生输出:


echo


done

将分配更改为


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

可以,但是似乎应该有一个更好的方法。


查看完整描述

3 回答

?
慕斯王

Bash确实具有一个称为“ 流程替换”的功能来完成此任务。


$ echo <(yes)

/dev/fd/63

在此,表达式<(yes)被(伪设备)文件的路径名替换,该文件连接到异步作业的标准输出yes(该文件y在无穷循环中打印字符串)。


现在,让我们尝试阅读一下:


$ cat /dev/fd/63

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

这里的问题是,由于该yes进程收到了SIGPIPE(在stdout上没有读取器),因此该进程同时终止了。


解决方案是以下构造


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

在启动后台作业之前,这将打开文件作为输入fd 3。


现在,您可以随时从后台作业中读取信息。举个愚蠢的例子


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

y

y

y

请注意,其语义与将后台作业写入驱动器支持的文件略有不同:当缓冲区已满时,后台作业将被阻止(您可以通过读取fd清空缓冲区)。相比之下,仅在硬盘驱动器无响应时才阻止写入驱动器备份的文件。


进程替换不是POSIX sh功能。


这是一个快速的技巧,可以(几乎)为异步作业驱动器提供备份,而无需为其分配文件名:


$ 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指定必须删除所有尾随的换行符。


(还请注意,如果写入器永不停止(yes永不停止),则捕获输出将导致OOM 。但是自然地,即使read从未额外写入行分隔符,该问题仍然存在)


查看完整回答
反对 回复 2019-11-14
?
九州编程

在Bash中处理协同处理的一种非常强大的方法是使用coproc内建函数。


假设您有一个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."

}

然后banana,您将按以下方式运行coproc:


coproc bananafd { banana; }

这就像运行,banana &但具有以下额外功能:它在数组中创建两个文件描述符bananafd(0输出1为索引,输入为索引)。您将banana使用read内置捕获输出:


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"

警告:您必须stuff先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

您可以检查是否为时已晚(例如,您是否花了太长时间stuff),因为在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!


希望这可以帮助!


查看完整回答
反对 回复 2019-11-14
?
墨色风雨

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


test "assignment" > /tmp/_out &

wait

a=$(</tmp/_out)


查看完整回答
反对 回复 2019-11-14

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信