Bash:捕获在后台运行的命令的输出 [英] Bash: Capture output of command run in background

查看:51
本文介绍了Bash:捕获在后台运行的命令的输出的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

#!/bin/bash

function test {
    echo "$1"
}

echo $(test "echo") &
wait

a=$(test "assignment") &
wait

echo $a

echo done

此代码产生输出:

echo

done

将分配更改为

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

有效,但似乎应该有更好的方法来做到这一点.

works, but it seems like there should be a better way of doing this.

推荐答案

Bash 确实有一个名为 Process Substitution 的功能来实现这一点.

Bash has indeed a feature called Process Substitution to accomplish this.

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

这里,表达式<(yes) 替换为一个(伪设备)文件的路径名,该文件连接到异步作业的标准输出yes(在无限循环中打印字符串 y).

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).

现在让我们试着读取它:

Now let's try to read from it:

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

这里的问题是 yes 进程在此期间终止,因为它收到了一个 SIGPIPE(它在 stdout 上没有读取器).

The problem here is that the yes process terminated in the meantime because it received a SIGPIPE (it had no readers on stdout).

解决方案是以下构造

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

这会在后台作业启动之前打开文件作为输入 fd 3.

This opens the file as input fd 3 before the background job is started.

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

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

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

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.

进程替换不是 POSIX sh 功能.

Process substitution is not a POSIX sh feature.

这里有一个快速的技巧,可以在不为其分配文件名的情况下(几乎)为异步作业提供驱动器支持:

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 最近还添加了 O_TEMPFILE 选项,这使得在根本不可见文件的情况下进行此类攻击成为可能.不知道bash是否已经支持.

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.

更新:

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

@rthur, if you want to capture the whole output from fd 3, then use

output=$(cat <&3)

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

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.

(另请注意,如果编写器永不停止(yes 永不停止),捕获输出将导致 OOM.但是如果 read 自然而然地存在该问题,如果行分隔符永远不会另外写)

(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)

这篇关于Bash:捕获在后台运行的命令的输出的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆