45

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

Adrian
  • 10,246
  • 4
  • 44
  • 110

6 Answers6

46

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.

Community
  • 1
  • 1
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • 2
    The following "return" is preferable it seems: this is the functionality built into the shell for this purpose. Killing the shell is inelegant. – JPGConnolly Jan 10 '16 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 Jan 11 '16 at 17:04
  • What value of $? does the caller of the source function see? Or does it terminate that too? – Michael Sep 08 '16 at 22:34
  • @Michael, the value of `$$` is the pid of the shell process. – Adrian Sep 08 '16 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 Feb 22 '17 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
}
pizza
  • 7,296
  • 1
  • 25
  • 22
  • This isn't valid Bash syntax. – Todd A. Jacobs Jun 22 '12 at 06:40
  • This won't work. It will just cause the function that is in error to be returned to. Not what I want. – Adrian Jun 25 '12 at 17:41
  • 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 Jun 25 '12 at 19:39
  • This won't work either as it was supposed to be running in the current shell, which that would kill. – Adrian Jun 25 '12 at 19:46
  • 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 Jun 25 '12 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 Jun 29 '13 at 03:56
  • Neat hack, simple and works great. – Arkham Angel Oct 27 '22 at 20:46
6

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).

l0b0
  • 55,365
  • 30
  • 138
  • 223
  • 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 Jun 25 '12 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 Jun 26 '12 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. – JPGConnolly Jan 10 '16 at 12:06
2

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

Bhavesh G
  • 3,000
  • 4
  • 39
  • 66
Nahuel Fouilleul
  • 18,726
  • 2
  • 31
  • 36
  • 1
    Functions return without needing a return statement. – Dennis Williamson Jun 21 '12 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 Jun 21 '12 at 15:23
  • I want all functions on the stack to terminate. What does "but in the if ; before do stuff" mean? – Adrian Jun 25 '12 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)} – Nahuel Fouilleul Jun 25 '12 at 18:33
  • Thanks Nahuell, I know that's the easiest solution. I was looking for one that didn't spawn an additional shell. – Adrian Jun 25 '12 at 19:47
  • Is there some way to `return` with the return status of another function? Something like `return thisfunc` within a function called `myfunc`, which (in my imaginary syntax) would (1) call `thisfunc` (2) get the return code (3) exit `myfunc` returning the same return status returned by `thisfunc`. – Wildcard Nov 30 '15 at 08:04
  • 1
    @Wildcard if the call is the last statement of the function, the status is returned implicitly, otherwise special variable $? can be used – Nahuel Fouilleul Nov 30 '15 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.

Mark Reed
  • 91,912
  • 16
  • 138
  • 175
  • I am checking on the way down. Just want to exit all functions that are currently being run without exiting the shell. – Adrian Jun 25 '12 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 Jun 25 '12 at 22:45
  • 1
    That is unfortunately not useful to what I wanted to do. – Adrian Apr 30 '13 at 12:14
0

Nest the script in a shell:

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