I want to execute a long running command in Bash, and both capture its exit status, and tee its output.
So I do this:
command | tee out.txt
ST=$?
The problem is that the variable ST captures the exit status of tee
and not of command. How can I solve this?
Note that command is long running and redirecting the output to a file to view it later is not a good solution for me.
Base on @brian-s-wilson 's answer; this bash helper function:
used thus:
1: get_bad_things must succeed, but it should produce no output; but we want to see output that it does produce
2: all pipeline must succeed
Dumb solution: Connecting them through a named pipe (mkfifo). Then the command can be run second.
So I wanted to contribute an answer like lesmana's, but I think mine is perhaps a little simpler and slightly more advantageous pure-Bourne-shell solution:
I think this is best explained from the inside out - command1 will execute and print its regular output on stdout (file descriptor 1), then once it's done, printf will execute and print icommand1's exit code on its stdout, but that stdout is redirected to file descriptor 3.
While command1 is running, its stdout is being piped to command2 (printf's output never makes it to command2 because we send it to file descriptor 3 instead of 1, which is what the pipe reads). Then we redirect command2's output to file descriptor 4, so that it also stays out of file descriptor 1 - because we want file descriptor 1 free for a little bit later, because we will bring the printf output on file descriptor 3 back down into file descriptor 1 - because that's what the command substitution (the backticks), will capture and that's what will get placed into the variable.
The final bit of magic is that first
exec 4>&1
we did as a separate command - it opens file descriptor 4 as a copy of the external shell's stdout. Command substitution will capture whatever is written on standard out from the perspective of the commands inside it - but since command2's output is going to file descriptor 4 as far as the command substitution is concerned, the command substitution doesn't capture it - however once it gets "out" of the command substitution it is effectively still going to the script's overall file descriptor 1.(The
exec 4>&1
has to be a separate command because many common shells don't like it when you try to write to a file descriptor inside a command substitution, that is opened in the "external" command that is using the substitution. So this is the simplest portable way to do it.)You can look at it in a less technical and more playful way, as if the outputs of the commands are leapfrogging each other: command1 pipes to command2, then the printf's output jumps over command 2 so that command2 doesn't catch it, and then command 2's output jumps over and out of the command substitution just as printf lands just in time to get captured by the substitution so that it ends up in the variable, and command2's output goes on its merry way being written to the standard output, just as in a normal pipe.
Also, as I understand it,
$?
will still contain the return code of the second command in the pipe, because variable assignments, command substitutions, and compound commands are all effectively transparent to the return code of the command inside them, so the return status of command2 should get propagated out - this, and not having to define an additional function, is why I think this might be a somewhat better solution than the one proposed by lesmana.Per the caveats lesmana mentions, it's possible that command1 will at some point end up using file descriptors 3 or 4, so to be more robust, you would do:
Note that I use compound commands in my example, but subshells (using
( )
instead of{ }
will also work, though may perhaps be less efficient.)Commands inherit file descriptors from the process that launches them, so the entire second line will inherit file descriptor four, and the compound command followed by
3>&1
will inherit the file descriptor three. So the4>&-
makes sure that the inner compound command will not inherit file descriptor four, and the3>&-
will not inherit file descriptor three, so command1 gets a 'cleaner', more standard environment. You could also move the inner4>&-
next to the3>&-
, but I figure why not just limit its scope as much as possible.I'm not sure how often things use file descriptor three and four directly - I think most of the time programs use syscalls that return not-used-at-the-moment file descriptors, but sometimes code writes to file descriptor 3 directly, I guess (I could imagine a program checking a file descriptor to see if it's open, and using it if it is, or behaving differently accordingly if it's not). So the latter is probably best to keep in mind and use for general-purpose cases.
Unlike @cODAR's answer this returns the original exit code of the first command and not only 0 for success and 127 for failure. But as @Chaoran pointed out you can just call
${PIPESTATUS[0]}
. It is important however that all is put into brackets.It may sometimes be simpler and clearer to use an external command, rather than digging into the details of bash. pipeline, from the minimal process scripting language execline, exits with the return code of the second command*, just like a
sh
pipeline does, but unlikesh
, it allows reversing the direction of the pipe, so that we can capture the return code of the producer process (the below is all on thesh
command line, but withexecline
installed):Using
pipeline
has the same differences to native bash pipelines as the bash process substitution used in answer #43972501.* Actually
pipeline
doesn't exit at all unless there is an error. It executes into the second command, so it's the second command that does the returning.This solution works without using bash specific features or temporary files. Bonus: in the end the exit status is actually an exit status and not some string in a file.
Situation:
you want the exit status from
someprog
and the output fromfilter
.Here is my solution:
See my answer for the same question on unix.stackexchange.com for a detailed explanation of how that works and some caveats.