Bash functions ignore errors when called in a boolean expression? - bash

The following bash script prints "This should never run!":
#!/bin/bash
set -e
func() {
echo "Bailing out..."
false # Should bail out because of set -e
echo "This should never run!"
}
func || true
What's going on here?
A simple conclusion to make is that set -e is not propagated inside functions. However, this is not the case. Changing the last line (func || true) to just func causes the function to stop at the false statement as expected.
Another hypothesis is that bash will start executing the function, and on the first error (false) evaluate the rest of the line it was called on (func || true), and if that retuns 0 (true), then it continues executing the function as if nothing happened. However, replacing true with echo "Oops!" disproves that, since no "Oops!" is printed.
Is set -e somehow ignored when executing functions as part of a boolean expression?

From man bash regarding set -e:
Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL GRAMMAR above), exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test following the if or elif reserved words, part of any command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return value is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits. This option applies to the shell environment and each subshell environment separately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.

Related

Bash - Exit function with error message instead of return value to variable [duplicate]

What is the difference between the return and exit statement in Bash functions with respect to exit codes?
From man bash on return [n];
Causes a function to stop executing and return the value specified by n to its caller. If n is omitted, the return status is that of the last command executed in the function body.
... on exit [n]:
Cause the shell to exit with a status of n. If n is omitted, the exit status is that of the last command executed. A trap on EXIT is executed before the shell terminates.
EDIT:
As per your edit of the question, regarding exit codes, return has nothing to do with exit codes. Exit codes are intended for applications/scripts, not functions. So in this regard, the only keyword that sets the exit code of the script (the one that can be caught by the calling program using the $? shell variable) is exit.
EDIT 2:
My last statement referring exit is causing some comments. It was made to differentiate return and exit for the understanding of the OP, and in fact, at any given point of a program/shell script, exit is the only way of ending the script with an exit code to the calling process.
Every command executed in the shell produces a local "exit code": it sets the $? variable to that code, and can be used with if, && and other operators to conditionally execute other commands.
These exit codes (and the value of the $? variable) are reset by each command execution.
Incidentally, the exit code of the last command executed by the script is used as the exit code of the script itself as seen by the calling process.
Finally, functions, when called, act as shell commands with respect to exit codes. The exit code of the function (within the function) is set by using return. So when in a function return 0 is run, the function execution terminates, giving an exit code of 0.
return will cause the current function to go out of scope, while exit will cause the script to end at the point where it is called. Here is a sample program to help explain this:
#!/bin/bash
retfunc()
{
echo "this is retfunc()"
return 1
}
exitfunc()
{
echo "this is exitfunc()"
exit 1
}
retfunc
echo "We are still here"
exitfunc
echo "We will never see this"
Output
$ ./test.sh
this is retfunc()
We are still here
this is exitfunc()
I don't think anyone has really fully answered the question because they don't describe how the two are used. OK, I think we know that exit kills the script, wherever it is called and you can assign a status to it as well such as exit or exit 0 or exit 7 and so forth. This can be used to determine how the script was forced to stop if called by another script, etc. Enough on exit.
return, when called, will return the value specified to indicate the function's behavior, usually a 1 or a 0. For example:
#!/bin/bash
isdirectory() {
if [ -d "$1" ]
then
return 0
else
return 1
fi
echo "you will not see anything after the return like this text"
}
Check like this:
if isdirectory $1; then echo "is directory"; else echo "not a directory"; fi
Or like this:
isdirectory || echo "not a directory"
In this example, the test can be used to indicate if the directory was found. Notice that anything after the return will not be executed in the function. 0 is true, but false is 1 in the shell, different from other programming languages.
For more information on functions: Returning Values from Bash Functions
Note: The isdirectory function is for instructional purposes only. This should not be how you perform such an option in a real script.*
Remember, functions are internal to a script and normally return from whence they were called by using the return statement. Calling an external script is another matter entirely, and scripts usually terminate with an exit statement.
The difference "between the return and exit statement in Bash functions with respect to exit codes" is very small. Both return a status, not values per se. A status of zero indicates success, while any other status (1 to 255) indicates a failure. The return statement will return to the script from where it was called, while the exit statement will end the entire script from wherever it is encountered.
return 0 # Returns to where the function was called. $? contains 0 (success).
return 1 # Returns to where the function was called. $? contains 1 (failure).
exit 0 # Exits the script completely. $? contains 0 (success).
exit 1 # Exits the script completely. $? contains 1 (failure).
If your function simply ends without a return statement, the status of the last command executed is returned as the status code (and will be placed in $?).
Remember, return and exit give back a status code from 0 to 255, available in $?. You cannot stuff anything else into a status code (e.g., return "cat"); it will not work. But, a script can pass back 255 different reasons for failure by using status codes.
You can set variables contained in the calling script, or echo results in the function and use command substitution in the calling script; but the purpose of return and exit are to pass status codes, not values or computation results as one might expect in a programming language like C.
Sometimes, you run a script using . or source.
. a.sh
If you include an exit in the a.sh, it will not just terminate the script, but end your shell session.
If you include a return in the a.sh, it simply stops processing the script.
exit terminates the current process; with or without an exit code, consider this a system more than a program function. Note that when sourcing, exit will end the shell. However, when running, it will just exit the script.
return from a function go back to the instruction after the call, with or without a return code. return is optional and it's implicit at the end of the function. return can only be used inside a function.
I want to add that while being sourced, it's not easy to exit the script from within a function without killing the shell. I think, an example is better on a 'test' script:
#!/bin/bash
function die(){
echo ${1:=Something terrible wrong happen}
#... clean your trash
exit 1
}
[ -f /whatever/ ] || die "whatever is not available"
# Now we can proceed
echo "continue"
doing the following:
user$ ./test
Whatever is not available
user$
test -and- the shell will close.
user$ . ./test
Whatever is not available
Only test will finish and the prompt will show.
The solution is to enclose the potentially procedure in ( and ):
#!/bin/bash
function die(){
echo $(1:=Something terrible wrong happen)
#... Clean your trash
exit 1
}
( # Added
[ -f /whatever/ ] || die "whatever is not available"
# Now we can proceed
echo "continue"
) # Added
Now, in both cases only test will exit.
The OP's question:
What is the difference between the return and exit statement in BASH functions with respect to exit codes?
Firstly, some clarification is required:
A (return|exit) statement is not required to terminate execution of a (function|shell). A (function|shell) will terminate when it reaches the end of its code list, even with no (return|exit) statement.
A (return|exit) statement is not required to pass a value back from a terminated (function|shell). Every process has a built-in variable $? which always has a numeric value. It is a special variable that cannot be set like "?=1", but it is set only in special ways (see below *).
The value of $? after the last command to be executed in the (called function | sub shell) is the value that is passed back to the (function caller | parent shell). That is true whether the last command executed is ("return [n]"| "exit [n]") or plain ("return" or something else which happens to be the last command in the called function's code.
In the above bullet list, choose from "(x|y)" either always the first item or always the second item to get statements about functions and return, or shells and exit, respectively.
What is clear is that they both share common usage of the special variable $? to pass values upwards after they terminate.
* Now for the special ways that $? can be set:
When a called function terminates and returns to its caller then $? in the caller will be equal to the final value of $? in the terminated function.
When a parent shell implicitly or explicitly waits on a single sub shell and is released by termination of that sub shell, then $? in the parent shell will be equal to the final value of $? in the terminated sub shell.
Some built-in functions can modify $? depending upon their result. But some don't.
Built-in functions "return" and "exit", when followed by a numerical argument both set $? with their argument, and terminate execution.
It is worth noting that $? can be assigned a value by calling exit in a sub shell, like this:
# (exit 259)
# echo $?
3
In simple words (mainly for newbie in coding), we can say,
`return`: exits the function,
`exit()`: exits the program (called as process while running)
Also if you observed, this is very basic, but...,
`return`: is the keyword
`exit()`: is the function
If you convert a Bash script into a function, you typically replace exit N with return N. The code that calls the function will treat the return value the same as it would an exit code from a subprocess.
Using exit inside the function will force the entire script to end.
Adding an actionable aspect to a few of the other answers:
Both can give exit codes - default or defined by the function, and the only 'default' is zero for success for both exit and return. Any status can have a custom number 0-255, including for success.
Return is used often for interactive scripts that run in the current shell, called with . script.sh for example, and just returns you to your calling shell. The return code is then accessible to the calling shell - $? gives you the defined return status.
Exit in this case also closes your shell (including SSH connections, if that's how you're working).
Exit is necessary if the script is executable and called from another script or shell and runs in a subshell. The exit codes then are accessible to the calling shell - return would give an error in this case.
First of all, return is a keyword and exit is a function.
That said, here's a simplest of explanations.
return
It returns a value from a function.
exit
It exits out of or abandons the current shell.

Make 'trap ERR' working inside bash functions with 'return' (or in any subshells)

I'm trying to use trap ERR in my scripts. But:
function hmmm() {
trap 'exit 10' ERR
echo 12>/SOME/NONEXISTING/FILE
# some commands that must not be done if previous has failed
echo "THAT MUST NOT BE PRINTED" >&2
return 5
}
echo ok1
a=$(hmmm) || status="$?"
echo "function returns: $status"
Prints
ok1
test2.sh: line 3: /SOME/NONEXISTING/FILE: No such file or directory
THAT MUST NOT BE PRINTED
function returns: 5
The same behavior with any combination of set -e, set -E, trap on top level etc. I always need to handle return code of function - so, as I undertand, I can't use trap ERR in my scripts at all - I willn't ever working. Am I right, or there is working method to enable trap ERR inside functions, subshells and sourced libraries and keep constructions like
a=$( ...somecode ... ) || result="$?"
working? Or, that more important, in example above make bash exit on error inside function ALWAYS, not depending on calling method.
Added:
In fact, I want to know is there any working way to BE SURE that errors are trapped inside functions, subshells and sourced code. Because my functions and libraries can be used by other peoples - so, I can't control how this functions are called, and, to be honest, even if I can - I will never use some behavior inside function, that can be accidentally and silently changed from outside.
trap isn't executed since it's a part of ||.
Change:
a=$(hmmm) || status="$?"
to:
a=$(hmmm)
status="$?"
From bash manual:
The ERR trap is not executed if the failed command is part of the
command list immediately following a while or until keyword, part of
the test in an if statement, part of a command executed in a && or ||
list except the command following the final && or ||, any command in a
pipeline but the last, or if the command's return value is being
inverted using !. These are the same conditions obeyed by the errexit
(-e) option.

Bash control flow using || on function, with set -e

If I put set -e in a Bash script, the script will exit on future errors. I'm confused about how this works with functions. Consider the following, which will only print one to standard out:
set -e # Exit on error
fun(){
echo one
non_existing_command
echo two
}
fun
Clearly, the non_existing_command is an error and so the script exits before the second echo. Usually one can use the or operator || to run another command if and only if the first command fails. That is, I would suspect the following to print out both one and three, but not two:
set -e # Exit on error
fun(){
echo one
non_existing_command
echo two
}
fun || echo three
What I get however is one and two. That is, the || operator prevents the exit (as it should) but it chooses to continue with the function body and disregard the right-hand command.
Any explanation?
It appears to be documented in the set builtin command
If a compound command or shell function executes in a context where -e is being ignored [such as on the left-hand of a ||], none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status.
Emphasis and comment are mine.
Also, if you try to set -e within the function, don't bother: the next sentence:
If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

How to use detect failure in command substitution

As you know set -e is very useful to discover any failure when command is executed. However I found it to be delicate to use and I don't know how to use it in the following scenarios:
==============First example================================
set -e
function main() {
local X1="$(exp::notdefined)"
echo "Will reach here : ${LINENO}"
X2="$(exp::notdefined)"
echo "Will not reach here : ${LINENO}"
}
main
==============Second example================================
set -e
function exp::tmp() {
echo "Now '$-' is "$-" : ${LINENO}"
false
return 0
}
function main() {
X1="$(exp::tmp)"
echo "Will reach here : ${LINENO}. '\$X1' : ${X1}"
X2="$(set -e ; exp::tmp)"
echo "Will not reach here : ${LINENO}"
}
main
===============================
The first example shows that, if we use command substitution on a local variable, then it will not fail even if the command substituted is not found. I don't know how to detect these kinds of failures.
The second example shows that, if the bash options (-e) will not propagate unless we call set -e inside the command braces. Is there any better way to do this?
You request immediate exit on pipeline failure with -e, e.g.:
-e Exit immediately if a pipeline (which may consistof a single simple
command), a list, or a compound command (see SHELL GRAMMAR above),
exits with a non-zero status.
The reason the bad command substitution does not cause failure within the function is because local provides its own return status.
local [option] [name[=value] ...]
... The return status is 0 unless local is used outside a function, an
invalid name is supplied, or name is a readonly variable.
The assignment of a failed command substitution does not cause local to return non-zero. Therefore, no immediate-exit is triggered.
As far as checking for a failure of command substitution following local, since the output is assigned to the variable, and the return will not be non-zero in the event of command substitution failure, you would have to validate by checking the variable contents itself for expected values for success/failure.
From the bash manual:
Subshells spawned to execute command substitutions inherit the value of
the -e option from the parent shell. When not in posix mode, bash
clears the -e option in such subshells.
Example 2 behaves differently when bash runs with --posix; however for example 1 I can't find any documentation explaining why local would cause this.

Difference between return and exit in Bash functions

What is the difference between the return and exit statement in Bash functions with respect to exit codes?
From man bash on return [n];
Causes a function to stop executing and return the value specified by n to its caller. If n is omitted, the return status is that of the last command executed in the function body.
... on exit [n]:
Cause the shell to exit with a status of n. If n is omitted, the exit status is that of the last command executed. A trap on EXIT is executed before the shell terminates.
EDIT:
As per your edit of the question, regarding exit codes, return has nothing to do with exit codes. Exit codes are intended for applications/scripts, not functions. So in this regard, the only keyword that sets the exit code of the script (the one that can be caught by the calling program using the $? shell variable) is exit.
EDIT 2:
My last statement referring exit is causing some comments. It was made to differentiate return and exit for the understanding of the OP, and in fact, at any given point of a program/shell script, exit is the only way of ending the script with an exit code to the calling process.
Every command executed in the shell produces a local "exit code": it sets the $? variable to that code, and can be used with if, && and other operators to conditionally execute other commands.
These exit codes (and the value of the $? variable) are reset by each command execution.
Incidentally, the exit code of the last command executed by the script is used as the exit code of the script itself as seen by the calling process.
Finally, functions, when called, act as shell commands with respect to exit codes. The exit code of the function (within the function) is set by using return. So when in a function return 0 is run, the function execution terminates, giving an exit code of 0.
return will cause the current function to go out of scope, while exit will cause the script to end at the point where it is called. Here is a sample program to help explain this:
#!/bin/bash
retfunc()
{
echo "this is retfunc()"
return 1
}
exitfunc()
{
echo "this is exitfunc()"
exit 1
}
retfunc
echo "We are still here"
exitfunc
echo "We will never see this"
Output
$ ./test.sh
this is retfunc()
We are still here
this is exitfunc()
I don't think anyone has really fully answered the question because they don't describe how the two are used. OK, I think we know that exit kills the script, wherever it is called and you can assign a status to it as well such as exit or exit 0 or exit 7 and so forth. This can be used to determine how the script was forced to stop if called by another script, etc. Enough on exit.
return, when called, will return the value specified to indicate the function's behavior, usually a 1 or a 0. For example:
#!/bin/bash
isdirectory() {
if [ -d "$1" ]
then
return 0
else
return 1
fi
echo "you will not see anything after the return like this text"
}
Check like this:
if isdirectory $1; then echo "is directory"; else echo "not a directory"; fi
Or like this:
isdirectory || echo "not a directory"
In this example, the test can be used to indicate if the directory was found. Notice that anything after the return will not be executed in the function. 0 is true, but false is 1 in the shell, different from other programming languages.
For more information on functions: Returning Values from Bash Functions
Note: The isdirectory function is for instructional purposes only. This should not be how you perform such an option in a real script.*
Remember, functions are internal to a script and normally return from whence they were called by using the return statement. Calling an external script is another matter entirely, and scripts usually terminate with an exit statement.
The difference "between the return and exit statement in Bash functions with respect to exit codes" is very small. Both return a status, not values per se. A status of zero indicates success, while any other status (1 to 255) indicates a failure. The return statement will return to the script from where it was called, while the exit statement will end the entire script from wherever it is encountered.
return 0 # Returns to where the function was called. $? contains 0 (success).
return 1 # Returns to where the function was called. $? contains 1 (failure).
exit 0 # Exits the script completely. $? contains 0 (success).
exit 1 # Exits the script completely. $? contains 1 (failure).
If your function simply ends without a return statement, the status of the last command executed is returned as the status code (and will be placed in $?).
Remember, return and exit give back a status code from 0 to 255, available in $?. You cannot stuff anything else into a status code (e.g., return "cat"); it will not work. But, a script can pass back 255 different reasons for failure by using status codes.
You can set variables contained in the calling script, or echo results in the function and use command substitution in the calling script; but the purpose of return and exit are to pass status codes, not values or computation results as one might expect in a programming language like C.
Sometimes, you run a script using . or source.
. a.sh
If you include an exit in the a.sh, it will not just terminate the script, but end your shell session.
If you include a return in the a.sh, it simply stops processing the script.
exit terminates the current process; with or without an exit code, consider this a system more than a program function. Note that when sourcing, exit will end the shell. However, when running, it will just exit the script.
return from a function go back to the instruction after the call, with or without a return code. return is optional and it's implicit at the end of the function. return can only be used inside a function.
I want to add that while being sourced, it's not easy to exit the script from within a function without killing the shell. I think, an example is better on a 'test' script:
#!/bin/bash
function die(){
echo ${1:=Something terrible wrong happen}
#... clean your trash
exit 1
}
[ -f /whatever/ ] || die "whatever is not available"
# Now we can proceed
echo "continue"
doing the following:
user$ ./test
Whatever is not available
user$
test -and- the shell will close.
user$ . ./test
Whatever is not available
Only test will finish and the prompt will show.
The solution is to enclose the potentially procedure in ( and ):
#!/bin/bash
function die(){
echo $(1:=Something terrible wrong happen)
#... Clean your trash
exit 1
}
( # Added
[ -f /whatever/ ] || die "whatever is not available"
# Now we can proceed
echo "continue"
) # Added
Now, in both cases only test will exit.
The OP's question:
What is the difference between the return and exit statement in BASH functions with respect to exit codes?
Firstly, some clarification is required:
A (return|exit) statement is not required to terminate execution of a (function|shell). A (function|shell) will terminate when it reaches the end of its code list, even with no (return|exit) statement.
A (return|exit) statement is not required to pass a value back from a terminated (function|shell). Every process has a built-in variable $? which always has a numeric value. It is a special variable that cannot be set like "?=1", but it is set only in special ways (see below *).
The value of $? after the last command to be executed in the (called function | sub shell) is the value that is passed back to the (function caller | parent shell). That is true whether the last command executed is ("return [n]"| "exit [n]") or plain ("return" or something else which happens to be the last command in the called function's code.
In the above bullet list, choose from "(x|y)" either always the first item or always the second item to get statements about functions and return, or shells and exit, respectively.
What is clear is that they both share common usage of the special variable $? to pass values upwards after they terminate.
* Now for the special ways that $? can be set:
When a called function terminates and returns to its caller then $? in the caller will be equal to the final value of $? in the terminated function.
When a parent shell implicitly or explicitly waits on a single sub shell and is released by termination of that sub shell, then $? in the parent shell will be equal to the final value of $? in the terminated sub shell.
Some built-in functions can modify $? depending upon their result. But some don't.
Built-in functions "return" and "exit", when followed by a numerical argument both set $? with their argument, and terminate execution.
It is worth noting that $? can be assigned a value by calling exit in a sub shell, like this:
# (exit 259)
# echo $?
3
In simple words (mainly for newbie in coding), we can say,
`return`: exits the function,
`exit()`: exits the program (called as process while running)
Also if you observed, this is very basic, but...,
`return`: is the keyword
`exit()`: is the function
If you convert a Bash script into a function, you typically replace exit N with return N. The code that calls the function will treat the return value the same as it would an exit code from a subprocess.
Using exit inside the function will force the entire script to end.
Adding an actionable aspect to a few of the other answers:
Both can give exit codes - default or defined by the function, and the only 'default' is zero for success for both exit and return. Any status can have a custom number 0-255, including for success.
Return is used often for interactive scripts that run in the current shell, called with . script.sh for example, and just returns you to your calling shell. The return code is then accessible to the calling shell - $? gives you the defined return status.
Exit in this case also closes your shell (including SSH connections, if that's how you're working).
Exit is necessary if the script is executable and called from another script or shell and runs in a subshell. The exit codes then are accessible to the calling shell - return would give an error in this case.
First of all, return is a keyword and exit is a function.
That said, here's a simplest of explanations.
return
It returns a value from a function.
exit
It exits out of or abandons the current shell.

Resources