3

script.sh:

#!/bin/bash
my-bin-file-i-run

if [ $? -eq 0 ]
then
    exit 0
else
    if [[ >&2 == *"name_to_handle_at"* ]]; then
        exit 0
    fi
    echo >&2
    exit 1
fi

I'd like to run my command and if it throws an error which the message includes "name_to_handle_at" it will handle it like the script had no errors, all other errors should be shown as usual. Can't really get it to work.

2 Answers2

6

Your syntax is faulty as you can't just compare the standard error of some previously executed command with == like that.

One suggestion is to save the error stream to a file and then parse that:

#!/bin/bash

if ! my-bin-file-i-run 2>error.log; then
    if ! grep -q -F 'name_to_handle_at' error.log; then
       echo 'some error message' >&2
       exit 1
    fi
fi

This would run the command and redirect the standard error stream to a file called error.log. If the command terminates with an error, grep is used to look for the string name_to_handle_at in the log file. If that can't be found, an error message is printed and the script terminates with a non-zero exit status.

In any other case, the script terminates with a zero exit status.

If you want the error.log file to be removed when your script terminates, you may do so explicitly with rm error.log in the appropriate places, or with an EXIT trap:

#!/bin/bash

trap 'rm -f error.log' EXIT

if ! my-bin-file-i-run 2>error.log; then
    if ! grep -q -F 'name_to_handle_at' error.log; then
       echo 'some error message' >&2
       exit 1
    fi
fi
terdon
  • 242,166
Kusalananda
  • 333,661
  • Ah, interesting! Is there a way to do this with variables instead of error files by any chance? – Karl Morrison Aug 16 '19 at 07:31
  • 2
    @KarlMorrison Capturing the output from a program is definitely possible with a command substitution, but since we don't know if this output may contain megabytes of data, or just a few lines of text, it may be safer to just send the data to a temporary file. The code also becomes a fair bit uglier (IMHO). – Kusalananda Aug 16 '19 at 07:37
  • Your trap at the top changed my mind, thanks! – Karl Morrison Aug 18 '19 at 11:38
  • @KarlMorrison There's also nothing stopping you from using mktemp to create the name of the error log file (errlog=$(mktemp), then ... 2>"$errlog" etc.) This would likely create the file under /tmp which may be a memory mounted filesystem (no writes to disk). – Kusalananda Aug 18 '19 at 11:44
0

You can pipe the stderr of your program to grep and use the (bash-specific) PIPESTATUS variable to distinguish between the 4 combinations of command succeeded / failed and command printed / did not print that error message:

{ your_command 2>&1 >&3 | grep your_error_message >/dev/null; } 3>&1
case ${PIPESTATUS[*]} in
0*) ;; # the command succeeded
*0) ;; # the command failed but printed the error message
esac

Example:

# usage wrapper pattern cmd args ...
wrapper(){
    msg=$1; shift
    { "$@" 2>&1 >&3 | grep "$msg" >/dev/null; } 3>&1
    case ${PIPESTATUS[*]} in
    0*|*0) return 0;;
    *) return "${PIPESTATUS[0]}";;
    esac
}

# usage test_cmd status error_message
test_cmd(){ status=$1; shift; echo >&2 "$@"; return "$status"; }

$ wrapper foo test_cmd 13 foo; echo $?
0
$ wrapper foo test_cmd 13 bar; echo $?
13

Notes:

Do not replace the grep >/dev/null with grep -q; that will cause grep to exit at the first match and cause your command to be SIGPIPEd.

You can however put a ... | tee /dev/stderr | ... between your command and the grep; that will cause error messages to be both passed to grep and printed to stderr.

Many subpar programs (especially python scripts) write error messages to stdout instead of stderr; if that's the case you can simply use

your_command 2>&1 | ...

instead of all that { 2>&1 >&3 | ... } >&3 fd juggling.