314

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?

5
  • 4
    drop the function keyword, myfun() {...} suffices Commented Mar 25, 2011 at 13:33
  • 2
    What matters to if is the zero-exit status of myfun: if myfun exits with 0, then ... is executed; if it is anything else else ... is executed.
    – Eelvex
    Commented Mar 25, 2011 at 18:26
  • 8
    @nhed: the function keyword is a bashism, and will cause syntax errors in some other shells. Basically, it's either unnecessary or forbidden, so why use it? It's not even useful as a grep target, since it might not be there (grep for () instead). Commented Jun 1, 2013 at 7:33
  • 7
    @GordonDavisson: what? there are other shells? ;-)
    – nhed
    Commented Jun 1, 2013 at 14:41
  • Please don't use 0 and 1. See stackoverflow.com/a/43840545/117471 Commented Sep 6, 2017 at 22:05

11 Answers 11

431

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" ]
}
9
  • 4
    No you don't need to do that - see the sample.
    – Erik
    Commented Mar 25, 2011 at 11:44
  • 74
    For better readability you can use the 'true' command (which does nothing and completes successfully, i.e. returns 0) and 'false' command (which does nothing and completes unsuccessfully, i.e. returns a non-zero value). Also, a function that ends without an explicit return statement returns the exit code of the last executed command, so in the example above, the function body can be reduced to only [ -d "$1" ].
    – amichair
    Commented Mar 2, 2013 at 18:47
  • 27
    Bengt: it makes sense wheen you think of it as “error code”: error code 0 = everything went ok = 0 errors; error code 1 = the main thing this call was supposed to do failed; else: fail! look it up in the manpage. Commented Mar 3, 2014 at 19:51
  • 9
    It makes sense when you consider that in programming things can usually only succeed in one way, but can fail in infinite ways. Well maybe not infinite, but lots, the odds are stacked against us. Success/Error(s) is not boolean. I think this "Use 0 for true and 1 for false." should read "Use 0 for success and non-zero for failure".
    – Davos
    Commented Aug 10, 2017 at 3:57
  • 13
    Please don't use 0 and 1. See stackoverflow.com/a/43840545/117471 Commented Sep 6, 2017 at 22:05
310

Why you should care what I say in spite of there being a much higher 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.

  1. Experienced developers will spot you as an amateur (for the reason below).
  2. Experienced developers don't do this (for all the reasons below).
  3. It is error prone.
    • Even experienced developers can mistake 0 and 1 as false and true respectively (for the reason above).
  4. It requires (or will encourage) extraneous and ridiculous comments.
  5. 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 the "1=true, 0=false" mistake.

22
  • 16
    Avoiding the standard use of 0/1 as a return value just because it's obtuse and prone to confusion is silly. The entire shell language is obtuse and prone to confusion. Bash itself uses the 0/1 = true/false convention in its own true and false commands. That is, the keyword true literally evaluates to a 0 status code. In addition, if-then statements by nature operate on booleans, not success codes. If an error happens during a boolean operation, it should not return true or false but simply break execution. Otherwise you get false positives (pun).
    – Beejor
    Commented Dec 18, 2017 at 21:26
  • 3
    " if ! $(exit $status); then " -- That deserves a better explanation. It's not intuitive. I have to think about whether the program will exit before printing the message, or not. The rest of your answer was good, but that spoiled it. Commented Mar 6, 2018 at 3:44
  • 2
    @BrunoBronosky I read your answer and I think I understand the distinction between the content pipeline (stdin->stdout) and the error codes in return values. Yet, I don't understand why using return 1 should be avoided. Assuming I have a validate function I find it reasonable to return 1 if the validation fails. After all, that is a reason to stop the execution of a script if it isn't handled properly (e.g., when using set -e).
    – JepZ
    Commented Nov 4, 2018 at 14:46
  • 2
    There is nothing wrong with using return 1... Especially if you do not want to reference an earlier command. So you are not automatically an "amateur" doing that...
    – Akito
    Commented Mar 3, 2020 at 19:05
  • 2
    "exit" or "exist" ? It's ambiguous and I find it strange nobody has commented on this after 3 years. :-|
    – zaTricky
    Commented Oct 19, 2020 at 9:51
40
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes
4
  • 1
    What about negation?
    – einpoklum
    Commented Nov 26, 2016 at 21:44
  • What about it, @einpoklum?
    – Mark Reed
    Commented Sep 8, 2017 at 22:18
  • @MarkReed: I meant, add the "else" case to your example.
    – einpoklum
    Commented Sep 8, 2017 at 23:37
  • myfun "path" || echo no
    – Hrobky
    Commented Mar 12, 2018 at 0:06
14

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
4
  • 2
    I am uncertain as to how this answers the question asked. While it is nice to know that an empty $1 can return a true when empty, it does not provide any insight into how to return true or false from a bash function. I would suggest creating a new question "What happens when you do a test on an empty shell variable?" And then posting this as the answer.
    – DRaehal
    Commented Feb 17, 2014 at 15:48
  • 4
    Note, that if you add proper quoting to $1 ("$1") then you don't need to check for an empty variable. The [[ -d "$1" ]] would fail because this "" is not a directory.
    – morgents
    Commented May 20, 2016 at 7:49
  • @morgents inside [[ ... ]] the quotes make no difference. Inside [...] it does of course make a difference, because if the variable expansion is empty and elided, that just leaves [ -d ], which simply tests whether -d is a non-empty string; that of course is true. Commented Apr 3, 2022 at 23:08
  • (And from a wider perspective, some Unices will treat an empty filepath as equivalent to ., and some won't.) Commented Apr 3, 2022 at 23:08
13

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
2
  • Perhaps improve this code so that it doesn't spit out weird errors when you call it with if test_for_cat 'snow leopard' ; then echo "It's a feline" ; else echo "It's something else" ; fi Commented Jan 5, 2022 at 17:42
  • @MartinKealey I fixed the quoting that caused your error. But this is another example of why you should always use [[ instead of [ unless you absolutely must be POSIX sh compliant. See: mywiki.wooledge.org/BashFAQ/031 Commented Apr 2, 2022 at 0:00
11

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
}
14
  • Executing true or false inside a sub shell changes the outer scope? That makes some sense, considering that executing a failed binary would definitely trigger a "fail state". Are you sure that using true in this way "clears" it? What happens if you execute false prior to returning $(true)?
    – BuvinJ
    Commented Jul 19, 2020 at 13:30
  • I tested my concern, and it was not a problem. I can confirm this syntax works as expected, at least in Dash. Nice!
    – BuvinJ
    Commented Jul 20, 2020 at 16:50
  • 1
    The downside to this, however, is that you spawn an entire sub process (in many interpreters anyway) in order to simply return a primitive. The inefficiency of that, compared to any other "real" language, is staggering! I understand that it is very difficult to avoid such in shell scripting, but it is possible. In fact, I developed a library which allows one to avoid such insanity, generally speaking, and seen it dramatically speed up some "heavy" scripts. While my "backwards" answer is a bit less readable, I would still use it vs this pretty one if I cared about the processing details.
    – BuvinJ
    Commented Jul 20, 2020 at 16:59
  • To be picky (then I'll drop it...) this not "one command" btw. It's still two. One is nested inside a sub shell.
    – BuvinJ
    Commented Jul 20, 2020 at 19:59
  • 1
    If you're building a library and want to cater for "heavy" scripts then declaring TRUE=0; FALSE=1 somewhere and return $TRUE or return $FALSE would be a better answer. Code readability is more important than slight performance gains in most cases to use bash scripts. If performance and optimization is needed then bash isn't the answer. Commented Jul 21, 2020 at 19:28
6

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

1
  • 1
    If that test function had contained a printf, you would have noticed the lack of output from [ test ]. The rule is quite simple: the [ is not part of the inherent syntax of the if structure; rather it takes a list of commands, and [ is a (strangely named) command. By the way, calling a function test is a bad idea, since test is a built-in (and equivalent to [). Commented Apr 4, 2022 at 1:35
5

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.

1
  • nice, neat and understandable at first glance Commented Aug 2, 2020 at 12:56
4

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; }
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.

1
  • I get there because I wondered if using return 0 instead of return true wouldn’t be better because the first one is returning directly the value and therefore could be more efficient. But I’m noticing it’s just useless to use return at all if it’s to return the status of the last command :) I still wonder, when it’s needed to return in the middle of a function, to avoid executing what’s left, what’s the best solution then. I can’t see any difference between true; return and return true. And return 0 seems the most efficient still.
    – Stéphane
    Commented Jan 14 at 5:22
1

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.