Returning a boolean from a Bash function

269,072

Solution 1

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" ]
}

Solution 2

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.

  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).
  1. It requires (or will encourage) extraneous and ridiculous comments.
  2. 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.

Solution 3

myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes

Solution 4

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

Solution 5

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
Share:
269,072

Related videos on Youtube

luca
Author by

luca

Updated on April 25, 2022

Comments

  • luca
    luca almost 2 years

    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?

    • glenn jackman
      glenn jackman about 13 years
      drop the function keyword, myfun() {...} suffices
    • Eelvex
      Eelvex about 13 years
      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.
    • Gordon Davisson
      Gordon Davisson almost 11 years
      @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).
    • nhed
      nhed almost 11 years
      @GordonDavisson: what? there are other shells? ;-)
    • Bruno Bronosky
      Bruno Bronosky over 6 years
      Please don't use 0 and 1. See stackoverflow.com/a/43840545/117471
  • luca
    luca about 13 years
    mmm.. but then I'll have to use the [ ] operator? Or it should work like that? Maybe then I'm doing something else wrong...
  • Erik
    Erik about 13 years
    No you don't need to do that - see the sample.
  • Dewsworld
    Dewsworld almost 12 years
    @Erik can you please explain about that $1 on the if. I didn't understand how it works without [] and it doesn't show any value even.
  • amichair
    amichair about 11 years
    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" ].
  • DRaehal
    DRaehal about 10 years
    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.
  • flying sheep
    flying sheep about 10 years
    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.
  • demonkoryu
    demonkoryu about 8 years
    Yeah, but he probably returned 1 for true and 0 for error.
  • morgents
    morgents almost 8 years
    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.
  • einpoklum
    einpoklum over 7 years
    What about negation?
  • Davos
    Davos over 6 years
    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".
  • Bruno Bronosky
    Bruno Bronosky over 6 years
    Please don't use 0 and 1. See stackoverflow.com/a/43840545/117471
  • Mark Reed
    Mark Reed over 6 years
    What about it, @einpoklum?
  • einpoklum
    einpoklum over 6 years
    @MarkReed: I meant, add the "else" case to your example.
  • ZeroPhase
    ZeroPhase over 6 years
    I could have sworn there are languages where the c paradigm of 1 and 0 for true / false are reversed. Though, you should probably make a macro or whatever the language equivalent is for those.
  • Bruno Bronosky
    Bruno Bronosky over 6 years
    @ZeroPhase 1 & 0 for false & true would be ridiculous. If you have a binary datatype, there is no reason for that. What you are dealing with in bash is a status code that reflects success (singular) and failures (plural). It's "if success do this, else do that." Success at what? Could be checking for true/false, could be checking for a string, integer, file, directory, write permissions, glob, regex, grep, or any other command that is subject to failures.
  • Bruno Bronosky
    Bruno Bronosky over 6 years
    @some you can upvote my comment on the selected answer so that more people see this one. ;-) stackoverflow.com/questions/5431909/…
  • Beejor
    Beejor over 6 years
    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).
  • Bruno Bronosky
    Bruno Bronosky over 6 years
    @Beejor in your first sentence it sounds like you are saying it's not worth making things better because it's not possible to make them perfect. To that I say en.wikipedia.org/wiki/Perfect_is_the_enemy_of_good To the rest of your claims, it sounds like you either didn't read/understand my post, or gave up on bash and didn't learn it. "if-then statements by nature operate on booleans" Erm, wat? "success codes" Wat? Sounds like you started with a pun you wanted to make and worked backward. If you gave me an opportunity to help you and I missed it, please clarify.
  • Beejor
    Beejor over 6 years
    @BrunoBronosky What I meant is that working around 0/1 is not really better per se, since it's the convention Bash uses (how true and false work), even though the de facto convention in programming is 0=false. Bash mixes the concept of return/status/success/exit/whatever codes and booleans in an odd manner, as does your sanctimonious diatribe. So what I'm saying is more akin to "if it ain't broke", or rather "if it's broke, why fix it and break it another way".
  • Beejor
    Beejor over 6 years
    @BrunoBronosky The general concept of if-then across all programming languages is such that "if (statement evaluating to true or false)" (binary) and not "if (statement evaluating to integer, representing how the function failed or not)" (n-ary). Forcing a return code of 0/1 ensures a predictable binary result when you need one. It's not for every case, but works fine for creating functions to use with if. Either way, tomayto tomahto. I apologize if you took my post to heart, or I used the wrong nomenclature, but I use Bash a lot, and there's no need to talk down to me.
  • Bruno Bronosky
    Bruno Bronosky over 6 years
    @Beejor You refuse to let go of your flawed thinking. Bash DOES NOT use 0/1 for True/False values. It uses zero and non-zero for exit codes, the latter meaning failure. You know this but you are obstinate. The command false exits with [only] 1 (aka 2^0) because it has exactly 1 failure mode. If it had multiple it would use a power of 2 for each and bitwise AND these values together any time it needed to exit with multiple codes. A single exit code can give you countless amounts of information. It's brilliant, though a little confusing at first.
  • Bruno Bronosky
    Bruno Bronosky over 6 years
    @Beejor bash does not mix the concept of exit codes and booleans. You do. Bash is untyped, but even using declare, boolean is not an option. MANY languages perform if on "non binary" types. It's called truthiness. In bash, 0 is truthy. This is how I #DealWithIt. "Forcing a return code of 0/1" gives you nothing that using 0/255 or true/false lacks… except reader confusion. I'll stop there because I respect stackoverflow.com/a/35697810/117471
  • Craig  Hicks
    Craig Hicks about 6 years
    " 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.
  • Bruno Bronosky
    Bruno Bronosky about 6 years
    @CraigHicks, valid point. I added a sentence to address it. Honestly, I was confused when I first looked at it. At 5am it was not immediately obvious to me that it was the subshell that was being exited. When I wrote it, I had been writing bash for hours and was in the zone.
  • Hrobky
    Hrobky about 6 years
    myfun "path" || echo no
  • JepZ
    JepZ over 5 years
    @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).
  • Bruno Bronosky
    Bruno Bronosky over 5 years
    @Jepz that’s a great question. You are correct that return 1 is valid. My concern is all the comments here saying “0=true 1=false is [insert negative word]”. Those people are likely to read your code some day. So for them seeing return 255 (or 42 or even 2) should help them think about it more and not mistake it as “true”. set -e will still catch it.
  • Jesse Chisholm
    Jesse Chisholm over 4 years
    re: mixing of booleans and exit codes If you are going down that road, then it isn't just bash but most of linux and the c/c++ programs aimed at linux. Numerous program suites have something that equates to #define NO_ERROR 0 or #define SUCCESS 0.
  • Akito
    Akito about 4 years
    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...
  • BuvinJ
    BuvinJ almost 4 years
    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
    BuvinJ over 3 years
    I tested my concern, and it was not a problem. I can confirm this syntax works as expected, at least in Dash. Nice!
  • BuvinJ
    BuvinJ over 3 years
    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
    BuvinJ over 3 years
    To be picky (then I'll drop it...) this not "one command" btw. It's still two. One is nested inside a sub shell.
  • RandomName
    RandomName over 3 years
    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.
  • BuvinJ
    BuvinJ over 3 years
    See the answer by Bruno Bronosky regarding a return of 0 or 1...
  • BuvinJ
    BuvinJ over 3 years
    To be clear, I agree that your syntax is nicer to read, that readability generally trumps performance, and that shell scripting is the wrong language if you're goal is performance. That said, there are absolutely valid use cases where you would want good performance from such, and where such optimizations go a very long way.
  • BuvinJ
    BuvinJ over 3 years
    For instance, I wrote a more robust emulation of the python "os.walk", which recursively traces through file system trees and fires off "call back" functions for you to process the files, directories, sym links etc. It's super useful in shell scripting. It has a good deal work to do, and leans on other custom functions. When processing large volumes of data, my optimizations to avoid spawning a billion sub processes has a massive impact.
  • RandomName
    RandomName over 3 years
    Bruno Bronosky's answer against returning 0 or 1 is also for code readability reasons. Returning $TRUE or $FALSE fixes those concerns and without the subshells used in my answer. No code comments needed, looks clean and obvious what the developer was thinking.
  • BuvinJ
    BuvinJ over 3 years
    I've written scripts like that many times. The thing is, if you write the rest of a script in a sophistated style, requiring the reader to have an advanced understanding for shell scripting to begin with, using the builtin statements true and false as designed and intended is not exactly writing in an unreadable manner.
  • BuvinJ
    BuvinJ over 3 years
    Here's a little example of what I mean. What if I want to write [ -d "${path}" ] && rm -R "${path}" That's ninja style bash, rather than an if/then. Should I not write that, because it's funky as all hell compared to how to one would write that in a more readable language? It's my general observation that in shell scripting (and in an interesting parallel Batch...) that you are basically funnel into writing less and less readable code as your skills advance. This is true in a sense for any language, but it's more so syntactically in these terminal/shell input contexts.
  • RandomName
    RandomName over 3 years
    There is no right or wrong answer but I believe that we should try to be consistent from language to language if possible. The voice in my head reads [ -d "${path}" ] && rm -R as if directory exists then remove it recursively. Writing it with an if/then statement makes it quicker for our human brains to understand, also, makes it easier to skim past those statements when looking for some other piece of code. So yes, I would prefer if [ -d "${path}" ]; then rm --recursive "${path}"; fi.
  • david valentino
    david valentino over 3 years
    nice, neat and understandable at first glance
  • zaTricky
    zaTricky over 3 years
    "exit" or "exist" ? It's ambiguous and I find it strange nobody has commented on this after 3 years. :-|
  • yurez
    yurez over 3 years
    What if run my script with bash -e? Will it terminate in the middle when I use false?
  • Martin Kealey
    Martin Kealey over 2 years
    The $( in if ! $(exit $status) ; then is problematic, because it relies on the part inside the parentheses not outputting anything - which is true for a simple exit, but not necessarily true if it's more complicated. Perhaps you meant if ! ( exit "$status" ) ; then? Or even better, if ! some_command ; then ...?
  • Martin Kealey
    Martin Kealey over 2 years
    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
  • WinEunuuchs2Unix
    WinEunuuchs2Unix about 2 years
    On February 19, 2022 the "250+" (upvotes) can be changed to "350+" or, perhaps reworded altogether.
  • Bruno Bronosky
    Bruno Bronosky about 2 years
    @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
  • Martin Kealey
    Martin Kealey about 2 years
    @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.
  • Martin Kealey
    Martin Kealey about 2 years
    (And from a wider perspective, some Unices will treat an empty filepath as equivalent to ., and some won't.)
  • Martin Kealey
    Martin Kealey about 2 years
    I dislike this use of $(true) and $(false) because it breaks another important stylistic rule: always quote expansions. Someday someone is going to through the ShellCheck results, and "fix" this code by changing return $(true) to return "$(true)". Which isn't a syntax error, but will then blow up as return complains numeric argument required since an empty string is not a number.
  • Martin Kealey
    Martin Kealey about 2 years
    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 [).
  • Martin Kealey
    Martin Kealey about 2 years
    Any time I see return 255, I'm more inclined to think "some newbie mistakenly wrote return -1 or exit -1 (thinking it's like a syscall with errno) , and then the effect of that got codified into some internal document, and everyone else kept mimicking it.
  • Martin Kealey
    Martin Kealey about 2 years
    99% of the uses of $? are pointless verbosity. If you don't care about anything other than zero/non-zero, then you don't need to look at $? at all. Instead of somecmd ; status=$? ; if (exit "$status") ; then ... you should simply write if somecmd ; then .... If you really need to record $? (say, to put it in a debugging message) then do that as the first thing in the else block (you don't need to in the then block, because you know it's 0).
  • RandomName
    RandomName about 2 years
    I went with the return $TRUE approach myself because it doesn't create subshells. So, because I value code readability way too much, I vow to never use return "$TRUE" just to satisfy some online tool.