Bash:捕获在后台运行的命令的输出
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20017805/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Bash: Capture output of command run in background
提问by rthur
I'm trying to write a bash script that will get the output of a command that runs in the background. Unfortunately I can't get it to work, the variable I assign the output to is empty - if I replace the assignment with an echo command everything works as expected though.
我正在尝试编写一个 bash 脚本,该脚本将获取在后台运行的命令的输出。不幸的是,我无法让它工作,我分配给输出的变量是空的 - 如果我用 echo 命令替换分配,一切都会按预期工作。
#!/bin/bash
function test {
echo ""
}
echo $(test "echo") &
wait
a=$(test "assignment") &
wait
echo $a
echo done
This code produces the output:
此代码产生输出:
echo
done
Changing the assignment to
将分配更改为
a=`echo $(test "assignment") &`
works, but it seems like there should be a better way of doing this.
有效,但似乎应该有更好的方法来做到这一点。
采纳答案by Jo So
Bash has indeed a feature called Process Substitutionto accomplish this.
Bash 确实有一个称为Process Substitution的功能来实现这一点。
$ echo <(yes)
/dev/fd/63
Here, the expression <(yes)
is replaced with a pathname of a (pseudo device) file that is connected to the standard output of an asynchronous job yes
(which prints the string y
in an endless loop).
此处,表达式<(yes)
替换为连接到异步作业标准输出yes
(y
以无限循环方式打印字符串)的(伪设备)文件的路径名。
Now let's try to read from it:
现在让我们尝试阅读它:
$ cat /dev/fd/63
cat: /dev/fd/63: No such file or directory
The problem here is that the yes
process terminated in the meantime because it received a SIGPIPE (it had no readers on stdout).
这里的问题是该yes
进程在此期间终止,因为它收到了一个 SIGPIPE(它在 stdout 上没有读取器)。
The solution is the following construct
解决方案是以下构造
$ exec 3< <(yes) # Save stdout of the 'yes' job as (input) fd 3.
This opens the file as input fd 3 before the background job is started.
这会在后台作业启动之前打开文件作为输入 fd 3。
You can now read from the background job whenever you prefer. For a stupid example
您现在可以随时从后台作业中读取。举个愚蠢的例子
$ for i in 1 2 3; do read <&3 line; echo "$line"; done
y
y
y
Note that this has slightly different semantics than having the background job write to a drive backed file: the background job will be blocked when the buffer is full (you empty the buffer by reading from the fd). By contrast, writing to a drive-backed file is only blocking when the hard drive doesn't respond.
请注意,这与将后台作业写入驱动器支持的文件的语义略有不同:当缓冲区已满时,后台作业将被阻止(通过从 fd 读取来清空缓冲区)。相比之下,写入驱动器支持的文件只会在硬盘驱动器没有响应时阻塞。
Process substitution is not a POSIX sh feature.
进程替换不是 POSIX sh 功能。
Here's a quick hack to give an asynchronous job drive backing (almost) without assigning a filename to it:
这是一个快速的技巧,可以在不为其分配文件名的情况下(几乎)为异步作业驱动器提供支持:
$ 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 also recently got added the O_TEMPFILE option, which makes such hacks possible without the file ever being visible at all. I don't know if bash already supports it.
Linux 最近还添加了 O_TEMPFILE 选项,这使得在根本不可见文件的情况下进行此类黑客攻击成为可能。我不知道 bash 是否已经支持它。
UPDATE:
更新:
@rthur, if you want to capture the whole output from fd 3, then use
@rthur,如果你想从 fd 3 中捕获整个输出,那么使用
output=$(cat <&3)
But note that you can't capture binary data in general: It's only a defined operation if the output is text in the POSIX sense. The implementations I know simply filter out all NUL bytes. Furthermore POSIX specifies that all trailing newlines must be removed.
但请注意,您通常无法捕获二进制数据:如果输出是 POSIX 意义上的文本,则它只是一个定义的操作。我知道的实现只是过滤掉所有 NUL 字节。此外,POSIX 规定必须删除所有尾随换行符。
(Please note also that capturing the output will result in OOM if the writer never stops (yes
never stops). But naturally that problem holds even for read
if the line separator is never written additionally)
(另请注意yes
,read
如果编写器永不停止(永不停止),捕获输出将导致 OOM 。但即使从未额外写入行分隔符,该问题自然也成立)
回答by gniourf_gniourf
One very robust way to deal with coprocesses in Bash is to use... the coproc
builtin.
在 Bash 中处理协进程的一种非常健壮的方法是使用...coproc
内置函数。
Suppose you have a script or function called banana
you wish to run in background, capture all its output while doing some stuff
and wait until it's done. I'll do the simulation with this:
假设您有一个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."
}
You will then run banana
with the coproc
as so:
然后,您将运行banana
与coproc
像这样:
coproc bananafd { banana; }
this is like running banana &
but with the following extras: it creates two file descriptors that are in the array bananafd
(at index 0
for output and index 1
for input). You'll capture the output of banana
with the read
builtin:
这就像运行,banana &
但具有以下附加功能:它在数组中创建两个文件描述符bananafd
(在0
输出索引1
和输入索引处)。您将banana
使用read
内置函数捕获输出:
IFS= read -r -d '' -u "${bananafd[0]}" banana_output
Try it:
尝试一下:
#!/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"
Caveat: you must be done with stuff
before banana
ends! if the gorilla is quicker than you:
警告:你必须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"
In this case, you'll obtain an error like this one:
在这种情况下,您将收到如下错误:
./banana: line 22: read: : invalid file descriptor specification
You can check whether it's too late (i.e., whether you've taken too long doing your stuff
) because after the coproc
is done, bash removes the values in the array bananafd
, and that's why we obtained the previous error.
您可以检查是否为时已晚(即,您是否花了太长时间执行stuff
),因为coproc
完成后,bash 会删除 array 中的值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
Finally, if you really don't want to miss any of gorilla's moves, even if you take too long for your stuff
, you could copy banana
's file descriptor to another fd, 3
for example, do your stuff and then read from 3
:
最后,如果你真的不想错过任何的大猩猩的动作,即使你花费太长时间你的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"
This will work very well! the last read
will also play the role of wait
, so that output
will contain the complete output of banana
.
这将非常有效!最后read
也会起到 的作用wait
,这样output
就会包含 的完整输出banana
。
That was great: no temp files to deal with (bash handles everything silently) and 100% pure bash!
太好了:无需处理临时文件(bash 静默处理所有内容)和 100% 纯 bash!
Hope this helps!
希望这可以帮助!
回答by anubhava
One way to capture background command's output is to redirect it's output in a file and capture output from file after background process has ended:
捕获后台命令输出的一种方法是将其输出重定向到文件中,并在后台进程结束后从文件中捕获输出:
test "assignment" > /tmp/_out &
wait
a=$(</tmp/_out)