Sign up ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It's 100% free, no registration required.

I'm been having some weird problems with bash lately. While trying to simplify my script, I came up with this small piece of code:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

return should have exited the function without printing $?, shouldn't it? Well, then I checked if I can return from a pipe alone:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

The same happens without a while loop:

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

Is there something I'm missing here? A Google search brought nothing about this! My bash version is 4.2.37(1)-release on Debian Wheezy.

share|improve this question
    
Anything wrong with the settings I suggested in my reply that allow your script to behave the intuitive way you expected it to do? –  jlliagre 15 hours ago
    
@jlliagre It is a rather complex script on the thousands of lines. With the concern of breaking something else, I prefer to just avoid running a pipe within the function, so I replaced it with a process substitution. Thanks! –  Teresa e Junior 14 hours ago
    
Why not remove the first two examples, if the while is not needed for reproduction? It distracts from the point. –  Lightness Races in Orbit 11 hours ago
    
@LightnessRacesinOrbit A while loop is a very common usage for a pipe with return. The second example is more straight to the point, but it is something I don't believe anyone would ever use... –  Teresa e Junior 8 hours ago
    
Unfortunately my correct answer has been deleted... You are in a grey zone as you do something that is unspecified. The behavior depends on how the shell interprets pipes and this is even different between the Bourne Shell and the Korn Shell even though ksh was derived from sh sources. In the Bourne Shell, the while loop is in a subshell therefore you see the echo as with bash, In ksh the while loop is the foreground process and thus ksh does not call echo with your example. –  schily 1 hour ago

5 Answers 5

up vote 6 down vote accepted

Related: http://stackoverflow.com/a/7804208/4937930

It's not a bug that you cannot exit a script or return from a function by exit or return in subshells. They are executed in another process and not affecting the main process.

Besides that, I suppose you are seeing undocumented behaviors of bash on (probably) undefined spec. In a function, no errors are asserted for return at top level of subshell commands and it just behaves like exit.

IMHO it's a bash bug for the inconsistent behavior of return depending on whether the main statement is in a function or not.

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

Output:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23
share|improve this answer
    
The lack of error verbosity may be undocumented. But the fact that return doesn't work from a top-level command sequence in a subshell, and in particular doesn't exit the subshell, is what existing documents have already made me expect. The OP could use exit 1 || return 1 where they are trying to use return, and should then get the expected behavior. EDIT: @herbert's answer indicates that toplevel return in subshell is functioning as exit (but only from the subshell). –  dubiousjim yesterday
1  
@dubiousjim Updated my script. I mean return in a simple subshell sequence should be asserted as a runtime error in any cases, but actually it's not when it occurs in a fucntion. This problem has also been discussed in gnu.bash.bug‌​, but there's no conclusion. –  yaegashi yesterday
    
Your answer is not correct as it is unspecified whether the while loop is in a subshell or is the foreground process. Regardless of the implementation of the actual shell, the return statement is in a function and thus legal. The resulting behavior however is unspecified. –  schily 1 hour ago

It is not a bug in bash but its documented behavior:

Each command in a pipeline is executed in its own subshell

The return instruction is valid being inside a function definition but being in a subshell as well, it doesn't affect its parent shell so the next instruction, echo, is executed regardless. It is nevertheless a non portable shell construction as the POSIX standard allows the commands composing a pipeline to be executed either in a subshell (the default) or the top one (an allowed extension).

Additionally, each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment. All other commands shall be executed in the current shell environment.

Hopefully, you can tell bash to behave the way your expect with a couple of options:

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here
share|improve this answer
    
Since return won't quit the function, wouldn't it make more sense if the shell just printed bash: return: can only `return' from a function or sourced script, instead of giving the user a false sense that the function might have returned? –  Teresa e Junior 23 hours ago
1  
I don't see anywhere in documentation said that the return inside subshell is valid. I bet that this feature was copied from ksh, the return statement outside function or sourced script behave like exit. I'm not sure about the original Bourne shell. –  cuonglm 21 hours ago
1  
@jlliagre: Maybe Teresa is confused about the terminology of what she's asking for, but I don't see why it would be "tricky" for bash to issue a diagnostic if you execute a return from a subshell.  After all, it does know that it's in a subshell, as evidenced by the $BASH_SUBSHELL variable.  The biggest problem is that this could lead to false positives; a user who understands how subshells work could have written scripts that use return in lieu of exit to terminate a subshell.  (And, of course, there are valid cases where one might want to set variables or do a cd in a subshell.) –  Scott 19 hours ago
1  
It seems to me that anybody who expects a return in a subshell in a function to return from the function (in the main shell process) doesn’t understand subshells very well.  Conversely, I would expect a reader who understands subshells to expect return in a subshell in a function to terminate the subshell, just as exit would. –  Scott 18 hours ago
1  
@Scott again, Being in a subshell is not enough to assert calling return should be considered an error. After the fork, both the parent and the child shells are technically inside the same function although each one in their own instance, that is the reason why return is accepted as a valid instruction in the subshell. –  jlliagre 15 hours ago

Per POSIX documentation, using return outside of function or sourced script is unspecified. So, it depends on your shell to handle.

SystemV shell will report error, while in ksh, return outside of function or sourced script behave like exit. Most other POSIX shells and schily's osh also behave like that:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

ksh and zsh didn't output because the last part of pipe in these shells was executed in current shell instead of subshell. The return statement affected the current shell environment which called the function, cause the function return immediately without printing anything.

In interactive session, bash only report the error but didn't terminated the shell, schily's osh reported the error and terminated the shell:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

(zsh in interactive session and output is terminal do not terminated, bash, yash and schily's osh reported the error but didn't terminate the shell)

share|improve this answer
    
It can be argued return is used inside a function here. –  jlliagre yesterday
1  
@jlliagre: Not sure what you mean, return was use inside subshell inside function, except ksh and zsh. –  cuonglm yesterday
1  
I mean being inside a subshell which is itself inside a function doesn't necessarily mean being outside that function, i.e. nothing in the standard states pipeline components are to be considered to be outside the function where they are located. This would deserve to be clarified by the Open Group. –  jlliagre 22 hours ago
3  
I think no. That's outside the function. The shell which called the function and the subshell which executed return are different. –  cuonglm 22 hours ago
    
I understand your reasoning which rightly explains the issue, my point is according to shell grammar described in the POSIX standard, the pipeline is part of the compound-list which is part of the compound-command which is the body of the function. Nowhere is it stated pipeline components are to be considered outside the function. Just like if I am in a car and that car is parked in a garage, I can assume I am in that garage too ;-) –  jlliagre 15 hours ago

I think that you got the expected behavior, in bash, each command in a pipeline is executed in a subshell. You can convice yourself by trying to modify a global variable of your function:

foo(){ x=42; : | x=3; echo "x==$x";}

By the way, the return is working but it return from the subshell. Again you can check that:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

Will output the following:

1
This should not be printed.

So return statement correctly exited the subshell

.

share|improve this answer
2  
Hence, to exit the function, use foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $? and you'll get a result of 2. But for clarity I would make the return 1 be exit 1. –  dubiousjim yesterday
    
By the way, is there some substantiation for the fact that all members of a pipeline (not all but one) are executed in subshells? –  Incnis Mrsi 18 hours ago
    
@IncnisMrsi: See jlliagre's answer. –  Scott 16 hours ago

The more general answer is that bash and some other shells normally put all the elements of a pipeline into separate processes.  This is reasonable when the command line is

program1 | program2 | program3

since programs are normally run in separate processes anyway (unless you say exec program).  But it can come as a surprise for

command1 | command2 | command3

where some or all of the commands are built-in commands.  Trivial examples include:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

A slightly more realistic example is

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

where the entire whiledodone loop is put into a subprocess, and so its changes to t aren’t visible to the main shell after the loop ends.  And that’s exactly what you’re doing — piping into a while loop, causing the loop to run as a subshell, and then trying to return from the subshell.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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