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

Bash:捕获在后台运行的命令的输出,bash,Bash,我正在尝试编写一个bash脚本,它将获得在后台运行的命令的输出。不幸的是,我无法让它工作,我分配给输出的变量是空的-如果我用echo命令替换分配,那么一切都会按预期工作 #!/bin/bash function test { echo "$1" } echo $(test "echo") & wait a=$(test "assignment") & wait echo $a echo done 此代码生成以下输出: echo done 将作业更改为 a=

我正在尝试编写一个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") &`

工作正常,但似乎应该有更好的方法来实现这一点。

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

test "assignment" > /tmp/_out &
wait
a=$(</tmp/_out)
测试“分配”>/tmp/\u out&
等待

a=$(Bash确实有一个称为进程替换的特性来实现这一点

$ echo <(yes)
/dev/fd/63
这里的问题是
yes
进程同时终止,因为它收到了一个SIGPIPE(stdout上没有读卡器)

解决方案是以下构造

$ exec 3< <(yes)  # Save stdout of the 'yes' job as (input) fd 3.
$exec 3$for i in 1 2 3;do read在Bash中处理协进程的一种非常健壮的方法是使用
coproc
内置

假设您有一个名为
banana
的脚本或函数,您希望在后台运行,在执行一些
操作时捕获其所有输出,并等待完成。我将使用以下内容进行模拟:

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&
,但有以下附加功能:它创建两个文件描述符,它们位于数组
bananafd
中(在索引
0
处用于输出,索引
1
处用于输入)。您将使用
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"
警告:你必须在
香蕉
结束之前完成
东西
!如果大猩猩比你快:

#!/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
最后,如果你真的不想错过gorilla的任何一个动作,即使你花了太长时间在
东西上,你也可以将
香蕉
的文件描述符复制到另一个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"
这将非常有效!最后的
读取
也将扮演
等待
的角色,因此
输出
将包含
香蕉
的完整输出

这是伟大的:没有临时文件处理(bash处理一切安静)和100%纯bash


希望这有帮助!

是否有不使用文件就可以执行此操作的方法?是的,有(仅限bash)这是唯一一个对我有效的方法,谢谢!谢谢,看起来很棒!有没有任何方法可以在不循环每一行的情况下从读卡器获取所有输出?我假设调用read line也可以调用“wait”是正确的吗?警告:如果你想在现实生活脚本中使用此功能,你需要使用
mktemp
创建文件
backingfile
,甚至使用
trap
,因为组合
/
exec
/
rm
不是原子的!@gniourf\gniourf:不,原子性没有问题。不,trap根本不会产生任何效果omic。对讨论的结论不完全清楚。是否缺少对
wait
的调用?同样,在
cat之后,感谢您的详细回复。我最终使用了Jo So的答案,因为这似乎是一种将命令输出传递给另一个fd的更简单的方法-尽管coproc似乎很有用。@user2352030 JoSo的答案并不简单呃,一点也不!它甚至更复杂,因为它迫使你创建一个文件并将其删除。如果你想以安全的方式使用他的答案,你需要使用
mktemp
…甚至可能使用
trap
!不要被看似简单的东西所愚弄!也许我错了,但从我的理解来看,他的便携shell方法迫使我这么做这样做,但是使用bash构造
exec 3<@user2352030
coproc
的另一个优点是,您可以通过检查数组
bananafd
是否已设置来确定后台进程是否已完成。嗯……这看起来像是解决我遇到的问题的最佳解决方案,但事实上正式只有一个
coproc
可能会立即运行,这限制了它的运行。在我的例子中,它恰好适用于四个
coproc
(我在我的
.bashrc
中运行了四个昂贵的命令来初始化四个不同的bash变量,我不喜欢花2.5秒的时间等待它们顺序运行,因为并行执行可能会将时间减少到0.9秒左右),但我抛出了关于在第一个
coproc
之后启动的三个
coproc
的警告(因为第一个还没有完成)。
#!/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
#!/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
#!/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"