frans
frans

Reputation: 9758

bash: `set -e` does not work when used in if-expression?

Have a look at this little script:

#!/bin/bash

function do_something() {(
    set -e

    mkdir "/opt/some_folder"                                     # <== returns 1 -> abort?
    echo "mkdir returned $?"                                     # <== sets $0 to 0 again

    rsync $( readlink -f "${BASH_SOURCE[0]}" ) /opt/some_folder/ # <== returns 23 -> abort?
    echo "rsync returned $?"                                     # <== sets $0 to 0 again
)}


# here  every command inside `do_something` will be executed - regardless of errors
echo "run do_something in if-context.."
if ! do_something ; then
  echo "running do_something did not work"
fi

# here `do_something` aborts on first error
echo "run do_something standalone.."
do_something
echo $?

I was trying to do what was suggested here (don't miss the extra parentheses introducing a sub-shell) but I didn't execute the function (do_something in my case) separately but together with the if-expression.

Now when I run if ! do_something the set -e command seems to have no effect.

Can someone explain this to me?

Upvotes: 4

Views: 586

Answers (2)

ShadSterling
ShadSterling

Reputation: 1820

Using a function to change settings and traps overcomes this limitation, at least in Homebrew Bash 5.2.15(1)

If I start with this:

errexit_ignore()   {
    set +e
    trap -      ERR
}
errexit_fail() {
    set -e
    trap failed ERR
}
errexit_fail

I can later do horrible useful things like this:

for customization in ${customizations[@]}; do
    log_mark 2 "Checking ${customization} ..."
    errexit_ignore
    diff -qs "${expected}/${customization}" "${tmpdir}/${customization}"
    diff_exitcode="${?}"
    errexit_fail
    if [ "${diff_exitcode}" != "0" ]; then  ##  If it's not the expected file, the customization may have already been applied
        errexit_ignore
        diff -qs "${tmpdir}/${customization}" "${customized}/${customization}"
        diff_exitcode="${?}"
        errexit_fail
        if [ "${diff_exitcode}" != "0" ]; then  ##  If it's neither the expected file nor the customized file, either default has changed or the customization has changed
            errexit_ignore
            git ls-files --error-unmatch "${customized}/${customization}"
            track_exitcode="${?}"  ##  Detect untracked file
            git diff --exit-code "${customized}/${customization}"
            diff_exitcode="${?}"  ##  Detect modified tracked file
            errexit_fail
            if [ "${track_exitcode}" != "0" -o "${diff_exitcode}" != "0" ]; then  ##  If the customization has uncomitted changes, assume the default hasn't changed
                log_mark 1 "Customized ${customization} will be updated"
            else  ##  If the default has changed, manual review is needed (which may result in an updated customization)
                diff -u "${expected}/${customization}" "${tmpdir}/${customization}" || :
                diff -u "${tmpdir}/${customization}" "${customized}/${customization}" || :
                abort "Default version of ${customization} has changed, expected version must be updated and customization must be checked for compatibility"
            fi
        else
            log_mark 1 "Customized ${customization} already in place"
        fi
    else
        log_mark 1 "Default ${customization} has not changed"
    fi
done

Notes:

  • After trap function ERR, set +e doesn't work unless you also trap - ERR
  • Neither errexit_ignore nor errexit_fail can be defined on a single line (I'm not sure why not)

Upvotes: 0

Simon Doppler
Simon Doppler

Reputation: 2093

This is expected and described in the Bash Reference Manual.

-e

[...] The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, [...].

[...]

If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

Upvotes: 8

Related Questions