I recently stumbled across some shell script code which is repeatedly doing the same thing over again and I was wondering if this could be a bit simplified.
Basically in a defined function, after each and every call the return code is checked. If it is not 0, some outputs are done and the function exits prematurely. In this cases we willfully use return to escape the function but do not want to stop the whole process by using exit.
e.g.:
function _myFunc {
typeset ret=0
_myFirstSubFunc "TEST"
ret=$?
if [[ $? -ne 0 ]]; then
echo "myFirstSubFunc produced an error: $ret"
return $ret
fi
_mySecondSubFunc "TEST"
ret=$?
if [[ $? -ne 0 ]]; then
echo "mySecondSubFunc produced an error: $ret"
return $ret
fi
return $ret
}
I'd like to reduce this repeated code of
checking the return code
printing out some information if the returncode is != 0
stopping the current function from continuing
to one call, so the code isn't that much cluttered with this checking. The execution of the function should continue if the previous call was successful.
Usually, when I'd like to abort (exit) the whole process, one function will do this trick. But when a value should be returned in the case of an error things get (at least for me) a bit tricky.
I tried to use traps or aliases, unfortunately without success. Additionally I'm limited to ksh88, so it seems I cannot use some conditionals following the call by using ||.
Any ideas, if there is something else so I can reduce this tedious error handling?
Thanks!
function orReport {
if "$#"; then
return
else
ret=$?
echo "$1 produced an error: $ret"
return "$ret"
fi
}
function _myFunc {
orReport _myFirstSubFunc "TEST" || return
orReport _mySecondSubFunc "TEST" || return
}
Related
I have several functions in my .bashrc and I want to terminate the whole "run" on error. For example:
function assert_env()
{
var_name=$1
if [ -z "${!var_name}" ]; then
printf "Missing environment variable: $var_name\n"
exit 1
fi
}
function my_test()
{
assert_env "abc"
print $abc
}
If i type my_test in terminal I want to terminate the execution, but this script closes the terminal (as expected - I run exit 1).
How may I terminate the execution without closing the current terminal?
EDIT: To be more specific - I don't want to return from assert_env, I want my following commands from my_test to not be executed if the condition from assert_env is not met, something like C++ assert
Note that exit returns the exit code specified to the shell itself, so using exit 1 would obviously exit your current shell. You need to use return here.
As far as you requirement is considered, just use the exit code from the function assert_env to decide if you want to run further statements. Here using return will cause the current function to go out of scope and what ever code you are returning back to the callee can be used to check if its successful/failure
function assert_env() {
local ret_val=0; var_name=$1
if [ -z "${!var_name}" ]; then
printf "Missing environment variable: $var_name\n"
ret_val=1
fi
return ${ret_val}
}
Now using it
function my_test() {
if ! assert_env "abc"; then
return
fi
# Or could be just written as assert_env "abc" || return
}
This way, if the assert_env returns 0, the if-clause in my_test asserts failure and rest of the code gets executed. And on returning 1 the condition becomes true and the function call is returned without exiting the main scipt.
Note that function keyword is non-standard and not POSIX compliant and may not work across shells. Just drop the keyword if you want to make it portable.
I've found a workaround - execute the function content in a subshell
Example:
function assert_env()
{
var_name=$1
if [ -z "${!var_name}" ]; then
printf "Missing environment variable: $var_name\n"
exit 1
fi
}
function my_test()
{
(
printf "$1\n"
assert_env "abc"
printf $abc
)
}
To check the exit code of command in Bash the following logic is used:
if [ $? -ne 0 ]
then
echo "Error occurred"
return 1
fi
My problem is that adding this after every command makes the script very long with multiple copies of the same thing, and very hard to maintain.
The best thing here would be a function, that would be called from all the locations the the exit code needs to be checked. The problem is that the exit command can not be used, because it will kill current process (current Bash session will be killed), so only the return command can be used. But when using the return command, in a called function, the calling function must still check the exit code, and were back to the same problem.
Is there any thing like MACRO in Bash or any other way to the error checking more efficient?
Instead of this:
somecommand
if [ $? -ne 0 ]
then
echo "Error occurred"
return 1
fi
You don't need to write conditions on the $? variable,
you can use the command itself in if statements:
if ! somecommand
then
echo "Error occurred"
return 1
fi
Another alternative is to create a helper function to encapsulate the action on error, and use the || operator after the command to call the function and then return 1 after:
error() {
echo "Error occurred"
}
somecommand || { error; return 1; }
Finally, if you don't really need return 1 and you don't mind to exit the script in case of failure, then you can exit 1 inside the helper function and then the caller code can become more compact:
fatal() {
echo "Error occurred"
exit 1
}
somecommand || fatal
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
# ...
Now in the line EXIT HERE the script should exit and return exit code 1. The problem is that
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?
The answer to the question title (not in the body as other answers have addressed) is:
Return an exit code without closing shell
(exit 33)
If you need to have -e active and still avoid exiting the shell with a non-zero exit code, then do:
(exit 33) && true
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.
Use this instead of exit or return:
[ $PS1 ] && return || exit;
Works whether sourced or not.
You can use x"${BASH_SOURCE[0]}" == x"$0" to test if the script was sourced or called (false if sourced, true if called) and return or exit accordingly.
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:
check_process ()
{
if [ $1 -eq "50" ]
then
return 1
else
return 0
fi
}
and
source processStatus.sh $1
RET_VALUE=$?
if [ $RET_VALUE -ne "0" ]
then
exit 0
fi
You can use return if you use set -e in the beginning of the script.
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. {0..255}).
# 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
# ...
Thanks for the question, my case was to source a file for some setup, but end the script and skip the setup actions if certain conditions were not met.
I had hit the issue of an attempt to use exit() actually causing the closing of my terminal, and found myself here :D
After reviewing the options for the specific solution i just went with something like the below, I also think Deepaks answer is worth reviewing if this approach works in your case.
if [ -z "$REQUIRED_VAR" ]; then
echo "please check/set \$REQUIRED_VAR ..."
echo "skipping logic"
else
echo "starting logic"
doStuff()
echo "completed logic"
fi
I want to write a bash function that check if a file has certain properties and returns true or false. Then I can use it in my scripts in the "if". But what should I return?
function myfun(){ ... return 0; else return 1; fi;}
then I use it like this:
if myfun filename.txt; then ...
of course this doesn't work. How can this be accomplished?
Use 0 for true and 1 for false.
Sample:
#!/bin/bash
isdirectory() {
if [ -d "$1" ]
then
# 0 = true
return 0
else
# 1 = false
return 1
fi
}
if isdirectory $1; then echo "is directory"; else echo "nopes"; fi
Edit
From #amichair's comment, these are also possible
isdirectory() {
if [ -d "$1" ]
then
true
else
false
fi
}
isdirectory() {
[ -d "$1" ]
}
Why you should care what I say in spite of there being a 350+ upvote answer
It's not that 0 = true and 1 = false. It is: zero means no failure (success) and non-zero means failure (of type N).
While the selected answer is technically "true" please do not put return 1** in your code for false. It will have several unfortunate side effects.
Experienced developers will spot you as an amateur (for the reason below).
Experienced developers don't do this (for all the reasons below).
It is error prone.
Even experienced developers can mistake 0 and 1 as false and true respectively (for the reason above).
It requires (or will encourage) extraneous and ridiculous comments.
It's actually less helpful than implicit return statuses.
Learn some bash
The bash manual says (emphasis mine)
return [n]
Cause a shell function to stop executing 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 in the function.
Therefore, we don't have to EVER use 0 and 1 to indicate True and False. The fact that they do so is essentially trivial knowledge useful only for debugging code, interview questions, and blowing the minds of newbies.
The bash manual also says
otherwise the function’s return status is the exit status of the last command executed
The bash manual also says
($?) Expands to the exit status of the most recently executed foreground pipeline.
Whoa, wait. Pipeline? Let's turn to the bash manual one more time.
A pipeline is a sequence of one or more commands separated by one of the control operators ‘|’ or ‘|&’.
Yes. They said 1 command is a pipeline. Therefore, all 3 of those quotes are saying the same thing.
$? tells you what happened last.
It bubbles up.
My answer
While #Kambus demonstrated that with such a simple function, no return is needed at all. I think it
was unrealistically simple compared to the needs of most people who will read this.
Why return?
If a function is going to return its last command's exit status, why use return at all? Because it causes a function to stop executing.
Stop execution under multiple conditions
01 function i_should(){
02 uname="$(uname -a)"
03
04 [[ "$uname" =~ Darwin ]] && return
05
06 if [[ "$uname" =~ Ubuntu ]]; then
07 release="$(lsb_release -a)"
08 [[ "$release" =~ LTS ]]
09 return
10 fi
11
12 false
13 }
14
15 function do_it(){
16 echo "Hello, old friend."
17 }
18
19 if i_should; then
20 do_it
21 fi
What we have here is...
Line 04 is an explicit[-ish] return true because the RHS of && only gets executed if the LHS was true
Line 09 returns either true or false matching the status of line 08
Line 13 returns false because of line 12
(Yes, this can be golfed down, but the entire example is contrived.)
Another common pattern
# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
echo "some_command failed"
fi
# Do this...
some_command
status=$?
if ! (exit $status); then
echo "some_command failed"
fi
Notice how setting a status variable demystifies the meaning of $?. (Of course you know what $? means, but someone less knowledgeable than you will have to Google it some day. Unless your code is doing high frequency trading, show some love, set the variable.) But the real take-away is that "if not exit status" or conversely "if exit status" can be read out loud and explain their meaning. However, that last one may be a bit too ambitious because seeing the word exit might make you think it is exiting the script, when in reality it is exiting the (...) command group subshell.
** If you absolutely insist on using return 1 for false, I suggest you at least use return 255 instead. This will cause your future self, or any other developer who must maintain your code to question "why is that 255?" Then they will at least be paying attention and have a better chance of avoiding a mistake.
myfun(){
[ -d "$1" ]
}
if myfun "path"; then
echo yes
fi
# or
myfun "path" && echo yes
Be careful when checking directory only with option -d !
if variable $1 is empty the check will still be successfull. To be sure, check also that the variable is not empty.
#! /bin/bash
is_directory(){
if [[ -d $1 ]] && [[ -n $1 ]] ; then
return 0
else
return 1
fi
}
#Test
if is_directory $1 ; then
echo "Directory exist"
else
echo "Directory does not exist!"
fi
Use the true or false commands immediately before your return, then return with no parameters. The return will automatically use the value of your last command.
Providing arguments to return is inconsistent, type specific and prone to error if you are not using 1 or 0. And as previous comments have stated, using 1 or 0 here is not the right way to approach this function.
#!/bin/bash
function test_for_cat {
if [ "$1" = "cat" ];
then
true
return
else
false
return
fi
}
for i in cat hat;
do
echo "${i}:"
if test_for_cat "${i}";
then
echo "- True"
else
echo "- False"
fi
done
Output:
$ bash bash_return.sh
cat:
- True
hat:
- False
For code readability reasons I believe returning true/false should:
be on one line
be one command
be easy to remember
mention the keyword return followed by another keyword (true or false)
My solution is return $(true) or return $(false) as shown:
is_directory()
{
if [ -d "${1}" ]; then
return $(true)
else
return $(false)
fi
}
I encountered a point (not explictly yet mentioned?) which I was stumbling over. That is, not how to return the boolean, but rather how to correctly evaluate it!
I was trying to say if [ myfunc ]; then ..., but that's simply wrong. You must not use the brackets! if myfunc; then ... is the way to do it.
As at #Bruno and others reiterated, true and false are commands, not values! That's very important to understanding booleans in shell scripts.
In this post, I explained and demoed using boolean variables: https://stackoverflow.com/a/55174008/3220983 . I strongly suggest checking that out, because it's so closely related.
Here, I'll provide some examples of returning and evaluating booleans from functions:
This:
test(){ false; }
if test; then echo "it is"; fi
Produces no echo output. (i.e. false returns false)
test(){ true; }
if test; then echo "it is"; fi
Produces:
it is
(i.e. true returns true)
And
test(){ x=1; }
if test; then echo "it is"; fi
Produces:
it is
Because 0 (i.e. true) was returned implicitly.
Now, this is what was screwing me up...
test(){ true; }
if [ test ]; then echo "it is"; fi
Produces:
it is
AND
test(){ false; }
if [ test ]; then echo "it is"; fi
ALSO produces:
it is
Using the brackets here produced a false positive! (I infer the "outer" command result is 0.)
The major take away from my post is: don't use brackets to evaluate a boolean function (or variable) like you would for a typical equality check e.g. if [ x -eq 1 ]; then... !
Following up on #Bruno Bronosky and #mrteatime, I offer the suggestion that you just write your boolean return "backwards". This is what I mean:
foo()
{
if [ "$1" == "bar" ]; then
true; return
else
false; return
fi;
}
That eliminates the ugly two line requirement for every return statement.
I found the shortest form to test the function output is simply
do_something() {
[[ -e $1 ]] # e.g. test file exists
}
do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }
It might work if you rewrite this
function myfun(){ ... return 0; else return 1; fi;} as this function myfun(){ ... return; else false; fi;}. That is if false is the last instruction in the function you get false result for whole function but return interrupts function with true result anyway. I believe it's true for my bash interpreter at least.
Here are some good examples with test cases to experiment with.
For basic functions, call true/false in the last line of the method, or use true; return to exit early.
function is_true() { true; }
if is_true; then echo 'true is true'; fi
if ! is_true; then exit; else echo '! true is ! true'; fi
function is_false() { false; }
if ! is_false; then echo 'false is false'; fi
if is_false; then exit; else echo '! false is ! false'; fi
If you can not return immediately, store the return value in variable. Use (true; echo $?) while setting the variable. This also works for nested functions (see next section).
function from_var() {
local input=$1
local my_var
if ((input == 1)); then
my_var=$(true; echo $?)
else
my_var=$(false; echo $?)
fi
echo 'ignore this line'
(exit $my_var)
}
if from_var 1; then echo "return true is true"; else exit; fi
if from_var 0; then exit; else echo "return false is false"; fi
If you need to store the result of the function call that returns a bool, use the same technique, but pipe the output of the call to /dev/null or the result may also contain strings from echo or other commands. Notice the (exit $rval) in the if statement lets you correctly interpret the return value. (Other methods like if (($rval)) or if [ $rval ] will not work as expected.
# Return a truthy result
rval=$(from_var 1 >/dev/null; echo $?)
if (exit $rval); then echo "return true as variable is true"; else exit; fi
# Return a falsy result
rval=$(from_var 0 >/dev/null; echo $?)
if (exit $rval); then exit; else echo "return false as variable is false"; fi
The full output of this code is:
true is true
! true is ! true
false is false
! false is ! false
ignore this line
return true is true
ignore this line
return false is false
return true as variable is true
return false as variable is false
If you don't want to suppress the output from within the function using > /dev/null, then rewrite to call the function first.
from_var 0; rval="$?"
I am trying to do validation for every function I call in a script and erroring out the whole script if one function fails.
Looking for the best way to do this. I feel I knew a good way to do it at one time, but can't figure it out again.
I can brute force it, but it's nasty. This is how I can get it to work correctly
copy_files $1
if [ "$?" -ne "0" ]; then
error "Copying files"
return -1
fi
This gets pretty ugly since I have a script that goes:
command1
command2
command3
Having to wrap all these commands with returns is not very ideal. :( Please help!
command1 || (error "Cmd1 fail"; return -1); command2 || (error "Cmd2 fail"; return -1);
etc. etc. The || operator means the next command will only execute if the previous command failed. && is the opposite. The brackets group the commands to run.
Since you said you want to do this for every command in a script, you could insert
set -e
at the beginning. That call makes the shell exit immediately if any command returns a status code other than 0. (Exceptions: commands that are part of the test in a conditional statement, and those that are followed by && or || and further commands)
You could do something like this:
good() {
return 0;
}
bad() {
return 5;
}
wrapper() {
$1
rc=$?
if [ "$rc" -ne "0" ]; then
echo "error: $1 returned $rc"
exit -1
fi
}
wrapper bad
wrapper good
or, if you could pass a list of function, like so:
wrapper() {
for f in $*; do
$f
rc=$?
if [ "$rc" -ne "0" ]; then
echo "error: $f returned ${rc}"
return -1
fi
done
return 0;
}
wrapper good bad
if [ "$?" -ne "0" ]; then
#error handling here
exit -1;
fi