Bash is simply insane
What do you think the following scripti 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.
- 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, likecat myfile | while read ....[back]
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
returnwill be surprised.Oh well, it’s just another argument to avoid writing anything but very short scripts in shell.
Don’t avoid writing scripts in shell. Just avoid writing scripts for bash
zsh behaves exactly as I would expect in the above (and many other) cases.
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).”