将标准输出捕获到变量,但仍将其显示在控制台中
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 作为临时重复链接很有用。"
关键是为了达到这个结果是否值得让脚本更复杂。其实这并没有错,你这样做的方式。
【讨论】:
以上是关于将标准输出捕获到变量,但仍将其显示在控制台中的主要内容,如果未能解决你的问题,请参考以下文章