salezica
salezica

Reputation: 76909

Bash: stop on error in sourced script

In a shell script to be executed, I can abort on errors using set -e.

In a sourced script, however, using set -e will kill the original shell if a later command exits with an error status.

source set_e.sh
./exit_1.sh
# shell dies

A trivial solution would be to set +e at the end of the script, but this would break the parent's set -e if used (which may very well happen if someone wraps my script in the future).

How can I get abort-on-error functionality in a sourced script?

Upvotes: 20

Views: 10352

Answers (6)

Joakim
Joakim

Reputation: 51

Setting, catching and unsetting set -e turned out rather fragile for me. Thus I prefer trapping for errors.

This is a one-liner I often use, including trying a silenced return (works if sourced), otherwise exit. Also, unsetting the trap, since otherwise that one stays around from sourced scripts.

trap 'echo Error $? on line $LINENO; trap - ERR; return 2>/dev/null || exit' ERR

Upvotes: 2

that other guy
that other guy

Reputation: 123450

You can check if set -e is already enabled and conditionally set it afterwards:

[[ $- == *e* ]] && state=-e || state=+e
set -e
yourcode
set "$state"

Note, however, that set -e is buggy and scripts should never depend on it for correctness. For example, if someone sources your script in an if statement, set -e may no longer work correctly:

echo '
 set -e
 ls file.that.doesnt.exist
 echo "Success"
' > yourscript

set -e
if ! source yourscript
then 
  echo "Initialization failed"
fi
echo "Done"

then set -e will longer abort on failure in yourscript in bash 4.3.30:

ls: cannot access file.that.doesnt.exist: No such file or directory
Success
Done

while it will exit the entire script in bash 2, 3 and up to 4.2:

ls: cannot access file.that.doesnt.exist: No such file or directory

Upvotes: 2

galaxy
galaxy

Reputation: 424

Well, the question is not very clear re: what the original author wanted after intercepting the error in the sourced script, however, as an entry point for the solution the following will suffice:

You can set a trap on ERR and handle the error inside the sourced script there. Below are two scenarios: one with the sourced script using "set -e" and another with the sourced script NOT using "set -e".

The primary script is calling the secondary script with defined "set -e" and catches an error:

[galaxy => ~]$ cat primary.sh
#!/bin/sh

set -e
echo 'Primary script'
trap 'echo "Got an error from the secondary script"' ERR
source secondary.sh
trap - ERR
echo 'Primary script exiting'
[galaxy => ~]$ cat secondary.sh
#!/bin/sh

echo 'Secondary script'
set -e
echo 'Secondary script generating an error'
false
echo 'Secondary script - should not be reached'
[galaxy => ~]$ ./primary.sh
Primary script
Secondary script
Secondary script generating an error
Got an error from the secondary script
[galaxy => ~]$

The primary script is calling the secondary script without "set -e" and catches an error:

[galaxy => ~]$ cat primary.sh
#!/bin/sh

set -e
echo 'Primary script'
trap 'echo "Got an error from the secondary script"' ERR
source secondary.sh
trap - ERR
echo 'Primary script exiting'
[galaxy => ~]$ cat secondary.sh
#!/bin/sh

echo 'Secondary script'
echo 'Secondary script generating an error'
false
echo 'Secondary script - should not be reached if sourced by primary.sh'
[galaxy => ~]$ ./primary.sh
Primary script
Secondary script
Secondary script generating an error
Got an error from the secondary script
[galaxy => ~]$

As a bonus: intercepting an error in the sourced script and continuing:

[galaxy => ~]$ cat primary.sh
#!/bin/sh

echo 'Primary script'
i=0
while [ $i = 0 ]; do
    i=1
    trap 'echo "Got an error from the secondary script"; break' ERR
    source secondary.sh
done
trap - ERR
echo 'Primary script exiting'
[galaxy => ~]$ cat secondary.sh
#!/bin/sh

echo 'Secondary script'
echo 'Secondary script generating an error'
false
echo 'Secondary script - should not be reached if sourced by primary.sh'
[galaxy => ~]$ ./primary.sh
Primary script
Secondary script
Secondary script generating an error
Got an error from the secondary script
Primary script exiting
[galaxy => ~]$

Upvotes: 5

David C. Rankin
David C. Rankin

Reputation: 84551

Change set -e to return 0 (or choose your favorite integer instead of 0). You can think of it as treating your sourced file as a function. For example:

$ cat myreturn.sh
#!/bin/bash

let i=0

while test "$i" -lt 10; do

    echo "i $i"
    if test "$i" -gt 5 ; then
        return 5
    fi
    sleep 1
    ((i+=1))

done

return 4

$ ( . myreturn.sh )
i 0
i 1
i 2
i 3
i 4
i 5
i 6

$ echo $?
5

Upvotes: 4

konsolebox
konsolebox

Reputation: 75478

It's impossible. However you can opt to use a subshell if you want:

(
    set -e
    source another.sh
)

Only that environment of calling script can never be altered by the called script.

Note: It may be important to separate both commands with newline and not use a semicolon.

Upvotes: 9

buff
buff

Reputation: 2053

You could detect by set -o if the errexit option was set at the beginning of the sourced script and recover its original value at the end of the sourced script.

Upvotes: 0

Related Questions