How to stop execution in script which may have been sourced or directly executed - bash

If my script gets sourced with
. ./my_script.sh
source ./my_script.sh
then to stop execution in the script, I would use return.
If my script is directly executed with
./my_script.sh
bash ./my_script.sh
then I'd insert an exit.
If I don't know whether the user will source it or directly execute it, how can I cleanly stop the script without killing the terminal it was called from?
Preferably, the code snippet should be able to terminate the script even if it were placed inside one of the script's functions.

Try the following:
ec=0 # determine the desired exit code
return $ec 2>/dev/null || exit $ec
return will succeed if the script is being sourced, otherwise exit will kick in. The 2>/dev/null suppresses the error message in case the script is not sourced.
The net effect is that the script will terminate with the desired exit / return code, regardless of whether it was sourced or not.
Update: The OP wants to be able to exit the script from inside a function within the script.
The only way I can think of is to place all of your script's code in a subshell and call exit from inside the functions inside that subshell; then place the return 2>/dev/null || exit command after the subshell (as the only statement outside the subshell):
#!/usr/bin/env bash
( # subshell to place all code in
foo() {
exit 1 # exit the subshell
}
foo # invoke the function
)
# Terminate script with exit code from subshell.
ec=$?; return $ec 2>/dev/null || exit $ec

Here's one way to find out which method was used to invoke the script.
if [[ "$0" == "bash" ]]; then
echo "source 'file' was used"
else
echo "bash 'file' was used"
fi
Update, In response to comment by #mklement0:
If your script is named my_script.sh, you can use
b=$(basename "$0")
if [[ "$b" == "my_script.sh" ]]; then
echo "bash 'file' was used"
else
echo "source 'file' was used"
fi

Related

bash function - determine if running as a script or tty

Say I have a bash function:
run_stuff(){
if is_in_script; then
exit 1
fi
return 1;
}
basically, if I am running it in a script:
$ ./my-script.sh
then I want to exit with 1, but if I am running it directly in the terminal:
$ run_stuff
then I want to return 1 (o/w my terminal window will close)
what is the best way to check this cross-platform?
You can use $0.
echo $0 when run in my (bash) terminal returns -bash.
echo $0 in a script with no #! run with bash test.sh returns test.sh
echo $0 in a script with #!/bin/bash run as ./test.sh returns ./test.sh
It's not bullet proof, but you can check whether the current shell is interactive:
is_in_script() {
[[ $- == *i* ]]
}
However, I would just make run_stuff always return, and if a script should exit when the command reports failure, it can do run_stuff || exit rather than leaving this behavior up to the function itself.

Exit causes root logout if script user executes the script outside of the script directory

Bash Script Bug User Gets Logged Out Of Root On Exit
I have a big Bash script which requires to exit the script and restart it if some user input is invalid. I got a weird bug, where if the user does execute the script outside of the directory in which the script is located, he gets logged out of root. But if the script is executed inside the directory where the script is located, this doesn't occur.
I already tried removing the exit but that only makes things worse.
#!/bin/bash
some_function() {
read -p "Enter something: "
# Some commands
if [[ $? -gt 0 ]]; then
echo "error"
. /whatever/location/script.sh && exit 1
fi
}
The expected result is, that the script just restarts and exits the process the user ran. The actual result is just like that, but the user gets logged out of root if the script is terminated after that.
You did not say so, but it seems you are sourcing the script containing this function that exits. If you are sourcing it, then it is as if each command is typed at the command line... so exit will logout of whatever shell you are running.
For a script that is always sourced, use return instead of exit
If you don't know whether the script will be sourced or not, you'll need to detect it and choose the proper behavior based on how it was called. For example:
some_function() {
read -p "Enter something: "
# Some commands
if [[ $? -gt 0 ]]; then
echo "error"
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
# sourced
. /whatever/location/script.sh && return 1
else
# not sourced
. /whatever/location/script.sh && exit 1
fi
fi
}

bash succeeding even though a command fails [duplicate]

This question already has answers here:
Aborting a shell script if any command returns a non-zero value
(10 answers)
Closed 1 year ago.
I am a noob in shell-scripting. I want to print a message and exit my script if a command fails. I've tried:
my_command && (echo 'my_command failed; exit)
but it does not work. It keeps executing the instructions following this line in the script. I'm using Ubuntu and bash.
Try:
my_command || { echo 'my_command failed' ; exit 1; }
Four changes:
Change && to ||
Use { } in place of ( )
Introduce ; after exit and
spaces after { and before }
Since you want to print the message and exit only when the command fails ( exits with non-zero value) you need a || not an &&.
cmd1 && cmd2
will run cmd2 when cmd1 succeeds(exit value 0). Where as
cmd1 || cmd2
will run cmd2 when cmd1 fails(exit value non-zero).
Using ( ) makes the command inside them run in a sub-shell and calling a exit from there causes you to exit the sub-shell and not your original shell, hence execution continues in your original shell.
To overcome this use { }
The last two changes are required by bash.
The other answers have covered the direct question well, but you may also be interested in using set -e. With that, any command that fails (outside of specific contexts like if tests) will cause the script to abort. For certain scripts, it's very useful.
If you want that behavior for all commands in your script, just add
set -e
set -o pipefail
at the beginning of the script. This pair of options tell the bash interpreter to exit whenever a command returns with a non-zero exit code. (For more details about why pipefail is needed, see http://petereisentraut.blogspot.com/2010/11/pipefail.html)
This does not allow you to print an exit message, though.
Note also, each command's exit status is stored in the shell variable $?, which you can check immediately after running the command. A non-zero status indicates failure:
my_command
if [ $? -eq 0 ]
then
echo "it worked"
else
echo "it failed"
fi
I've hacked up the following idiom:
echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi
echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi
echo "Done."
Precede each command with an informative echo, and follow each command with that same
if [ $? -ne 0 ];... line. (Of course, you can edit that error message if you want to.)
Provided my_command is canonically designed, ie returns 0 when succeeds, then && is exactly the opposite of what you want. You want ||.
Also note that ( does not seem right to me in bash, but I cannot try from where I am. Tell me.
my_command || {
echo 'my_command failed' ;
exit 1;
}
You can also use, if you want to preserve exit error status, and have a readable file with one command per line:
my_command1 || exit
my_command2 || exit
This, however will not print any additional error message. But in some cases, the error will be printed by the failed command anyway.
The trap shell builtin allows catching signals, and other useful conditions, including failed command execution (i.e., a non-zero return status). So if you don't want to explicitly test return status of every single command you can say trap "your shell code" ERR and the shell code will be executed any time a command returns a non-zero status. For example:
trap "echo script failed; exit 1" ERR
Note that as with other cases of catching failed commands, pipelines need special treatment; the above won't catch false | true.
Using exit directly may be tricky as the script may be sourced from other places (e.g. from terminal). I prefer instead using subshell with set -e (plus errors should go into cerr, not cout) :
set -e
ERRCODE=0
my_command || ERRCODE=$?
test $ERRCODE == 0 ||
(>&2 echo "My command failed ($ERRCODE)"; exit $ERRCODE)

Propagating exit code to caller in case of a shell error from script having an exit trap

Is it possible to propagate an exit code to the caller in case of a syntax error in a Bash script with an EXIT trap? For example, if I have:
#! /bin/bash
set -eu
trap "echo dying!!" EXIT
echo yeah
echo $UNBOUND_VARIABLE
echo boo
Then, running it gives an exit code 0 even if the script did not really end successfully:
$ bash test.sh
yeah
test.sh: line 8: UNBOUND_VARIABLE: unbound variable
dying!!
$ echo $?
0
But if I comment out the exit trap, the script returns 1. Alternatively, if I replace the line with the unbound variable with a command that returns nonzero (e.g. /bin/false), that exit value is propagated as I would like it to.
The shell exits with the result of the last executed command. In your trap case, that's echo, which usually returns with success.
To propagate your value, simply exit with it.
#!/bin/bash
set -eu
die() {
echo "Dying!!"
exit "$1"
}
trap 'die $?' EXIT
echo yeah
echo $unbound
echo boo
Also note that set -e is considered harmful -- it makes you think the script will exit if a command fails, which it won't always do.
This behavior is related to different Bash versions. The original script works as expected on Bash 4.2 but not on 3.2. Having the error-prone code in a separate script file and running it in a subshell works around problems in earlier Bash versions:
#!/bin/bash
$BASH sub.sh
RETVAL=$?
if [[ "$RETVAL" != "0" ]]; then
echo "Dying!! Exit code: $RETVAL"
fi
sub.sh:
set -eu
echo yeah
echo $UNBOUND_VARIABLE
echo boo

Calling script from script

How can I have my shell script echo to me that the script that it calls has failed?
#!/bin/sh
test="/Applications/test.sh"
sh $test
exit 0
exit 1
#!/bin/sh
if sh /Applications/test.sh; then
echo "Well done $USER"
exit 0
else
echo "script failed with code [$?]" >&2
exit 1
fi
The /Applications/test.sh script should be well coded to exit with conventional status. 0 if it's ok and > 0 if it fails.
Like you can see, no need to test the special variable $?, we use boolean expression directly.
I usually take the following approach:
#!/usr/bin/env bash
test="/Applications/test.sh"
sh "${test}"
exit_status=$?
if [[ ${exit_status} ]] ; then
echo "Error: ${test} failed with status ${exit_status}." >&2
else
echo "Success!"
fi
In terms of best practice, you should not. If a script fails, it should emit an error message before it terminates so that its parent doesn't have to. The main reason for this is that the process knows why it is failing, while the parent can only guess. In other words, you should just write:
#!/bin/sh
test="/Applications/test.sh"
sh $test
Although really, it would be more typical to just write:
#!/bin/sh
/Applications/test.sh
test.sh will emit the necessary error message, and your script will return the same value as did test.sh. Also, in its current form your script will always be successful, even if test.sh actually failed because exit 0; exit 1 is pretty pointless: the exit 1 will never be called.

Resources