views:

7555

answers:

5

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?

A: 

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)
Chas. Owens
This is the reason I included the pipe in my example.I still want the standard output, and I want it to do other things, go other places.
psycotica0
+2  A: 

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.)

Jonathan Leffler
I had hoped that there'd be some really crazy trick I didn't know, but it looks like this is it.Thanks.
psycotica0
A: 

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.

+4  A: 

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
Dennis Williamson
It is good technique to use 'exec' to set and close file descriptors. The close isn't really needed if the script exits immediately afterwards.
Jonathan Leffler
A: 

Thanks for this. Leffler's solution works well, (Gonthier's put stdout into ERROR, not stderr. Its output is "This is Useless", not "This is Error".) I was wondering if there is also a way to capture the exit value of the useless.sh command as well as its stderr. Running it in a subshell seems to preclude that because any variable that the exit value gets stored in is not available when the subshell exits. On could assume that if ERROR is empty that the command succeeded, but the exit value might be needed for other reasons.

Paul Dubuc