287

When I use exit command in a shell script, the script will terminate the terminal (the prompt). Is there a way to terminate a script and then staying in the terminal?

My script run.sh is expected to execute by directly being sourced, or sourced from another script.

To be more specific, there are two scripts, run2.sh as

...
. run.sh
echo "place A"
...

and run.sh as

...
exit
...

when I run it by . run2.sh, and if it hit exit codeline in run.sh, I want it to stop to the terminal and stay there. But using exit, the whole terminal gets closed.

PS: I have tried to use return, but echo codeline will still gets executed...

5
  • 7
    I really, really, really have to ask: why are you using exit in a sourced script? Commented Mar 9, 2012 at 20:44
  • 4
    the exit command should not terminate your terminal session/login. if you use exit 0 to terminate the script after success, when you run your script ex: ./test.sh you should see the output but your console will remain open. Commented Mar 9, 2012 at 20:45
  • You could use the shell command, that opens in fact a shell terminal. My own experience however is that this doesn't happen with exit. Exit normally gives back the control to the parent script. Commented Mar 9, 2012 at 20:45
  • This makes no sense to me. It only kills process (aka the terminal) if called from the top level (globally.) And why would you do that? At the top level, it exits gracefully by itself after completing anyway. Am I missing something here? Commented Aug 23, 2021 at 5:35
  • 12
    @IgnacioVazquez-Abrams your comment does little except to make the OP feel bad. It sort of hints that the problem has to do with sourcing the script, but why not just say that instead of making someone feel bad for trying it? After all, it's a kind of subtle point that exec and source run differently in terms of shell. Ah, but I see that five people have joined you in being mean... Commented Oct 17, 2021 at 8:39

19 Answers 19

403

The "problem" really is that you're sourcing and not executing the script. When you source a file, its contents will be executed in the current shell, instead of spawning a subshell. So everything, including exit, will affect the current shell.

Instead of using exit, you will want to use return.

Sign up to request clarification or add additional context in comments.

7 Comments

Here's another explanation that I found helpful: askubuntu.com/a/53179/148337
Although correct, this is not a great answer. It ignores that the calling script may declare variables or functions that that called script needs access to. Better to explain how to set a return code and then process it in runs.sh @ruakh has the better answer to this question.
What if the function is a nested call? i.e. a calls b, b calls c, c wants to immediately exit a and b.
Source is almost the same as copy paste. It's better to use sh <script> or bash <script> if one wants to run a script and terminate at some point
bash: return: can only `return' from a function or sourced script
|
60

Yes; you can use return instead of exit. Its main purpose is to return from a shell function, but if you use it within a source-d script, it returns from that script.

As §4.1 "Bourne Shell Builtins" of the Bash Reference Manual puts it:

return [n]

Stop executing a shell function or sourced file and return the value n to its caller. If n is not supplied, the return value is the exit status of the last command executed. […]

[…]

The return status is non-zero if return is supplied a non-numeric argument or is used outside a function and not during the execution of a script by . or source.

10 Comments

return can ONLY be used from a function. If you use return and execute it as a shell script (e.g. sh run.sh), bash will report an error - return: can only return' from a function or sourced script`
@TzungsingDavidWong: You do realize that the bulk of this answer is a quotation from the official reference manual? And that the error-message you quote agrees with this answer instead of with your claim?
l am not disagree with you. I simply want to point out that return won't work if the script is run as a shell script and not executed by . (or source). BTW, where can I find the document about source -d?
There are times when I will run my scripts dotsource and other times when I will run the same scripts as a shell script. So, is there a way to detect internally to a script, whether it has been run dotsourced, or whether it has been run as a shell script? That way, I can detect how it is being invoked and handle that situation appropriately?
Yes it is possible to determine if sourced or not, check out this great thread: stackoverflow.com/a/28776166/4872338
@YorSubs: But if it sets profile settings, does that even have any effect when the script is run as a normal script?
|
29

You can add an extra exit command after the return statement/command so that it works for both, executing the script from the command line and sourcing from the terminal.

Example exit code in the script:

   if [ $# -lt 2 ]; then
     echo "Needs at least two arguments"
     return 1 2>/dev/null
     exit 1
   fi

The line with the exit command will not be called when you source the script after the return command.

When you execute the script, return command gives an error. So, we suppress the error message by forwarding it to /dev/null.

3 Comments

this is clever. does it have a downside?
A mite too clever... would be the only downside. Someone's gonna see that and delete one or the other. Probably, me.
That is what comments are for ;) I use this: # Return + exit based on: stackoverflow.com/a/54344104
13

Instead of running the script using . run2.sh, you can run it using sh run2.sh or bash run2.sh.

A new sub-shell will be started. To run the script then, it will be closed at the end of the script, leaving the other shell opened.

4 Comments

If so, then why the second parameter sh "." run2.sh?
@H0WARD You're right I forgot to remove the dots. I have edited the answer now.
The OP explicitly states source as a requirement.
@JonathanNeufeld Yes, but I, and many others like me, found this post because I habitually source bash scripts and was wondering why I kept killing my terminal when testing a script someone else wrote. TIL the critical difference between source and sh is shell / sub-shell. (NB: I prefer Perl for most non-trivial scripts, hence my ignorance of this issue until now.)
13

For what it’s worth, I've been using the following for years in all my scripts; it works like a charm in all use cases I've encountered—I have extensive shell scripts that are sourced at startup, plus many Bash functions I regularly use. I remember trying return long back and having issues with it.

kill -INT $$
# Here's the definition...
# SIGINT    2    Interrupt from keyboard

1 Comment

Goog idea, thank you for sharing ... the "kill" command should be called something like "sendsignal" because that's what actually does, the kill signal is just one of them, the default one but it's OK
7

Actually, I think you might be confused by how you should run a script.

If you use sh to run a script, say, sh ./run2.sh, even if the embedded script ends with exit, your terminal window will still remain.

However, if you use . or source, your terminal window will exit/close as well when subscript ends.

For more detail, please refer to What is the difference between using sh and source?.

Comments

5

I had the same problem and from the previous answers and from what I understood, this ultimately worked for me:

  1. Have a shebang line that invokes the intended script, for example,

#!/bin/bash uses bash to execute the script

I have scripts with both kinds of shebang's. Because of this, using sh or . was not reliable, as it lead to a mis-execution (like when the script bails out having run incompletely)

The answer therefore, was

    • Make sure the script has a shebang, so that there is no doubt about its intended handler.

    • chmod the .sh file so that it can be executed. (chmod +x file.sh)

    • Invoke it directly without any sh or .

      (./myscript.sh)

Comments

4

To write a script that is secure to be run as either a shell script or sourced as an rc file, the script can check and compare $0 and $BASH_SOURCE and determine if exit can be safely used.

Here is a short code snippet for that

[ "X$(basename $0)" = "X$(basename $BASH_SOURCE)" ] && \
    echo "***** executing $name_src as a shell script *****" || \
    echo "..... sourcing $name_src ....."

1 Comment

This is a very good answer, even with the quirk that it returns: basename: unknown option -- b when sourcing. You should also address the exit and $name_src for completeness. An explanation that the checking is done by using s sub-shell should also be stated.
3

This is just like you put a run function inside your script run2.sh. You use exit code inside run while source your run2.sh file in the bash tty. If the give the run function its power to exit your script and give the run2.sh its power to exit the terminator. Then of cuz the run function has power to exit your teminator.

    #! /bin/sh
    # use . run2.sh

    run()
    {
        echo "this is run"
        #return 0
        exit 0
    }

    echo "this is begin"
    run
    echo "this is end"

Anyway, I approve with Kaz it's a design problem.

2 Comments

Not clear from the text what this answer attempts to do, but it certainly does not solve the question at hand.
I don't see how this reply provides anything useful. The OP problem was related to sourcing a script that uses 'exit' instead of 'return' .
3

I improved the answer of Tzunghsing, with more clear results and error redirection, for silent usage:

#!/usr/bin/env bash

echo -e "Testing..."

if [ "X$(basename $0 2>/dev/null)" = "X$(basename $BASH_SOURCE)" ]; then
    echo "***** You are Executing $0 in a sub-shell."
    exit 0
else
    echo "..... You are Sourcing $BASH_SOURCE in this terminal shell."
    return 0
fi

echo "This should never be seen!"

Or if you want to put this into a silent function:

function sExit() {
    # Safe Exit from script, not closing shell.
    [ "X$(basename $0 2>/dev/null)" = "X$(basename $BASH_SOURCE)" ] && exit 0 || return 0
}

...

# ..it has to be called with an error check, like this:
sExit && return 0

echo "This should never be seen!"

Please note that:

  • if you have enabled errexit in your script (set -e) and you return N with N != 0, your entire script will exit instantly. To see all your shell settings, use, set -o.
  • when used in a function, the 1st return 0 is exiting the function, and the second return 0 is exiting the script.

1 Comment

Of course, the exit code doesn't have to be 0 but can also be user provided as shown in Shahid's similar answer in this thread.
2

It's correct that sourced vs. executed scripts use return vs. exit to keep the same session open, as others have noted.

Here's a related tip, if you ever want a script that should keep the session open, regardless of whether or not it's sourced.

The following example can be run directly like foo.sh or sourced like . foo.sh/source foo.sh. Either way it will keep the session open after "exiting". The $@ string is passed so that the function has access to the outer script's arguments.

#!/bin/sh
foo(){
    read -p "Would you like to XYZ? (Y/N): " response;
    [ $response != 'y' ] && return 1;
    echo "XYZ complete (args $@).";
    return 0;
    echo "This line will never execute.";
}
foo "$@";

Terminal result:

$ foo.sh
$ Would you like to XYZ? (Y/N): n
$ . foo.sh
$ Would you like to XYZ? (Y/N): n
$ |
(terminal window stays open and accepts additional input)

This can be useful for quickly testing script changes in a single terminal while keeping a bunch of scrap code underneath the main exit/return while you work. It could also make code more portable in a sense (if you have tons of scripts that may or may not be called in different ways), though it's much less clunky to just use return and exit where appropriate.

Comments

2

I think that this happens because you are running it on source mode with the dot:

. myscript.sh

You should run that in a subshell:

/full/path/to/script/myscript.sh

See 'source'.

1 Comment

You don't need full path to script if . myscript.sh works. At most, you might need ./myscript.sh.
1

Also make sure to return with the expected return value. Else if you use exit when you will encounter an exit, it will exit from your base shell since source does not create another process (instance).

Comments

1

The following trick allows the script to gracefully exit without terminating the shell, whether sourced or direct execution:

Instead of exit, use this:

[[ $PS1 ]] && return $? || exit $?

Alternatively, define this function and call it instead of exit:

    __EXIT__()
   {
       [[ $PS1 ]] && return $1 || exit $1
   }
    ....
    __EXIT__ $?

3 Comments

This works great! But you should describe how this works. What is special about $PS1?
$PS1 is a special variable in bash the specifies the content and format of a user prompt in an interactive shell. This variable is undefined for a non-interactive script, but defined if the same scripts is sourced, where the script is executed under the shell it was sourced from. That's how we differentiate direct execution vs sourcing of a script.
Indeed this is the only solution that worked consistently in MSYS and MINGW64. I tried defining a similar exit function using ${BASH_SOURCE[0]}, but that didn't work, or I didn't get the if statement correct. Your solution is portable, clean and clear.
0

If a command succeeded successfully, the return value will be 0. We can check its return value afterwards.

Is there a “goto” statement in bash?

Here is some dirty workaround using trap which jumps only backwards.


#!/bin/bash
set -eu
trap 'echo "E: failed with exitcode $?" 1>&2' ERR

my_function () {
    if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
        echo "this is run"
        return 0
    else
        echo "fatal: not a git repository (or any of the parent directories): .git"
        goto trap 2> /dev/null
    fi
}

my_function
echo "Command succeeded"  # If my_function failed this line is not printed

Related:

Comments

0

I couldn't find solution so for those who want to leave the nested script without leaving terminal window:

# this is just script which goes to directory if path satisfies regex
wpr(){
    leave=false
    pwd=$(pwd)
    if [[ "$pwd" =~ ddev.*web ]]; then
        # echo "your in wordpress instalation"
        wpDir=$(echo "$pwd" | grep -o  '.*\/web')
        cd $wpDir
        return
    fi
    echo 'please be in wordpress directory'
    # to leave from outside the scope
    leave=true
    return
}

wpt(){
    # nested function which returns $leave variable
    wpr
    
    # interupts the script if $leave is true
    if $leave; then
        return;
    fi
    echo 'here is the rest of the script, executes if leave is not defined'
}

Comments

0

If your terminal emulator doesn't have -hold, you can sanitize a sourced script and hold the terminal with:

#!/bin/sh
sed "s/exit/return/g" script >/tmp/script
. /tmp/script
read

Otherwise, you can use $TERM -hold -e script.

1 Comment

Lol. I think this must be the most exotic solution here. A script that basically rewrites itself before execution.
0

I don't have any idea whether this is useful for you or not, but in Z shell (executable zsh), you can exit a script, but only to the prompt if there is one, by using parameter expansion on a variable that does not exist, as follows.

${missing_variable_ejector:?}

Though this does create an error message in your script, you can prevent it with something like the following.

{ ${missing_variable_ejector:?} } 2>/dev/null

Comments

-4
  1. exit 0 will come out of the script if it is successful.

  2. exit 1 will come out of the script if it is a failure.

You can try these above two based on your requirement.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.