How to store standard error in a variable in a Bas

2019-01-01 00:11发布

问题:

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 another shell script:

alsoUseless.sh

./useless.sh | sed \'s/Output/Useless/\'

I want to capture \"This Is Error\", or any other stderr from useless.sh, into a variable. Let\'s call it ERROR.

Notice that I am using stdout for something. I want to continue using stdout, so redirecting stderr into stdout is not helpful, in this case.

So, basically, I want to do

./useless.sh 2> $ERROR | ...

but that obviously doesn\'t work.

I also know that I could do

./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`

but that\'s ugly and unnecessary.

Unfortunately, if no answers turn up here that\'s what I\'m going to have to do.

I\'m hoping there\'s another way.

Anyone have any better ideas?

回答1:

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.

(Formally untested code - use at own risk.)



回答2:

alsoUseless.sh

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


回答3:

Redirected stderr to stdout, stdout to /dev/null, and then use the backticks or $() to capture the redirected stderr:

ERROR=$(./useless.sh 2>&1 >/dev/null)


回答4:

There are a lot of duplicates for this question, many of which have a slightly simpler usage scenario where you don\'t want to capture stderr and stdout and the exit code all at the same time.

if result=$(useless.sh 2>&1); then
    stdout=$result
else
    rc=$?
    stderr=$result
fi

works for the common scenario where you expect either proper output in the case of success, or a diagnostic message on stderr in the case of failure.

Note that the shell\'s control statements already examine $? under the hood; so anything which looks like

cmd
if [ $? -eq 0 ], then ...

is just a clumsy, unidiomatic way of saying

if cmd; then ...


回答5:

# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr=\"$(command </dev/stdin 2>&1 1>&3)\"
exitcode=\"${?}\"
echo \"STDERR: $stderr\"
exit ${exitcode}


回答6:

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.



回答7:

This is an interesting problem to which I hoped there was an elegant solution. Sadly, I end up with a solution similar to Mr. Leffler, but I\'ll add that you can call useless from inside a Bash function for improved readability:

#!/bin/bash

function useless {
    /tmp/useless.sh | sed \'s/Output/Useless/\'
}

ERROR=$(useless)
echo $ERROR

All other kind of output redirection must be backed by a temporary file.



回答8:

$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo \"a=>$a b=>$b\"
a=>stdout b=>stderr


回答9:

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.



回答10:

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.



回答11:

If you want to bypass the use of a temporary file you may be able to use process substitution. I haven\'t quite gotten it to work yet. This was my first attempt:

$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)\'
-bash: command substitution: line 42: `<)\'

Then I tried

$ ./useless.sh 2> >( ERROR=$( cat <() )  )
This Is Output
$ echo $ERROR   # $ERROR is empty

However

$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error

So the process substitution is doing generally the right thing... unfortunately, whenever I wrap STDIN inside >( ) with something in $() in an attempt to capture that to a variable, I lose the contents of $(). I think that this is because $() launches a sub process which no longer has access to the file descriptor in /dev/fd which is owned by the parent process.

Process substitution has bought me the ability to work with a data stream which is no longer in STDERR, unfortunately I don\'t seem to be able to manipulate it the way that I want.



回答12:

In zsh:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )


回答13:

POSIX

STDERR can be captured with some redirection magic:

$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/

$ echo $error
ls: cannot access \'/XXXX\': No such file or directory

Note that piping of STDOUT of the command (here ls) is done inside the innermost { }. If you\'re executing a simple command (eg, not a pipe), you could remove these inner braces.

You can\'t pipe outside the command as piping makes a subshell in bash and zsh, and the assignment to the variable in the subshell wouldn\'t be available to the current shell.

bash

In bash, it would be better not to assume that file descriptor 3 is unused:

{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1; 
exec {tmp}>&-  # With this syntax the FD stays open

Note that this doesn\'t work in zsh.


Thanks to this answer for the general idea.



回答14:

For error proofing your commands:

execute [INVOKING-FUNCTION] [COMMAND]

execute () {
    error=$($2 2>&1 >/dev/null)

    if [ $? -ne 0 ]; then
        echo \"$1: $error\"
        exit 1
    fi
}

Inspired in Lean manufacturing:

  • Make errors impossible by design
  • Make steps the smallest
  • Finish items one by one
  • Make it obvious to anyone