5

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?

1
  • Aside from the fact that it is documented in that way (as explained by Simon Doppler), it would be counterproductive if the -e would also abort inside an if. Note that the semantics of if foo; then ....; else ....; fi is to executed the then part, if foo returns a zero exit code, and the else part, if it returns a non-zero exit code. If set -e would abort the script even on such an invocation of foo, the else part never gets a chance to be executed. Commented Feb 3, 2020 at 10:03

2 Answers 2

8

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.

Sign up to request clarification or add additional context in comments.

1 Comment

0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.