complicated bash functions returning boolean - bash

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.

Related

Compound conditional in a bash while loop

Im modifying an existing bash script and having some trouble getting the while loop behaving correctly. This is the original code
while ! /usr/bin/executable1
do
# executable1 returned an error. So sleep for some time try again
sleep 2
done
I would like to change this to the following
while ! /usr/bin/executable1 && ! $(myfunc)
do
# executable1 and myfunc both were unsuccessful. So sleep for some time
sleep 2
done
executable1 returns 0 on success and 1 on failure. I understand that "true" in bash evaluates to 0 so thats why the original script would keep looping till executable returned success
Accordingly myfunc is coded like this
myfunc ()
{
# Check if file exists. If exits, return 0, If not, return 1
if [ -e someFile ]; then
return 0
fi
return 1
}
I notice that the my new while loop does not seem to call executable1. It always calls myfunc() and then exits out of the loop immediately. What am I doing wrong?
I tried various ways of coding the while loop (with (( )), [ ], [[ ]] etc), but nothing seems to fix it
You don't need $(...) to call a function, just to capture its standard output. You simply want
while ! /usr/bin/executable1 && ! myfunc
do
sleep 2
done
Note that myfunc can also be more simply written
myfunc () {
[ -e someFile ]
}
or even (in bash)
myfunc () [[ -e someFile ]]
Either way, it's almost not worth defining myfunc separately; just use
while ! /usr/bin/executable1 && ! [[ -e someFile ]]
do
sleep 2
done
It might also be simpler to use an until loop:
until /usr/bin/executable1 || [[ -e someFile ]]; do
sleep 2
done

BASH: How to correctly return true and false from a function

I have a function in a bash script that I want to return true if a process is running and false if not. The code for my function is:
checkProcess() {
if [ kill -s 0 $(getPid) > /dev/null 2>&1 ]; then
return 0
else
return 1
fi
}
When I use this function it does not seem to work. I use it like so:
if [ ! checkProcess ]; then
#do something, like rm file containing PID
fi
However if I just use the below implementation it works fine:
if [ ! kill -s 0 $(getPid) > /dev/null 2>&1 ]; then
#do something, like rm file containing PID
fi
Is there a reason for this? Am I not returning the correct values in my function? Or is it all just wrongly implemented?
[ is not syntax; it is a command. It is commonly used with if, which works based on its return code.
To directly check the return code of a different command, e.g. kill, you should remove the [ and ] (the last argument to [):
if kill -s 0 $(getPid) &>/dev/null; then
# ...
fi
Note that &> is a Bash extension, equivalent to >/dev/null 2>&1.

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).

Returning a boolean from a Bash function

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

Bash Script function verification

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

Resources