将 stdout 捕获到一个变量,但仍然在控制台中显示它

我有一个 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。

44515 次浏览

You can use more than three file descriptors. Try here:

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

"Each open file gets assigned a file descriptor. [2] The file descriptors for stdin, stdout, and stderr are 0, 1, and 2, respectively. For opening additional files, there remain descriptors 3 to 9. It is sometimes useful to assign one of these additional file descriptors to stdin, stdout, or stderr as a temporary duplicate link."

The point is whether it's worth to make script more complicated just to achieve this result. Actually it's not really wrong, the way you do it.

Duplicate &1 in your shell (in my example to 5) and use &5 in the subshell (so that you will write to stdout (&1) of the parent shell):

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

This will print "aaa" two times, once because of the echo in the subshell, and the second time it prints the value of the variable.

In your code:

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

Op De Cirkel's answer has the right idea. It can be simplified even more (avoiding use of cat):

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

Here's an example capturing both stderr and the command's exit code. This is building on the answer by 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"

If the folder /taco/ exists, this will capture its contents. If the folder doesn't exist, it will capture an error message and the exit code will be 2.

If you omit 2>&1then only stdout will be captured.

If by "the console" you mean your current TTY, try

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

This is slightly dubious practice because people who try to use this sometimes are surprised when the output lands somewhere unexpected when they don't have a TTY (cron jobs etc).