97

I was using the "exit 1" statement in my Bash functions to terminate the whole script and it worked fine:

function func()
{
   echo "Goodbye"
   exit 1
}
echo "Function call will abort"
func
echo "This will never be printed"

But then I realized that it doesn't do the work when called like:

res=$(func)

I understand that I created a subshell and "exit 1" aborts that subshell and not the primary one....

But is there a way to write a function which aborts the whole execution, no matter how it is called? I just need to get the real return value (echoed by the function).

Peter Mortensen
  • 30,030
  • 21
  • 100
  • 124
LiMar
  • 2,652
  • 2
  • 21
  • 27

6 Answers6

112

What you could do, is register the top level shell for the TERM signal to exit, and then send a TERM to the top level shell:

#!/bin/bash
trap "exit 1" TERM
export TOP_PID=$$

function func()
{
   echo "Goodbye"
   kill -s TERM $TOP_PID
}

echo "Function call will abort"
echo $(func)
echo "This will never be printed"

So, your function sends a TERM signal back to the top level shell, which is caught and handled using the provided command, in this case, "exit 1".

FatalError
  • 50,375
  • 14
  • 97
  • 115
  • 1
    I was thinking of the C `setsid()` function, but it doesn't work quite the same way. Updated to not use the `setsid` command, as it would require us to start a new process. – FatalError Mar 27 '12 at 17:09
  • 3
    Works. Any level of function nesting, any flow... Just works. – LiMar Mar 27 '12 at 18:04
  • Is it possible to make a general abort() function out of this that exits using the code of the first argument, e.g. `abort 2` would do the `trap "exit 2" TERM` before `kill` ? – Neil C. Obremski Nov 22 '13 at 20:53
  • @NeilC.Obremski: Sure. It's mainly an issue of how to get the value to propagate back to the top level shell. Probably use a command like `tempfile` to select a place to store the code in the top shell, then have the shell that's exiting write the code to that file, then just read in the parent's handler. – FatalError Nov 25 '13 at 14:34
  • 2
    Seems to not work when there are intervening subshells. See alt solution for bash using `set -E` [here](http://unix.stackexchange.com/questions/48533/exit-shell-script-from-a-subshell) – Ron Burk May 10 '15 at 05:18
  • Yes, it does not work like it should as @RonBurk says, if there are some subshells – Igor Chubin Jul 11 '17 at 10:03
  • `TERM`? Don't you mean [`SIGTERM`](https://en.wikipedia.org/wiki/Signal_(IPC)#POSIX_signals)? – Peter Mortensen Feb 28 '21 at 00:41
  • 1
    @PeterMortensen `TERM` works... why would `SIGTERM` be better? – Jonathan Cross Mar 14 '22 at 15:50
48

You can use set -e which exits if a command exits with a non-zero status:

set -e 
func
set +e

Or grab the return value:

(func) || exit $?
Community
  • 1
  • 1
brice
  • 23,143
  • 7
  • 76
  • 94
  • 3
    Interestingly I see a solution. "set -e" in the main script causes any function returning 1 (or exiting with it) to abort the whole execution. Unfortunately "set -e" changes drastically all the behavior and I cannot use it. – LiMar Mar 27 '12 at 16:49
  • 1
    You should use (( )) for numerical comparisons. Inside of [ ] -gt, -eq, etc should be used. – jordanm Mar 27 '12 at 17:04
  • 20
    or even `res=$(func) || exit` – glenn jackman Mar 27 '12 at 17:14
  • @glennjackman gets the cookie, I think. that's much cleaner that the garbage I have! – brice Mar 27 '12 at 17:15
  • 3
    that kind of defies the spirit of the question. if every function call needs to catch the error itself, there is no point in using `exit` in the first place. the function could simply return. / however, if `set -e` is set globally, this does make good sense – phil294 Sep 14 '17 at 22:41
  • What @glennjackman posted won't work if you had `local res=...` instead of `res=...`, just wasted more time than I wish on that... – captainGeech Jul 04 '21 at 08:15
  • @captainGeech, shellcheck.net will warn you about that: https://github.com/koalaman/shellcheck/wiki/SC2155 – glenn jackman Jul 04 '21 at 16:59
  • @glennjackman haven't heard of shellcheck before, that's awesome! thanks for sharing – captainGeech Jul 07 '21 at 01:06
2

I guess better is

#!/bin/bash
set -e
trap "exit 1" ERR

myfunc() {
     set -x # OPTIONAL TO SHOW ERROR
     echo "Exit with failure"
     set +x # OPTIONAL
     exit 1
}
echo "BEFORE..."
myvar="$(myfunc)"
echo "AFTER..But not shown"
2

A child process can't force the parent process to close implicitly. You need to use some kind of signaling mechanism. Options might include a special return value, or perhaps sending some signal with kill, something like

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}
Daenyth
  • 33,666
  • 12
  • 82
  • 120
0

If you just need top be able to bomb out of your scripts from within a function, you can do:

function die () {
    set -e
    /bin/false
}

then elsewhere, within your functions, instead of using "exit", use "die".

John
  • 1
0

But is there a way to write a function which aborts the whole execution, no matter how it is called?

No.

I just need to get the real return value (echoed by the function).

You can

res=$(func)
echo $?
Luca
  • 4,163
  • 1
  • 20
  • 24