1. 25
    1. 4

      Today I learned about the “if myfunc” pitfall. I’m pretty fond of shell programming in general, in a punk/lomography kind of way, but that one scares me.

      1. 2

        Yeah it comes up on the bash mailing list with some frequency. I think it’s been true for 30 years or more, and every so often there is a big thread about it. Hence “groundhog day”.

        Here’s another one (where I participate but so do many other people):


        It’s weird shell folk knowledge.

        I add this to my scripts to flag it when run under OSH:

        shopt -s strict:all 2>/dev/null || true 


        (Right now Oil’s own scripts keep are run under bash as well.)

      2. 1

        +1 for


        I’ll henceforth use that to explain the essence of shell programming.

    2. 3

      # try sets _status, not $?

      why is that?

      1. 8

        Good question, I will put this in the FAQ !

        Basically the idea is that idiomatic Oil scripts never look at $? – it’s only used to trigger the errexit rule.

        Instead they invoke try and inspect _status when they want to handle errors

        Some detail: try is a builtin that takes a block, and every builtin has an exit code.

        The exit status of try is always zero, because if it returned a non-zero status for $?, then the errexit rule would trigger, and you wouldn’t be able to handle the error!

        So there are no special cases in the interpreter for try.

        For context, other builtins that take a Ruby-like block:

        • cd – save and restore current dir
        • shopt – save and restore option state
        • forkwait – “replacement” for subshell ( cd / ; echo $PWD ) which is rarely used in Oil
        • fork – replacement for &

        Generally the errors occur INSIDE the block, not outside, like:

        cd /tmp {
           cp zzzz /   # error happens here
           echo done 
        }  # not here

        Of course you can nest cd and try if you want.

        And the _ convention for _status is for globals / “registers” that the shell itself mutates:

        • _status, and the rarely used _pipeline_status, _process_sub_status
        • _match(1) for the match of a regular expression
        • _this_dir for imports relative to the current directory

        Related questions: https://www.oilshell.org/release/0.10.0/doc/error-handling.html#faq-on-language-design (why is there try but no catch)

        1. 2

          Good answer :)

          That’s pretty neat, being able to implement this without special cases.

          1. 1

            Glad it made sense! A related thing that I think will prove useful is that you don’t have to pass a block literal to try. Instead you can pass a variable like

            const myblock = &(cd /tmp; echo $PWD)
            try (myblock)   # typed argument to try

            I mentioned this in a thread a few months ago:


            I don’t expect this to be common in app code, but I can see it being useful in test frameworks and the like.