Return an exit code without closing shell
I’d like to return an exit code from a BASH script that is called within another script, but could also be called directly. It roughly looks like this:
#!/bin/bash dq2-get $1 if [ $? -ne 0 ]; then echo "ERROR: . " # EXIT HERE fi # extract, do some stuff # .
- I cannot use return , because when I forget to source the script instead of calling it, return will not exit, and the rest of the script will be executed and mess things up.
- I cannot use exit , because this closes the shell.
- I cannot use the nice trick kill -SIGINT $$ , because this doesn’t allow to return an exit code.
Is there any viable alternative that I have overlooked?
6 Answers 6
The answer to the question title (not in the body as other answers have addressed) is:
Return an exit code without closing shell
If you need to have -e active and still avoid exiting the shell with a non-zero exit code, then do:
The true command is never executed but is used to build a compound command that is not exited by the -e shell flag.
That sets the exit code without exiting the shell (nor a sourced script).
For the more complex question of exiting (with an specific exit code) either if executed or sourced:
#!/bin/bash [ "$BASH_SOURCE" == "$0" ] && echo "This file is meant to be sourced, not executed" && exit 30 return 88
Will set an exit code of 30 (with an error message) if executed.
And an exit code of 88 if sourced. Will exit both the execution or the sourcing without affecting the calling shell.
This is tremendously useful. one thing I have had trouble with this though is I like to use ‘set -e’ to halt a script in its tracks. unfortunately return 88 will cause this to kill the shell as before. Are there any pointers on being able to handle that case?
@openCivilisation The usual solution to keep the shell working even if the -e option is set is to make the command line a compound command, the simplest compound command is (exit 33) && true . The true command is never actually executed.
Use this instead of exit or return:
Works whether sourced or not.
It did not work for me as written (running as script did not work), but the following works: [ -v PS1 ] && return || exit . A way to test it easily: [ -v PS1 ] && (echo returning; return) || (echo exiting; exit)
Also, if I understand it correctly, this solution does NOT work when source-ing from inside another script. The accepted answer works in that (complex) scenario though.
You can use x»$» == x»$0″ to test if the script was sourced or called (false if sourced, true if called) and return or exit accordingly.
I tried that, but I got bored, because I couldn’t put that into a function (because then again I’d miss a way to exit) — I’d have to place an if construction at every place in the script where an error could occur. Or is there a better way?
You could have the if statement be part of a signal trap and then replace every EXIT HERE with a kill -SIGWHATEVER $$
The x s are 100% unnecessary; they add nothing whatsoever over the behavior of [ «$» = «$0» ] . The only time there’s ever a value to the x$foo idiom in shells that comply with the POSIX.2 standard (ever since its initial early-90s publication) is when you’re using test calls with more than three arguments (which the aformentioned standard explicitly flags as obsolescent). And if you need compatibility with ancient shells, == is flatly wrong on its face; the only backwards-compatible (or even baseline POSIX-compliant) string comparison operator is = .
Another option is to use a function and put the return values in that and then simply either source the script (source processStatus.sh) or call the script (./processStatus.sh) . For example consider the processStatus.sh script that needs to return a value to the stopProcess.sh script but also needs to be called separately from say the command line without using source (only relevant parts included) Eg:
source processStatus.sh $1 RET_VALUE=$? if [ $RET_VALUE -ne "0" ] then exit 0 fi
50 doesn’t need quotes around it, because it parses to the same two-character string either way. $1 does need quotes, because it can parse to zero strings, or one string, or more than one string depending on its contents. Consider check_process «1 -eq 1 -o 39» ; when you don’t have the quotes that runs if [ 1 -eq 1 -o 39 -eq 50 ] , which is true, whereas [ «1 -eq 1 -o 39» -eq 50 ] would correctly be an error.
If you just want to check if the function returned no errors, I’d rather suggest rewriting your code like this:
#!/bin/bash set -e # exit program if encountered errors dq2-get () < # define the function here # . if [ $1 -eq 0 ] then return 0 else return 255 # Note that nothing will execute from this point on, # because `return` terminates the function. ># . # lots of code . # . # Now, the test: # This won't exit the program. if $(dq2-get $1); then echo "No errors, everything's fine" else echo "ERROR: . " fi # These commands execute anyway, no matter what # `dq2-get $1` returns (i.e. ). # extract, do some stuff # .
Now, the code above won’t leave the program if the function dq2-get $1 returns errors. But, implementing the function all by itself will exit the program because of the set -e . The code below describes this situation:
# The function below will stop the program and exit # if it returns anything other than `0` # since `set -e` means stop if encountered any errors. $(dq2-get $1) # These commands execute ONLY if `dq2-get $1` returns `0` # extract, do some stuff # .
How to exit a function in bash
Note that if you have set -e set at the top of your script and your return 1 or any other number besides 0, your entire script will exit.
@YevgeniyBrikman that’s only true if the error in the function is unexpected. If the function is called using e.g. || then it’s possible to return a nonzero code and still have the script continue to execute.
@DanPassaro Yup, there are definitely solutions possible, but I just wanted to call out that extra care needs to be taken with set -e and returning non-zero values, as that caught me by surprise in the past.
If you want to return from an outer function with an error without exit ing you can use this trick:
do-something-complex() < # Using `return` here would only return from `fail`, not from `do-something-complex`. # Using `exit` would close the entire shell. # So we (ab)use a different feature. :) fail() < : "$"; > nested-func() < try-this || fail "This didn't work" try-that || fail "That didn't work" >nested-func >
$ do-something-complex try-this: command not found bash: __fail_fast: This didn't work
This has the added benefit/drawback that you can optionally turn off this feature: __fail_fast=x do-something-complex .
Note that this causes the outermost function to return 1.
The : is a built-in bash operator that is a «no-op». It evaluates the expression but doesn’t do anything with it. I’m using it to do variable substitution that will fail if the variable isn’t defined, which it obviously isn’t.
Thanks. Could I replace the expression to some other expression to check the input parameter of do-something-complex ? checkPara () < if [ $1 -lt $2 ]; then echo $3; fi; >do-something-complex() I would do-something-complex show user some message and return immediately if there is no parameter fed to the function.
This answer is sorely lacking in explanation, even after the response to «Could you explain. «. It’s as if Elliot doesn’t want to reveal his «trick».
What’s left to explain? It’s a variable substitution where we expect the variable to not be defined.
My use case is to run the function unless it’s already running. I’m doing
mkdir /tmp/nice_exit || return 0
And then at the end of the function
You might want to use rmdir instead of rm -rf . Your function will also never run anymore if an unexpected error or oversight causes it to stop without deleting that directory (function returns, program or system exits/crashes/is powered off. ) For these reasons you might want to use a lock file with flock(1) instead.
@FrenchMasterSword Yeah, it’s a function that’s running when a container is exiting. So I don’t think all that applies. Happy to solve any issues that come up with not running, just always want to make sure it doesn’t run again at the same time.