将标准输出捕获到变量,但仍将其显示在控制台中

Posted

技术标签:

【中文标题】将标准输出捕获到变量,但仍将其显示在控制台中【英文标题】:Capture stdout to a variable but still display it in the console 【发布时间】:2012-09-09 04:45:26 【问题描述】:

我有一个 bash 脚本,它调用了几个长时间运行的进程。出于处理原因,我想将这些调用的输出捕获到变量中。但是,由于这些是长时间运行的进程,我希望 rsync 调用的输出实时显示在控制台中,而不是事后显示。

为此,我有found 一种方法,但它依赖于将文本输出到/dev/stderr。感觉输出到/dev/stderr并不是一个好办法。

VAR1=$(for i in 1..5; do sleep 1; echo $i; done | tee /dev/stderr)

VAR2=$(rsync -r -t --out-format='%n%L' --delete -s /path/source1/ /path/target1 | tee /dev/stderr)

VAR3=$(rsync -r -t --out-format='%n%L' --delete -s /path/source2/ /path/target2 | tee /dev/stderr)

在上面的示例中,我调用了 rsync 几次,我想在处理文件名时查看它们,但最后我仍然希望在变量中输出,因为我稍后会解析它。

是否有一种“更清洁”的方式来完成此任务?

如果有什么不同,我使用的是 Ubuntu 12.04,bash 4.2.24。

【问题讨论】:

【参考方案1】:

在您的 shell 中复制 &1(在我的示例中为 5)并在子 shell 中使用 &5(这样您将写入父 shell 的标准输出 (&1)):

exec 5>&1
FF=$(echo aaa|tee >(cat - >&5))
echo $FF

会打印两次aaa,一次是因为子shell中的echo,第二次打印变量的值。

在您的代码中:

exec 5>&1
VAR1=$(for i in 1..5; do sleep 1; echo $i; done | tee >(cat - >&5))
# use the value of VAR1

【讨论】:

不应该在父shell中关闭描述符吗? @akhan:我假设那是exec 5>&- FF=$(echo aaa | tee /dev/tty) 怎么样? Source 这不会保留输出颜色。 @user1011471,当你写tee >(inner_cmd...)时,shell打开一个到内部命令的管道,这样管道的写端也被外部命令(tee)继承,通常在描述符63下. 外部命令不知道外壳做了什么有趣的事情,它得到一个文件名参数/dev/fd/63。 en.wikipedia.org/wiki/Process_substitution 不知道为什么会出现这个错误,但这与写入描述符 5 的内部命令无关。【参考方案2】:

Op De Cirkel 的回答是正确的。它可以进一步简化(避免使用cat):

exec 5>&1
FF=$(echo aaa|tee /dev/fd/5)
echo $FF

【讨论】:

/dev/fd/5 不是特定于操作系统的吗? 我一直想知道你是否可以只使用tee /dev/fd/1,但这不起作用,因为输出仍然被$() 捕获。因此,如果其他人想知道同样的事情, 有必要使用额外的文件描述符(如 5)。 我们可以进一步简化并使其成为一个没有exec 的单行器: FF=$(echo aaa|tee /dev/fd/5); 5>&1 大括号允许在运行子shell 命令之前发生重定向,而$FF 仍然保留在当前 shell 的范围(不适用于普通括号 ( )。这样之后甚至不需要关闭 FD 5,这是一个被忽视的卫生习惯。 @akhan 不,它不会,据说 bash 正在模拟这条路径,如果它本身不存在于操作系统中。 如果使用sudo -u <other non-root user> <script> 运行此操作,则 Op De Cirkel 的答案有效,但此答案无效。写入 /dev/fd/5 相当于直接写入终端。 /dev/fd/5 是终端的 /dev/pts/ 文件的符号链接,该文件归最初登录的用户所有,并且不能被 sudo 用户写入。但是,cat - >&5 写入由 bash 在进程中打开的文件描述符(这与写入 /dev/fd/5 不同)。此文件描述符通过每个父进程转发写入,避免任何权限问题。【参考方案3】:

这是一个捕获stderr 和命令退出代码的示例。这是基于 Russell Davis 的回答。

exec 5>&1
FF=$(ls /taco/ 2>&1 |tee /dev/fd/5; exit $PIPESTATUS[0])
exit_code=$?
echo "$FF"
echo "Exit Code: $exit_code"

如果文件夹/taco/ 存在,这将捕获其内容。如果文件夹不存在,则会捕获错误消息,退出代码为 2。

如果您省略2>&1,则只会捕获stdout

【讨论】:

【参考方案4】:

如果您所说的“控制台”是指您当前的 TTY,请尝试

variable=$(command with options | tee /dev/tty)

这是一种有点可疑的做法,因为尝试使用它的人有时会在没有 TTY(cron 作业等)时输出到达意想不到的地方时感到惊讶。

【讨论】:

它在 Docker 容器中不起作用。 tee: /dev/tty: No such device or address 我认为这通常不是真的。它在 Debian Docker 容器中对我有用。【参考方案5】:

您可以使用三个以上的文件描述符。在这里试试:

http://tldp.org/LDP/abs/html/io-redirection.html

"每个打开的文件都被分配一个文件描述符。[2] stdin、stdout 和 stderr 的文件描述符分别是 0、1 和 2。对于打开其他文件,剩下的描述符是 3 到 9。它是有时将这些附加文件描述符之一分配给 stdin、stdout 或 stderr 作为临时重复链接很有用。"

关键是为了达到这个结果是否值得让脚本更复杂。其实这并没有错,你这样做的方式。

【讨论】:

以上是关于将标准输出捕获到变量,但仍将其显示在控制台中的主要内容,如果未能解决你的问题,请参考以下文章

在 Bash 中同时捕获标准输出和标准错误 [重复]

R:同时重定向到标准输出和动态文件

如何使用 Python 在日志文件中复制/捕获标准输出

重定向期望标准输入产生调用

防止在 EditText 上输入键,但仍将文本显示为多行

如何在 elisp 中捕获 shell 命令的标准输出?