7

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.

7
  • My vague thought would be that if you modify attempt 2 to use a pipe instead, you'd get the exit code of the piped-through code, right? Aug 23 '17 at 15:01
  • @UlrichSchwarz yeah, but there's also perfectly valid output on stdout. Forgot to mention that. I'd like to maintain all output. Will update Q.
    – gertvdijk
    Aug 23 '17 at 15:04
  • I agree with Ulrich; also, what do you mean however, I am not able to control the main shell here to exit?
    – Alex
    Aug 23 '17 at 15:04
  • @Alex clarified in Q I hope :)
    – gertvdijk
    Aug 23 '17 at 15:10
  • exit 1 exits the script... what do you mean, it won't exit the main program?
    – Alex
    Aug 23 '17 at 15:43
3

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 $?"
1
  • Beat me to it. Alright, I will edit in the link you provided.
    – Alex
    Aug 23 '17 at 16:19
0
# output "NOK" if standard error has any output; "OK" otherwise:
errlog=$(mktemp)
somecommand 1>> "$stdlog" 2> "$errlog"
if [[ -s "$errlog" ]]; then
    # File exists and has a size greater than zero
    echo "NOK"
else
    echo "OK"  
fi
# Done parsing standard error; tack it to the regular log
cat "$errlog" >> "$stdlog"
rm -f "$errlog"
3
  • Well, I think I've described this approach in my Q (creating temp files). I've posted the Q in the hope to avoid all that.
    – gertvdijk
    Aug 23 '17 at 15:53
  • In fairness, redirecting to a subshell is essentially creating temporary files; it just all happens behind the scenes.
    – DopeGhoti
    Aug 23 '17 at 15:54
  • In fairness, redirecting to a subshell is essentially creating temporary files; it just all happens behind the scenes. Really? I can't seem to find information on what kind of files and where I could find them.
    – gertvdijk
    Aug 23 '17 at 15:57
0

The most voted answer will work in most cases. But since I use set +o errexit in bash it errored out. This should work better in bash:

fail_if_stderr() (
  # save current options
  bash_options="${-}"
  
  # disable exit on error
  set +o errexit
  
  # Save return code of command in rc
  rc=$({
    ("$@" 2>&1 >&3 3>&- 4>&-; echo "$?" >&4) |
    grep '^' >&2 3>&- 4>&-
  } 4>&1)
  
  # Save return code of grep in err_in_stderr
  err_in_stderr=$?
  
  # enable exit on error if it was previously enabled
  test "${bash_options#*e*}" != "$bash_options" && set -o errexit
  
  # exit with original return code if it's not zero
  [ "$rc" -eq 0 ] || exit "$rc"
  
  # exit with return code 125 if something was in stderr
  [ "$err_in_stderr" -ne 0 ] || exit 125
) 3>&1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.