Bash – simple test for anything printed on stderr in shell/Bash

bashexitfile-descriptorsstderrsubshell

I'd like invoke a command inside a shell script for Continuous Integration purposes. Exit status 0 means succes, otherwise failure. I'm writing a wrapper script to run several commands and fail if any error has occurred from one of them.

However, one of the commands (3rd party software) does not conform to the "de facto" exit status != 1 on failure. It does, however, print its errors on stderr in case of a failure.

Current wrapper script, which should have worked fine if both mycommand and other-command will fail with exit status != 0 because of the -e switch:

#!/bin/bash -ex
mycommand --some-argument /some/path/to/file
other-command --other-argument /some/other/file

How do I check for anything being printed to stderr (to fail the main script)? Here's what I've tried:

  1. stderr output redirect to file, check file contents.
    Would like to avoid creating temporary files.
  2. Redirect stderr to subshell stdin, e.g.:

    mycommand 2> >(if grep .; then echo NOK; else echo OK; fi)
    

    This seems to work fine, however, I am not able to control the main shell here to exit, i.e. exit 1 won't exit the main program. Neither can I control variables outside the subshell to propagate its result out. Do I really have to create a named pipe or something?

  3. Set up extra file descriptors like this answer.
    Doesn't look very elegant to me, really.

Some 'requirements':

  • It should not fail on regular output on stdout (output there too).
  • I'd like to retain the otherwise useful output on stdout.
  • I'd like to keep any output currently on stderr printed (could be to stdout, but should not be hidded).

So it should behave like a wrapper that only exits with an unclean status, retaining printed output.

I was just hoping that there's something more elegant to check for anything in stderr. Bashisms acceptable.

Best Answer

You could do (POSIXly):

if { cmd 2>&1 >&3 3>&- | grep '^' >&2; } 3>&1; then
  echo there was some output on stderr
fi

Or to preserve the original exit status if it was non-zero:

fail_if_stderr() (
  rc=$({
    ("$@" 2>&1 >&3 3>&- 4>&-; echo "$?" >&4) |
    grep '^' >&2 3>&- 4>&-
  } 4>&1)
  err=$?
  [ "$rc" -eq 0 ] || exit "$rc"
  [ "$err" -ne 0 ] || exit 125
) 3>&1

Using exit code 125 for the cases where the command returns with a 0 exit status but produced some error output.

To be used as:

fail_if_stderr cmd its args || echo "Failed with $?"