Returning a boolean from a Bash function - bash

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="$?"

Related

exception handling in korn shell

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
}

bash, logic of: a && b && c

One thing I don't get with this operators is when I use two of them in sequence.
What I mean by that:
% true && echo "problem"
problem
% echo $?
0
So far, so good. true returns "error" (exit status 1) and echo "problem" returns 0, so logical AND operation result must be 0.
% true && echo "problem" || echo "exit"
problem
OK, that's a surprise: since true && echo "problem" results in 0, || should also evaluate echo "exit", since after all right-hand operand of || might be true and so the result of this logical OR might be true.
Now:
% true && echo "problem" && echo "exit"
problem
exit
This is also surprising: after all since true && echo "problem" returns zero, the lazy && operator should not evaluate echo "exit" since the result of logical AND must be zero anyway.
Why is the behavior of last two examples opposite to what I intuitively expect?
P.S. This is opposite of Python behavior:
% python
>>> def pp():
... print "problem"
...
>>> def pe():
... print "exit"
>>> True and pp() and pe()
problem
>>> True and pp() or pe()
problem
exit
You're misunderstanding success/failure statuses and also what && and || mean in this context in bash.
An exit status of success is 0, while failure is non-zero so when you say true returns "error" (exit status 1), no it doesn't. Also, nothing in shell "returns" anything. Scripts and functions produce output and have an exit status - using the word "return" leads to confusion over which of those 2 separate things you mean. For example if we define this function:
foo() {
echo "hello"
return 7
}
and then use it to populate a variable:
var=$(foo)
$ echo "$var"
hello
did foo() "return" hello or did foo() "return" 7? The best answer is no, it didn't "return" either - it output hello and exited with status 7.
Although there's a poorly-named "return" keyword there what that REALLY is producing is an exit status for the function, the same as if you had a shell script that was just:
echo "hello"
exit 7
and you can see that if you test it ($? always holds the exit status of the most recently run command):
foo() {
echo "hello"
return 7
}
$ foo
hello
$ echo "$?"
7
I assume the shell creators chose "return" for the function keyword because "exit" already meant "exit from the running process" but IMHO that made things confusing, though I don't have a better suggestion and even if I did that ship has sailed long ago. If you read return 7 as set the exit status to 7 then return from the function without assuming the function is actually "returning" anything then you'd be right.
Note also that the function is outputting "hello" - that's also not a "return" but if you use it as var=$(foo) then var ends up containing hello so then some people incorrectly refer to that as a "return" too since in other languages like C if you wrote var=foo():
char *foo() {
return "hello"
}
var=foo()
then var would contain the argument that was given to return in the function but that is just not the same semantics as shell where, unlike the similar C code above, var=$(foo) sets var to the output from foo(), not the "return" (actually exit status) from foo().
So - the function above doesn't actually "return" anything, it outputs hello and exits with status 7.
So here's what your command line true && echo "problem" || echo "exit" actually does:
true = output nothing and exit with status 0 (success)
echo "problem" - output problem and exit with status 0 (success)
echo "exit" - output exit and exit with status 0 (success)
Now, what do && and || mean? What they really are is shorthand for if statements:
&& foo = if the previously run command exited success then execute foo
|| foo = if the previously run command exited failure then execute foo
So a command line like:
cmdA && cmdB || cmdC
in terms of success/fail status should be read as:
cmdA
ret=$?
if (( ret == 0 )); then
cmdB
ret=$?
fi
if (( ret != 0 )); then
cmdC
ret=$?
fi
( exit "$ret" )
We need the ret temp variable because the if itself has an exit status that'd overwrite $?. So cmdC will get called if cmdA exits with a failure status, but it'll also get called if cmdA succeeded and then cmdB exited with a failure status. At the end of cmdA && cmdB || cmdC the exit status as stored in $? will simply be the exit status of whichever command ran last, it will not, for example, be the product of boolean arithmetic on all of the exit statuses of all the commands that ran as apparently suggested in the question might be the case.
Note also that what that should NOT be read as is what you may intuitively have expected if you thought of && ... || ... as a ternary expression, which trips many people up, especially since that || is typically an error leg whose incorrect placement may escape your code inspectors/testers notice:
cmdA
if (( $? == 0 )); then
cmdB
else
cmdC
fi
Given the above, here's what your command lines actually mean:
true && echo "problem" || echo "exit"
true
ret=$?
if (( ret == 0 )); then
echo "problem"
ret=$?
fi
if (( ret != 0 )); then
echo "exit"
ret=$?
fi
( exit "$ret" )
true && echo "problem" && echo "exit"
true
ret=$?
if (( ret == 0 )); then
echo "problem"
ret=$?
fi
if (( ret == 0 )); then
echo "exit"
ret=$?
fi
( exit "$ret" )
and if what you WANTED to have happen instead of "2" above was actually:
true
if (( $? == 0 )); then
echo "problem"
else
echo "exit"
fi
then you should write that code or similar instead of using &&s and ||s (e.g. as 1 line you could write if true; then echo "problem"; else echo "exit"; fi) so you don't get unexpected output if you reach the echo "problem" leg and it fails for some reason thereby causing you to afterwards fall into the echo "exit" leg (unlikely with just echo but very possible with other commands).

complicated bash functions returning boolean

I want to do something like this:
function one {
if [ "$1" == "hello" ]; then
return true
fi
return false
}
if [ one hello -o one goodbye ]; then
echo "Well, that's weird."
fi
As written, I know this doesn't work. Bash won't let you return boolean values. You can return 0 or 1. But bash doesn't seem to like using 0 or 1 as boolean expressions. So instead I have to return 0 instead of true and 1 for false. I can live with that. But then my if becomes:
if [ one hello -eq 0 -o one goodbye -eq 0 ] ...
Which I can do, but it's awkward. But THAT doesn't work, either, because test doesn't want to call my function with an argument.
if [ `one hello` -eq 0 -o `one goodbye` -eq 0 ] ...
Finally, I think that version works. But it's fugly.
Is there an elegant way to:
Have a bash function that returns true/false
And it takes arguments
And then it gets used in a complex if-statement (there's some combination of -o / -a between the [] )
I'd really like to have some way of writing:
if [ myfunc1 $somearg -a $myfunc2 $someotherarg ]; then ...
For now, I'm doing it the fugly way.
You don't really write predicates like this. Instead, you set the exit status to 0 if a function succeeds, or non-zero value if it fails. The if statement then checks the exit status directly.
one () {
if [ "$1" = "hello" ]; then
return 0
fi
return 1
}
if one hello || one goodbye; then
echo "Well, that's weird."
fi
Since [ itself is a command that has a 0 exit status when the test is true and 1 if false, you can define one as simply
one () {
[ "$1" = "hello" ]
}
If no return statement is encountered, the exit status of a function is the exit status of the last command to execute.
Functions don't return true or false. Instead, they succeed or fail. if checks to see if a command succeeded or not. [ is a very common command that is used in shell scripts, but it misleads many into believing it is part of the grammar. It is not. You can simply write:
if one hello || one goodbye; then
echo "Well, that's weird."
fi
You don't want to use -a or -o. (Those are typically passed as arguments to [, but are effectively deprecated in that usage.) Instead, use the shell operators && and ||.
The fun thing about the strings "true" and "false" is that they are also shell builtin commands that have the expected exit status:
function one {
[[ "$1" == "hello" ]] && echo true || echo false
}
a=$(one hello)
b=$(one goodbye)
if $a || $b; then ...
But, don't do this. Keep it simple as other answers advice.
Like this?
one () {
if [ "$1" = "hello" ]; then
echo true; return 0
fi
echo false; return 1
}
$ one hello && one bye || one hello
true
false
true
Use the builtin commands, /bin/true and /bin/false:
/bin/true always returns 0.
/bin/false always returns 1.
function one {
[[ "$1" == "hello" ]] && /bin/true || /bin/false
}
if one hello || one goodbye; then
# do stuff…
fi;
Because they’re builtins under the /bin (/usr/bin) directory, which will likely be in your $PATH, you can simply call true or false:
function one {
[[ "$1" == "hello" ]] && true || false
}
if one hello || one goodbye; then
# do stuff…
fi;
Some of the answers above almost got it. Though, you wouldn’t want to echo the words, “true” or “false,” as strings—that does nothing in relation to the question. You want to call the commands that were made for this very purpose.

check returncode in a most small (and elegant) way

how to make this work?
#!/bin/bash
# this code is ugly and does not work
check_if_failed() {
echo "arg1: $1"
echo "arg2: $2"
if [ $1 -ne 0 ] ; then
exit $1
fi
}
CHECK="check_if_failed $? $LINENO"
true ; $CHECK
false ; $CHECK
# (edit)
if true ; then
false ; $CHECK
fi
The goal is to have one very small command to check the returncode, so that I can append it to every command line in a simple way.
Why adding something after each command you want to check? Let bash do it for you!
#!/bin/bash
trap 'echo "Line $LINENO returned $?"' ERR
true
false || : # this will not be checked
false # this will be checked
I think what you're looking for is simply
some_command || exit
As in C, the || short-circuits, so its right-hand side only gets evaluated if the left-hand side evaluates to "false" (which is interpreted here as a nonzero return code).

Bash ignore error and get return code

I am using set -e to abort on errors.
But for particular one function I want to ignore error and on error I want return code of the function.
Example:
do_work || true
if [ $? -ne 0 ]
then
echo "Error"
fi
But it is not work return code is always true due || true
How to get return code on do_work on error ?
do_work || {
status=$?
echo "Error"
}
Several of the answers given here are not correct, because they result in a test against a variable that will be un-defined if do_work succeeds.
We need to cover the successful case as well, so the answer is:
set -eu
do_work && status=0 || status=1
The poster's question is a little ambiguous because it says in the text "on error I want return code" but then the code implies "I always want the return code"
To illustrate, here is problematic code:
set -e
do_work() {
return 0
}
status=123
do_work || status=$?
echo $status
In this code the value printed is 123, and not 0 as we might hope for.
You could use a subshell shortcut:
( set +e; do_work )
if [ $? -ne 0 ]
then
echo "Error"
fi
Hope this helps =)
One way is to use a pipe, -e only looks at the right-most result of a pipe:
set -e
do_work | true
retn=${PIPESTATUS[0]}
if (( $retn != 0 ))
then
echo "Error $retn"
fi
echo Ending
I wrote a simple do_work which just did exit 42 and got the following output:
Error 42
Ending
The PIPESTATUS array is maintained by Bash, with each element giving the return code of each part of the pipeline. We need to capture it at once (hence $retn) since it is overwritten at each command.
Of course this might be problematic if your do_work includes a pipe itself.
do_work || status=$?
if [ $status -ne 0 ]
then
echo "Oh no - Fail whale $status has arrived"
fi

Resources