Home > database >  Difference of behavior between “set -e source” and “bash -ec source”
Difference of behavior between “set -e source” and “bash -ec source”

Time:03-27

Context

While setting up a basic unit testing system, I ran into an odd issue. My goal was to make sure all individual test scripts:

  • were run with set -e to detect errors, without needing to explicitly set this in each file;
  • knew right away about the functions to be tested (stored in another file) without needing to explicitly source those in each test file.

Observations

Let this be a dummy test file called to-be-sourced.sh. We want to be able to know if a command in it fails:

# Failing command!
false

# Last command is OK:
true

And here is a dummy test runner, which must run the test file:

#! /usr/bin/env bash

if (
    set -e
    . to-be-sourced.sh
)
then
    echo 'Via set: =0'
else
    echo 'Via set: ≠0'
fi

This yields Via set: =0, meaning that the runner is happy. But it should not! My hypothesis was:

set -e is not propagated within . sourcing, and as explained in the help for . and source, the exit status is the one of the last command.

But then I came up with a workaround that works, but also relies on .:

if bash -ec '. "$0"' to-be-sourced.sh
then
    echo 'Via bash: =0'
else
    echo 'Via bash: ≠0'
fi

This yields ≠0 whenever a command in the test file fails, regardless of whether that command was the last one of the test file. As a bonus, I can toss any number of . a/library/file.sh within the -c command, so each test file can use all of my functions out of the box. I should therefore be happy, but:

Why does this work, considering that the -c command also relies on . to load the test file (and I thought bash’s -e was equivalent to set’s -e)?

I also thought about using bash’s --init-file, but it appeared to be skipped when a script is passed as a parameter. And anyway my question is not so much about what I was trying to achieve, but rather about the observed difference of behavior.

Edit

Sounds like if is tempering with the way set -e is handled.

This halts execution, indicating failure:

. to-be-sourced.sh

… while this goes into the then (not the else), indicating success:

if . to-be-sourced.sh
then
    echo =0
else
    echo ≠0
fi

CodePudding user response:

(This may not be precisely correct, but I think it captures what happens.)

In your first example, set -e sets the option in a command that is lexically in the scope of an if statement, and so even though it is set, it is ignored. (You can confirm it is set by running echo $- inside to-be-sourced.sh. Note, too, that . itself has a 0 exit status, which you can confirm by replacing true with an echo statement; it's not that it fails but the failure is ignored.)

In your second example, -e sets the errexit option in a new process, which knows nothing about the if statement and therefore it is not ignored.

  •  Tags:  
  • bash
  • Related