Let\'s say I have a script like the following:
useless.sh
echo \"This Is Error\" 1>&2
echo \"This Is Output\"
And I have an
This post helped me come up with a similar solution for my own purposes:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
Then as long as our MESSAGE is not an empty string, we pass it on to other stuff. This will let us know if our format_logs.py failed with some kind of python exception.
It would be neater to capture the error file thus:
ERROR=$(</tmp/Error)
The shell recognizes this and doesn't have to run 'cat
' to get the data.
The bigger question is hard. I don't think there's an easy way to do it. You'd have to build the entire pipeline into the sub-shell, eventually sending its final standard output to a file, so that you can redirect the errors to standard output.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Note that the semi-colon is needed (in classic shells - Bourne, Korn - for sure; probably in Bash too). The '{}
' does I/O redirection over the enclosed commands. As written, it would capture errors from sed
too.
WARNING: Formally untested code - use at own risk.
Here's how I did it :
#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
$2 2> $tmpFile
eval "$1=$(< $tmpFile)"
rm $tmpFile
}
Usage example :
captureStderr err "./useless.sh"
echo -$err-
It does use a temporary file. But at least the ugly stuff is wrapped in a function.
This will allow you to pipe the output of your useless.sh
script through a command such as sed
and save the stderr
in a variable named error
. The result of the pipe is sent to stdout
for display or to be piped into another command.
It sets up a couple of extra file descriptors to manage the redirections needed in order to do this.
#!/bin/bash
exec 3>&1 4>&2 #set up extra file descriptors
error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )
echo "The message is \"${error}.\""
exec 3>&- 4>&- # release the extra file descriptors
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
Capture AND Print stderr
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
Breakdown
You can use $()
to capture stdout, but you want to capture stderr instead. So you swap stdout and stderr. Using fd 3 as the temporary storage in the standard swap algorithm.
If you want to capture AND print use tee
to make a duplicate. In this case the output of tee
will be captured by $()
rather than go to the console, but stderr(of tee
) will still go to the console so we use that as the second output for tee
via the special file /dev/fd/2
since tee
expects a file path rather than a fd number.
NOTE: That is an awful lot of redirections in a single line and the order matters. $()
is grabbing the stdout of tee
at the end of the pipeline and the pipeline itself routes stdout of ./useless.sh
to the stdin of tee
AFTER we swapped stdin and stdout for ./useless.sh
.
Using stdout of ./useless.sh
The OP said he still wanted to use (not just print) stdout, like ./useless.sh | sed 's/Output/Useless/'
.
No problem just do it BEFORE swapping stdout and stderr. I recommend moving it into a function or file (also-useless.sh) and calling that in place of ./useless.sh in the line above.
However, if you want to CAPTURE stdout AND stderr, then I think you have to fall back on temporary files because $()
will only do one at a time and it makes a subshell from which you cannot return variables.