48

I was writing a script and then came across a odd problem. If I'd source a script that contains a bunch of functions that may call an error function which outputs a string and then exits, it will exit my shell. I know why it does it. It is because a function call is in the same process space as the caller (at least it is in bash), so the exit within the function terminates the current process with the exit code provided. Example:

error()
{
  echo $1
  exit 1
}

fn()
{
  if [ $# == 0 ]; then
    error "Insufficient parameters."
  fi
  # do stuff
}

$ fn
Insufficient parameters.
[shell terminates]

So my question is, can I exit all functions in the function stack without terminating the current shell and without spawning a new subshell?

Thanks

6 Answers 6

50

To exit the function stack without exiting shell one can use the command:

kill -INT $$

As pizza stated, this is like pressing Ctrl-C, which will stop the current script from running and drop you down to the command prompt.

 

 


Note: the only reason I didn't select pizza's answer is because this was buried in his/her answer and not answered directly.

6
  • 3
    The following "return" is preferable it seems: this is the functionality built into the shell for this purpose. Killing the shell is inelegant. Commented Jan 10, 2016 at 12:05
  • 4
    @JPGConnolly what do you mean by The following "return" is preferable it seems? If you mean to use the builtin return statement, then you are not reading the question as there is no easy way to use it such that it will jump back to the base shell from within a function called several functions deep other than having to interrogate the return value of each function call, which in some cases is not practicable or possible. Doing a kill -INT $$ is quite a bit more elegant than using return in this case.
    – Adrian
    Commented Jan 11, 2016 at 17:04
  • What value of $? does the caller of the source function see? Or does it terminate that too?
    – Michael
    Commented Sep 8, 2016 at 22:34
  • @Michael, the value of $$ is the pid of the shell process.
    – Adrian
    Commented Sep 8, 2016 at 23:18
  • 1
    @JPGConnolly ... as I understand the question, we want to BREAK from a source-d script without exiting the shell. That is definitely why I'm here. It is for an aborted 'escape' sourced code. Pardon me for point-out that the feature is available in Windows (batch) shell as exit /b - Only exit the script, and not the shell (window).
    – will
    Commented Feb 22, 2017 at 13:02
16

you can do a

exit() { return $1;}

then

source ./your_script 

In answer to the skeptics, this only affect the current shell, it does not affect shells you spawn.

The more informative form can be

exit() {
    local ans
    local line
    read -p "You really want to exit this? " line
    ans=$(echo $line)
    case "$ans" in
            Y);;
            y);;
            *)kill -INT $$;;
    esac
    unset -f exit
    exit $1
}
7
  • This isn't valid Bash syntax. Commented Jun 22, 2012 at 6:40
  • This won't work. It will just cause the function that is in error to be returned to. Not what I want.
    – Adrian
    Commented Jun 25, 2012 at 17:41
  • 1
    if you don't want it return you can replace "return $1" with "kill -INT $$", that brings it back to your current shell in a prompt.
    – pizza
    Commented Jun 25, 2012 at 19:39
  • 1
    it is like pressing control-C it does not kill the current shell and it unwinds all the current functions and back to the prompt.
    – pizza
    Commented Jun 25, 2012 at 20:21
  • 2
    This is a useful hack to avoid exiting without touching the existing code. However, if the code is to be run in the user's current shell context and can be modified, I'd recommend the kill -INT $$;.
    – TrinitronX
    Commented Jun 29, 2013 at 3:56
7

You'll need to add return statements to each of your functions to check the return value of any functions they call in turn. Sourcing a file is like cutting and pasting the code into the current context, with the minor exception of variables like $BASH_SOURCE.

Alternatively you could define fn as a shell script, so that exit will do what you want (unless a fork is too expensive).

3
  • 1
    Doing a check for all function exit values is not useful. Currently, to get around this problem, I'm having the shell script call the individual functions inside it. Thus exiting it will not exit my shell.
    – Adrian
    Commented Jun 25, 2012 at 17:40
  • You don't need to check for all exit values - Just check whether it succeeded - if my_function - or whether the exit code is non-zero - my_function; if [ $? -ne 0 ].
    – l0b0
    Commented Jun 26, 2012 at 10:40
  • Defining the function as a shell script is a good solution, however using "exit" in that function will exit the shell. You're right in using "return" to stop the function without exiting the shell. Commented Jan 10, 2016 at 12:06
3

using return statement, but you need to add return after calling error

7
  • 1
    Functions return without needing a return statement. Commented Jun 21, 2012 at 15:22
  • 2
    but OP wants the function to terminate immediately, not when it gets to the bottom of the function body.
    – Mark Reed
    Commented Jun 21, 2012 at 15:23
  • I want all functions on the stack to terminate. What does "but in the if ; before do stuff" mean?
    – Adrian
    Commented Jun 25, 2012 at 17:35
  • I was answering to Dennis, Adrian, why not spawning a new shell in a new function, it's the easiest solution I think: fnc() (fn) , or easier to read fnc() { (fn)} Commented Jun 25, 2012 at 18:33
  • 1
    @Wildcard if the call is the last statement of the function, the status is returned implicitly, otherwise special variable $? can be used Commented Nov 30, 2015 at 10:55
2

The shell doesn't really have an exception mechanism for rewinding through many function calls at once. You have to just check return values and manually return all the way down.

3
  • I am checking on the way down. Just want to exit all functions that are currently being run without exiting the shell.
    – Adrian
    Commented Jun 25, 2012 at 17:33
  • 1
    Which means you use return instead of exit. But what I meant by all the way down is that you have to do a separate return inside each function, not just once in the innermost.
    – Mark Reed
    Commented Jun 25, 2012 at 22:45
  • 1
    That is unfortunately not useful to what I wanted to do.
    – Adrian
    Commented Apr 30, 2013 at 12:14
0

Nest the script in a shell:

#!/bin/bash
(
function foo { echo exiting; exit 1; }
foo
)

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.