32

I'm having trouble understanding the behavior of the return built-in in Bash. Here is a sample script.

#!/bin/bash

dostuff() {
    date | while true; do
        echo returning 0
        return 0
        echo really-notreached
    done

    echo notreached
    return 3
}

dostuff
echo returncode: $?

The output of this script is:

returning 0
notreached
returncode: 3

If, however, the date | is removed from line 4, the output is as I expected:

returning 0
returncode: 0

It seems like the return statement as used above is acting the way I thought the break statement ought to behave, but only when the loop is on the right hand side of a pipe. Why is this the case? I couldn't find anything to explain this behavior in the Bash man page or online. The script acts the same way in Bash 4.1.5 and Dash 0.5.5.

3
  • Interesting. Yes it seems that return behaves a bit like break; also with returns inside if statements you can see this happens. Unless the if expression is VERY simple. Commented Jul 31, 2019 at 15:42
  • @gaoithe nope || Commented Sep 6, 2024 at 4:35
  • If you read a function in C and other languages with returns in the functions, then you can follow the logic and understand the function flow, i.e. the behaviour at each return is that the function ends. It is not obvious at first that by introducing a pipe in bash you are introducing a subshell (like a nested function). Once you understand you have this nested subshell then you can understand the return behaviour. Commented Sep 6, 2024 at 8:38

5 Answers 5

37

In the date | while ... scenario, that while loop is executed in a subshell due to the presence of the pipe. Thus, the return statement breaks the loop and the subshell ends, leaving your function to carry on.

You'll have to reconstruct the code to remove the pipeline so that no subshells are created:

dostuff() {
    # redirect from a process substitution instead of a pipeline
    while true; do
        echo returning 0
        return 0
        echo really-notreached
    done < <(date)

    echo notreached
    return 3
}
Sign up to request clarification or add additional context in comments.

Comments

7

If you return inside a function, that function will stop executing but the overall program will not exit.

If you exit inside a function, the overall program will exit.

You cannot return in the main body of a Bash script. You can only return inside a function or sourced script.


For example:

#!/usr/bin/env bash

function doSomething {
    echo "a"
    return
    echo "b"  # this will not execute because it is after 'return'
}

function doSomethingElse {
    echo "d"
    exit 0
    echo "e"  # this will not execute because the program has exited
}

doSomething
echo "c"
doSomethingElse
echo "f"  # this will not execute because the program exited in 'doSomethingElse'

Running the above code will output:

a
c
d

1 Comment

The original question is returning inside a function, so while this answer is informative then it doesn't answer the question.
4

But return should terminate a function call, not a subshell. exit is intended to terminate (sub)shell. I think, it's some undocumented bug/feature.

  • echo | return typed in a commandline gives an error. That's correct - return should be in a function.
  • f(){ echo|return; } is accepted in the Bash and Dash, but return doesn't terminate a function call.

If return terminates a subshell, it would work outside a function. So, the conclusion is: return terminates a subshell in a function which is strange.

3 Comments

Here's a demo that supports the conclusion: function foo { echo start function; ( echo start subshell; return; echo end subshell); echo end function; }
This seems to be a good explaination. Simple expressions in if or while statements will not cause a subshell to run so return will behave as ~normal/expected~. More complex expressions (e.g. use variable in expression) will cause subshell so return looks like it behaves as a break;.
@gaoithe nope ||
1

The thing is: the subshell is a separate process. It doesn't really have a way to say to the parent shell: "I'm exiting because of a return"

There is no such thing in the exit status, which is the only thing the parent shell gets.

Comments

1

To cover this interesting feature of Bash...

  • return inside if (or any control command with expression like if/while/...)

  • return inside if with simple and less simple expressions

The subshell explanation is good. Control returns out of the current subshell. Which might be the Bash function. Or it might be any of the nested control commands with an expression which has caused a subshell to be invoked.

  1. for very simple expressions, e.g., "true" or "1 == 1" there is no subshell invoked. So return behaves as ~normal/expected~.

  2. for less simple expressions, e.g., variable expanded and compared with something then return behaves like break;

Simple (no subshell) examples:

$ rtest () { if true; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if [[ 1 == 1 ]] ; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if [[ 1 =~ 1 ]] ; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if $DO ; then echo one; return 2; echo two; else echo three; return 3; fi; echo not simple; return 7; }
$ rtest
one
$ echo $?
2

$ rtest () { if [[ $DO ]]; then echo one; return 2; echo two; else echo three; return 3; fi; echo not simple; return 7; }
$ rtest
three
$ echo $?
3

$ rtest () { if [[ $DO == 1 ]] ; then echo one; return 2; echo two; else echo three; return 3; echo four; fi; echo not simple; return 7; }
$ rtest; echo $?
one
2
$ DO=1; rtest; echo $?
one
2
$ DO=0; rtest; echo $?
three
3

Expression not simple and presuming subshell is invoked, return behaviour is like break;

Not simple (subshell) example ... =~ inside [[ ]]:

$ rtest () { if [[ $DO =~ 1 ]] ; then echo one; return 2; echo two; fi; echo not simple; return 7; }
$ rtest
not simple
$ echo $?
7

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.