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
# 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}
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.
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.
In zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
Will produce:
This Is Output
-
This Is Error
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.
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.