将stdout捕获到变量,但仍将其显示在控制台中


75

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

为此,我找到了一种方法,但是它依赖于将文本输出到/ 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。

Answers:


84

在您的shell中复制&1(在我的示例中为5),在子shell中使用&5(这样您将写入父shell的stdout(&1)):

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

将打印aaa两次,一次是由于子外壳中的回显,第二次将打印变量的值。

在您的代码中:

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

这行得通,谢谢!因此,在这种情况下,“ 5”必须是文件描述符,我将阅读并了解这些内容。
Mendhak 2012年

3
描述符不应该在父外壳中关闭吗?
2014年

2
@akhan:我想是exec 5>&-吗?
hakre 2014年

7
怎么FF=$(echo aaa | tee /dev/tty)消息来源
knight42

1
这不会保留输出颜色。
Shantanu Bhadoria'5

36

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

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

7
/ dev / fd / 5是否特定于操作系统?
akhan 2014年

9
我一直想知道您是否可以使用它tee /dev/fd/1,但这不起作用,因为输出仍然被捕获$()。因此,如果其他人想知道同一件事,必须使用额外的文件描述符(例如5)。
土边

2
我们甚至可以进一步简化它,使它成为没有以下内容的单行代码exec:花{ FF=$(echo aaa|tee /dev/fd/5); } 5>&1 括号允许重定向在subshel​​l命令运行之前发生,而$FF仍然在当前shell的范围内(这在常规方括号中无效)( )。这样甚至以后都无需关闭FD 5,这是一个被忽视的卫生习惯
。– tlwhitec

@akhan不,不会,据说bash应该模仿此路径,如果它本身不存在于OS中。
tlwhitec

如果运行此命令,sudo -u <other non-root user> <script>则Op De Cirkel的答案有效,但此答案无效。写入/ dev / fd / 5等效于直接写入终端。/ dev / fd / 5是指向终端的/ dev / pts /文件的符号链接,该文件由最初登录的用户拥有,而sudo'd用户不可写。但是,cat - >&5写入由进程中的bash打开的文件描述符(与写入/ dev / fd / 5不同)。该文件描述符通过每个父进程转发写操作,从而避免了任何权限问题。
Paul Donohue

13

这是捕获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被捕获。


6

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

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

这是一种有点可疑的做法,因为尝试使用此功能的人有时会在没有TTY的情况下(临时工作等)将输出降到意外的地方时感到惊讶。


5

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

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

“每个打开的文件都分配有一个文件描述符。[2] stdin,stdout和stderr的文件描述符分别为0、1和2。要打开其他文件,则保留描述符3到9。将这些其他文件描述符之一分配给stdin,stdout或stderr作为临时重复链接。”

关键是仅仅为了实现此结果而使脚本更复杂是否值得。实际上,这并不是真的错误,您的操作方式。


0

/dev/tty除了使用或其他答案建议的其他文件描述符之外,您还可以翻转它并仅使用临时文件。可以说,这在某些情况下更易于阅读,并且更便于携带。

tmpFile=$(mktemp)  # mak-a de temp
rsync /a /b | tee $tmpFile # sync my b*tch up
if grep "U F'd up" $tmpFile; then
  rm -rf / #Seppuku
fi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.