0

I have a process for which I would like to ignore a certain class of errors for. Unfortunately, there aren't distinguishable return codes for these particular errors, but their are distinguishable error messages. In short, when running:

npm run build

The text 'npm ERR! missing script: build' will show up in stderr if a "build" script is missing. However, in this particular case, I don't want to consider that an error, i.e. I want to return 0 as teh return code.

Some thoughts on what the solution may entail:

  1. Redirect stderr to pipe to grep for 'npm ERR! missing script: build'
  2. Leave stdout alone
  3. If grep finds the text, it will return 0, which is good, because this is a "success" to me anyway. In this case, I just want stderr to be ignored, however as a bonus, it might be nice to write out a "warning" to stdout
  4. If grep doesn't find the text, then I want the text to be written out to stderr and I want the "original" error return code returned.

Update 1: So far, I tried the following, somewhat naive, approach:

#!/bin/bash

npm run build 2> temp.file
grep 'npm ERR! missing script: build' temp.file
RC=$?

if [[ RC -eq 0 ]]; then
  echo 'WARNING: No "build" script found'
fi

..then I ran the following test cases:


Scenario 1: Valid build script: "build": "echo 'placeholder build script'"

Output: $ ./build.sh

consumer-app-cod@1.0.0-alpha build C:\projects\epo-consumer-app\app echo 'placeholder build script'

'placeholder build script'

Expected: Looks good


Scenario 2: Invalid build script: "build": "invalid-bin"

$ ./build.sh

consumer-app-cod@1.0.0-alpha build C:\projects\epo-consumer-app\app invalid-bin

Expected: something like: $ invalid-bin bash: invalid-bin: command not found


Scenario 3: Missing build script

$ ./build.sh npm ERR! missing script: build WARNING: No "build" script found

Expected: Looks good


A few things:

  1. The second test case failed like it should have, but it didn't print stderr (because I'm not sure how to get stderr back to stdout after I already redirected it to the temp file).
  2. The temp file doesn't seem ideal and it seems like maybe there should be a way just to redirect it through pipes without the intermediate file, but I'm not even sure where to start with that
sparty02
  • 566
  • 1
  • 6
  • 13
  • What have you tried? Most of us here are happy to help you improve your craft, but are less happy acting as short order unpaid programming staff. Show us your work so far in an [MCVE](http://stackoverflow.com/help/mcve), the result you were expecting and the results you got, and we'll help you figure it out. – ghoti Jan 22 '18 at 22:13
  • @ghoti yeah, my bad, I should have included some more background before posting it at first. I've updated the original description. – sparty02 Jan 22 '18 at 22:42
  • 2
    `retval=0; exec 3>&1; npm_stderr=$(npm run build 2>&1 1>&3) || retval=$?; exec 3>&-; if (( retval != 0 )) && ! [[ $npm_stderr = *"Ignore this specific error"* ]]; then printf '%s\n' "$npm_stderr" >&2; exit "$retval"; fi` -- replace `"Ignore this specific error"` with whatever string you prefer. – Charles Duffy Jan 22 '18 at 22:52

1 Answers1

0

In case anyone else stumbles across this question in a Google search, here's my solution to the problem:

ERROR=
ERROR_CAPTURE_FILE=$(mktemp -t errcap.XXXXXX)
add-exit-trap "/bin/rm -f $ERROR_CAPTURE_FILE"

function stderr-capture
{
    ERROR=
    
    # oblig SO ref: https://unix.stackexchange.com/a/333204/456858
    local exitval=0 ; exec 3>&1 ; { "$@" 2>&1 >&3 | tee $ERROR_CAPTURE_FILE ; } >&2 || exitval=$?
    
    if [[ $exitval -ne 0 ]]
    then
        ERROR=$(<$ERROR_CAPTURE_FILE)
    fi
    
    return $exitval
}

function selectively-ignore-errors
{
    local command="stderr-capture $1 2>/dev/null"
    shift

    local ignore_patternfile=$(mktemp -t errign.XXXXXX)
    add-exit-trap "/bin/rm -f $ignore_patternfile"
    >$ignore_patternfile
    for pattern
    do
        echo "^$pattern\$" >>$ignore_patternfile
    done

    # oblig SO ref: https://stackoverflow.com/a/18622662/1383177
    local exitval=0 ; eval "$command" || exitval=$?

    local final_error=$(echo "$ERROR" | grep -vf $ignore_patternfile ||:)
    if [[ $final_error ]]
    then
        echo "$final_error" >&2
        return $exitval
    fi
    return 0
}

Notes:

  • This is separated into two functions, because they are usable (and useful) separately.
  • This will work both under set -e and set -u. (If you omit the arg to selectively-ignore-errors, that's an error, but that's an error anyway.)
  • The add-exit-trap function (not shown) is a way to aggregate commands which will all be run on trap EXIT. This gets around the fact that normally traps on the same signal overwrite each other.
  • The entire first arg to selectively-ignore-errors is the command to be run, then all remaining args are patterns (BREs) which must match the entire line of a STDERR message you want to ignore. (If you don't want to force the entire line, you can just remove the ^ and \$ anchors in the for pattern loop, but I find it safer this way, because you don't want to be too aggressive in ignoring errors.)

Caveats:

  • The lines from STDERR are held till the end of the command and spat out all at once, so you do lose the position of the errors relative to the STDOUT output.
  • There's an eval in there, which some may find objectionable. This makes it possible to pass in commands which include redirections. It's probably possible to rejigger the function to avoid the eval, but I never bothered because all the calls to this function come from my own code.
  • This assumes that error messages correspond to a non-zero exit code, and vice versa. Warning messages might throw it off, though it probably wouldn't be hard to fix that shortcoming.