What do you think the following script ((The script is somewhat artificial, who would ever use the construct true | while ...
? I’ve used this just to show the point while keeping the examples short. Feel free to replace that part with something more useful, like cat myfile | while read ...
.)) will print?
foo() {
true | while true; do
false
rc=$?
if [ $rc -eq 1 ]; then
return 1
fi
done
echo $?
return 0
}
foo || echo "Failed foo"
Run it and see. I suspect everyone but the script gurus out there will be surprised.
What about this script then?
bar () {
local rc=0
true | while true; do
false
rc=$?
if [ $rc -eq 1 ]; then
return 1
fi
done
echo $?
echo $rc
return 0
}
bar
Surprised again?
I guess this means that scoping in bash is somewhat more complicated then I would have ever guessed.
it depends on shell - they can run the tail of pipe in subprocess or not
in zsh foo fails, in dash foo prints 1
That’s because of that ‘|’ I guess. It have to run it in separate process and “while” should be executed also in other process (or emulated somehow that) to connect stdin with stdout of “true”.
You can get “proper” (modified in the same sh-process as “while”) value of “rc” by:
true | {
while true; do
# ... (return will get out of { }, so probably break instead)
done
echo $?
echo $rc
}
It’s not just Bash, it’s all Bourne-like shells. I knew what was going to happen, but probably I’ve been doing too much shell programming ;)
The explanation is actually pretty simple. There’s two things at play here: pipes force subshells, and pipe’s return status is the tail’s return status.
i.e. “echo | while read; do :; done” == “echo | (while read; do :; done)” and “false | true” returns 0, “true | false” returns 1.
Nikolay Orlyuk, Well, that doesn’t do much to help the problem. There are still no clear visual clues that a sub-shell is used, and lexical scoping doesn’t exist.
ephemient, Yes, it’s simple to explain, but there are no clear visual clues that a sub-shell is used. That’s what bothers me about it. Anyone coming from a programming lanugage with proper lexical scoping and sane semantics of return
will be surprised.
Oh well, it’s just another argument to avoid writing anything but very short scripts in shell.
It’s less than ideal all right, but the bash manual does say “Each command in a pipeline is executed as a separate process (i.e., in a subshell).”