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.

  1. 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 ....[back]
Share

6 Comments

  1. it depends on shell – they can run the tail of pipe in subprocess or not

    in zsh foo fails, in dash foo prints 1

  2. 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 }

  3. 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.

  4. 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.

  5. 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.

  6. 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).”

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>