Reputation: 12601
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?
Upvotes: 317
Views: 343762
Reputation: 70319
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.
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.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.
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.
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
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.)
# 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 the "1=true, 0=false" mistake.
Upvotes: 320
Reputation: 116948
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="$?"
Upvotes: 1
Reputation: 139
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
Upvotes: 13
Reputation: 596
For code readability reasons I believe returning true/false should:
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
}
Upvotes: 11
Reputation: 3548
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; }
Upvotes: 4
Reputation: 11048
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...
!
Upvotes: 6
Reputation: 11048
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.
Upvotes: 5
Reputation: 91270
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" ]
}
Upvotes: 433
Reputation: 21
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.
Upvotes: 1
Reputation: 11367
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
Upvotes: 14
Reputation: 3654
myfun(){
[ -d "$1" ]
}
if myfun "path"; then
echo yes
fi
# or
myfun "path" && echo yes
Upvotes: 40