0
  • I am working on a highly portable script that users shall source to their shells, forcing me to use POSIX scripting.

  • There are many useful functions in the script, one of them is special though, as it returns true or false status to the calling function.


Now, I used to use return 0 in cases like this. But it appears a much more readable true or false for return 1, respectively, works too.

The question is if these work the very same way or if there is a difference. Thanks.

5
  • In general true and false are external commands (that do indeed exit with 0 and 1 respectively). Do you really want to fork off a new process just for some minor advantage in readability?
    – muru
    Commented Aug 27, 2023 at 0:41
  • 1
    @muru FWIW, true and false are builtin in most shells, these days. eg bash-5.1$ type true true is a shell builtin Commented Aug 27, 2023 at 0:44
  • Shells treat an exit code of 0 as success (true) and non-zero as failure or error (false). This is the opposite of how most programming languages treat various values (0 or empty string is false, non-zero or non-empty is true). That's because only one value (0) is needed to indicate success, while many values can indicate various types of error - "no such file", "permissions problem", "out of memory", etc. Also note that while there are some commonalities between programs, the meaning of exit codes is very much application-specific. not all errors/failure-mode are relevant to all apps.
    – cas
    Commented Aug 27, 2023 at 1:16
  • so, "return 0" in a shell script or function means "Success. No problems". Returning any other number means "Failure. A problem occurred." and the exact exit code can be used to look up the nature of the problem by referring to that script or function's documentation, or the documentation of the external program that it was running. Most man pages have an "EXIT CODES" or "EXIT STATUS" or similarly-named section.
    – cas
    Commented Aug 27, 2023 at 1:21
  • @StephenHarris oh yes, I don't know what I was checking. :facepalm:
    – muru
    Commented Aug 27, 2023 at 1:39

3 Answers 3

2

true is portable, but doesn't by itself return. return true is not portable, and also doesn't work reliably.

If you have a function like so:

f() {
    true
}

Then yes, it will portably return from the function with an exit status of zero. When falling off from the end, the exit status of the last command is returned as the exit status of the function.

But of course this doesn't return with a truthy status, as the true in the middle by itself doesn't affect the control flow.

g() {
    # silly example condition
    if [ 1 = 1 ]; then
        true
    fi
    echo something else
    false
}

But you could add an explicit return, as return without arguments also uses the exit status of the last command:

The value of the special parameter '?' shall be set to n, an unsigned decimal integer, or to the exit status of the last command executed if n is not specified. If n is not an unsigned decimal integer, or is greater than 255, the results are unspecified.

h() {
    # silly example condition
    if [ 1 = 1 ]; then
        true
        return
    fi
    echo something else
    false
}

Now, if you were to try return true, it might appear it'd do what you wanted in some shells, but in the end it probably doesn't. At least in zsh, it appears to take the argument as an arithmetic expression, so return true would use the value of the variable true, or zero by default if it's not set.

% i() { return 1+1; }
% i; echo $?
2

% unset true
% j() { return true; }
% j; echo $?
0

% true=123
% j; echo $?
123

If you can arrange for true to be always set to 0, and false to be set to 1, and trust the users of your script library to not mess with those, then you could use return true and return false in zsh, or return "$true" and return "$false" in a standard shell. I wouldn't recommend that with those names but maybe something like true_return_value or false_return_value could be used.

Otherwise, you could use true; return and false; return in a standard shell, but I'm not sure if that's any more readable than return 0 and return 1. The fact that the truthy return value is zero should be familiar to shell programmers in any case.

0
1

true and false are not portable valid boolean values.

For example, a simple shell script

f()
{
  return true
}

f && echo whee

If we run this under different shells:

$ sh x
return: Illegal number: 'true'
$ bash x
x: line 3: return: true: numeric argument required
$ ksh93 x
whee
$ zsh x
whee
$ ksh x
x[6]: true: bad number

(The last shell is pdksh 5.2.14)

However return 0 is portable

$ cat x
f()
{
  return 0
}

f && echo whee

$ sh x
whee
$ bash x
whee
$ ksh93 x
whee
$ zsh x
whee
$ ksh x
whee

We can also see a return if 0 is counted as true so the && works.

Now we can kinda cheat..

f()
{
  true
}

What this does is call the true command... and then use the fact that the return code of a function without an explicit return is the return of the last command run.

So while this will work it's not as readable, because there's no explicit return and you're relying on implicit behaviour.

1
  • Thank you kindly. I just woke up. I did read both answers, ikkachu's may include a bit more detail, so while accepting that one, I really value your precious time! cheers Commented Aug 27, 2023 at 7:18
1

Here is what POSIX has to say about it:

  • The return utility shall cause the shell to stop executing the current function or dot script. If the shell is not currently executing a function or dot script, the results are unspecified.
  • The value of the special parameter '?' shall be set to n, an unsigned decimal integer, or to the exit status of the last command executed if n is not specified. If n is not an unsigned decimal integer, or is greater than 255, the results are unspecified. When return is executed in a trap action, the last command is considered to be the command that executed immediately preceding the trap action.

So:

  • you can use return to return from sourced scripts (dot script), not just functions.

  • You can either pass 0 or 1 argument (no option, -- may not even be accepted), but that has to be an unsigned decimal integer number (0, 1, 2... not true, 0x1, -1, +1, unclear and in practice not portable for 010 which could be seen as an octal, not decimal number and in practice is like in zsh when in sh emulation)

  • because return without arguments returns with the status of the previous command, { true; return; } can be a way to return with a success exit status. Beware though that { false; return; } would return with a non-zero (failure) exit status, but which exit status false returns is not specified (in practice, I've never seen any implementation that used anything other than 1), and false may exit the shell (or subshell) if the errexit option is enabled (could be worked around with { false || return; }). It's not clear whether return $(true) or return $(false) should work. In practice, it doesn't in dash nor mksh for instance, so is not portable.

  • it's not perfectly clear what shell is not currently executing a function or dot script means exactly. Some have been interpreting it to mean that using return from a subshell even when that subshell is run from within a function or dot script or is the body of the function (as in f() (return 1)) is unspecified. In practice though, I find that return in those cases acts like exit. It exits/returns from the current subshell, but not from the enclosing function/dot-script.

    In any case, to return from a function/dot-script, that has to be done outside of a subshell if that subshell is not the last command in the function/dot-script, bearing in mind that what constitutes a subshell varies between shell. For instance in:

    f() {
      cmd | {
        IFS= -r read var || return
        echo whatever
        cat
      }
      echo Here
    }
    

    the return would return from the function in ksh or zsh, but not in bash (unless the lastpipe option is set) or dash, mksh because in those, all pipe components, even the last run in a subshell (as allowed by POSIX). So you'd need to work around it with something like:

    f() {
      cmd | {
        IFS= -r read var || return
        echo whatever
        cat
        true
      } || return
      echo Here
    }
    

    Forcing a subshell, and use exit within would make it clearly POSIX and behave consistently across shells (and also clearer that the $var variable assignment by read may not survive the pipeline):

    f() {
      cmd | (
        IFS= -r read var || exit
        echo whatever
        cat
        true
      ) || return
      echo Here
    }
    

    (in all those return/exit would return with the failure exit status of read; you can change to exit 1/return 1 to give a specific failure exit status value)

  • note that the behaviour of ! return or ! return 1 is not portable (not that useful for return, but would be more useful if portable for ! break to break from loops with a failure exit status).

In any case, the true and false commands don't cause the enclosing function/dot-script to return, except as already noted for the special case where the errexit option is set (and its effect not cancelled one way or the other) in which case failing commands including false cause the shell process to exit (not necessarily/only the enclosing function/dot-script), they are ordinary commands, usually builtin, but not special builtins let alone control flow operators contrary to return/break/continue...

To make your code more legible, you could choose to define variables for the success exit status and for a failure exit status like:

ok=0 true=0 success=0 yes=0 on=0
failure=1 false=1 no=1 off=1

And use return "$true" or return "$false" or same with exit, which in zsh can be simplified to return $true or even return true

Beware though that in arithmetic expressions, the meaning is reversed, 0 meaning false and anything else true like in C. So with those values of those variables while (( true )) (in Korn-like shells) would not work as expected, so you may prefer to name those variables status_ok, status_failure instead of ok/failure.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .