I have a bash script which calls several long-running processes. I want to capture the output of those calls into variables for processing reasons. However, because these are long running processes, I would like the output of the rsync calls to be displayed in the console in real-time and not after the fact.
To this end, I have found a way of doing it but it relies on outputting the text to /dev/stderr. I feel that outputting to /dev/stderr is not a good way of doing things.
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)
In the example above, I am calling rsync a few times and I want to see the file names as they are processed, but in the end I still want the output in a variable because I will be parsing it later.
Is there a 'cleaner' way of accomplishing this?
If it makes a difference, I am using Ubuntu 12.04, bash 4.2.24.
Duplicate &1 in your shell (in my examle 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
Will print aaa two times, ones because of the echo in the subshell, and second time print 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>&1
then only stdout
will be captured.
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.
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).
Alternative to using /dev/tty
, or an additional file descriptor as suggested by the other answers, you can also flip it and simply use a temp file. This is arguably easier to read and more portable in certain situations.
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
来源:https://stackoverflow.com/questions/12451278/capture-stdout-to-a-variable-but-still-display-it-in-the-console