Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Capturing stderr?

12 views
Skip to first unread message

Ben Bacarisse

unread,
Jun 8, 2012, 9:46:05 AM6/8/12
to
I have a bash script that captures the output of a command (it's gpg, as
it happens) and also tests the exit code:

out=$(command 2>/dev/null)
[[ $? -eq 0 ]] || exit 1

In order to improve the error messages from the script, I'd now like to
distinguish between two error cases, but the command exists with the
same $? in both. The error output, however, *is* different.

The two most obvious solutions seem either yucky or rather over-the-top.
I could, at a pinch, collect both stderr and stdout and then filter the
error messages from $out because gpg errors are prefixed with "gpg:" and
it is unlikely that lines beginning "gpg:" will occur in the "real"
output. But that won't work for other commands, and it just seems wrong
even here.

The obvious solution is to use a temp file, but that seems like a lot of
fiddling for a slightly improved error message. (Also I am not 100%
sure that gpg won't ever put anything into its error output that I would
not want in a temp file. I say this just to explain why I'm still
looking even though a temp file works in the general case.)

In short, am I missing some trick that can simplify this:

tmp=$(mktemp)
out=$(command 2>$tmp)
if [[ $? -ne 0 ]]; then
# examine content of $tmp and report accordingly
fi
rm $tmp

I would be happy with bash-only solutions, but a universal one is
always going to be worth knowing.

--
Ben.

Stephane Chazelas

unread,
Jun 8, 2012, 3:25:37 PM6/8/12
to
2012-06-08 14:46:05 +0100, Ben Bacarisse:
[...]
> In short, am I missing some trick that can simplify this:
>
> tmp=$(mktemp)
> out=$(command 2>$tmp)
> if [[ $? -ne 0 ]]; then
> # examine content of $tmp and report accordingly
> fi
> rm $tmp
>
> I would be happy with bash-only solutions, but a universal one is
> always going to be worth knowing.
[...]

If you think of what's need to be done to achieve it without a
temp file, you'll realise why there's no straightforward way.

in

out=$(cmd)

you run cmd with its stdout connected to a pipe and the shell
reads from the other end of the pipe to fill $out up.

Now, for a

(out,err)=$(cmd)

You'd need 2 pipes, and the shell would need to have a
select(2)/poll(2) loop on the other end of the pipes to fill the
2 variables.

Another thing you could to is

err_and_out=$(
{ out=$(cmd); } 2>&1
ret=$?
printf 'MYSEPARATOR%s' "$out"
exit "$ret"
)
ret=$?
out=${err_and_out##*MYSEPARATOR}
err=${err_and_out%MYSEPARATOR}

Or (zsh syntax, but you have the equivalent in ksh93/bash):

err_and_out=$(
{ out=$(cmd); } 2>&1
ret=$?
printf '%s' "$out,${#out}"
exit "$ret"
)
ret=
out_length=${err_and_out##*,}
err_and_out=${err_and_out%,*}
if (($out_length)); then
out=$err_and_out[-out_length,-1]
err=$err_and_out[1,-out_length-1]
else
out=
err=$err_and_out
fi

--
Stephane

Ben Bacarisse

unread,
Jun 8, 2012, 4:57:06 PM6/8/12
to
Stephane Chazelas <stephane...@gmail.com> writes:

> 2012-06-08 14:46:05 +0100, Ben Bacarisse:
> [...]
>> In short, am I missing some trick that can simplify this:
>>
>> tmp=$(mktemp)
>> out=$(command 2>$tmp)
>> if [[ $? -ne 0 ]]; then
>> # examine content of $tmp and report accordingly
>> fi
>> rm $tmp
>>
>> I would be happy with bash-only solutions, but a universal one is
>> always going to be worth knowing.
> [...]
>
> If you think of what's need to be done to achieve it without a
> temp file, you'll realise why there's no straightforward way.

Yes, I certainly thought is was not going to be trivial.

> in
>
> out=$(cmd)
>
> you run cmd with its stdout connected to a pipe and the shell
> reads from the other end of the pipe to fill $out up.
>
> Now, for a
>
> (out,err)=$(cmd)
>
> You'd need 2 pipes, and the shell would need to have a
> select(2)/poll(2) loop on the other end of the pipes to fill the
> 2 variables.
>
> Another thing you could to is
>
> err_and_out=$(
> { out=$(cmd); } 2>&1
> ret=$?
> printf 'MYSEPARATOR%s' "$out"
> exit "$ret"
> )
> ret=$?
> out=${err_and_out##*MYSEPARATOR}
> err=${err_and_out%MYSEPARATOR}

Thanks, I think that's rather neat.

In my particular case, if I can examine the error output and the normal
output separately I can even dispense with the exit code. It makes it
short and sweet.

<snip length-based version of the same>
--
Ben.

Kaz Kylheku

unread,
Jun 8, 2012, 6:58:22 PM6/8/12
to
On 2012-06-08, Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
> I have a bash script that captures the output of a command (it's gpg, as
> it happens) and also tests the exit code:
>
> out=$(command 2>/dev/null)
> [[ $? -eq 0 ]] || exit 1

More simply:

out=$(command ...) || exit 1

A variable assignment acts as a command, with a termination status
that can be tested in the normal way.

> In order to improve the error messages from the script, I'd now like to
> distinguish between two error cases, but the command exists with the
> same $? in both. The error output, however, *is* different.

Unfortunately there is no shell feature for capturing standard error and
standard output into different variables.

Moreover, fifo-based hacks to avoid creating a file run into the problem
that they require parallel subprocesses, which cannot set a variable
in the parent (other than via some print/eval hack: that may be something).

> In short, am I missing some trick that can simplify this:
>
> tmp=$(mktemp)
> out=$(command 2>$tmp)
> if [[ $? -ne 0 ]]; then
> # examine content of $tmp and report accordingly
> fi
> rm $tmp

The concern about what goes into the temp file could be addressed
using Bash's process substitution.
Perhaps process substitution, if the "examine content" part can be
confined to a subprocess:

if ! out=$(command ... 2> >(grep "whatever" > $tmp)) ; then
# process temp file contents.
fi

That is to say, we redirect stderr not directly to a file, but
to a pipeline which greps out some relevant part of the error
message and sends only *that* to a file.

Hopefully this will suppress something sensitive in the error
output that should not go into the file.
0 new messages