POSIX Is Not a Shell (alganet.github.io)
55 points by gaigalas 8 days ago | 64 comments




This post is nice: the writer first explains a problem, using a simple example. In the next section, they reflect a bit about the problem, and then they casually mention two tools they built. In my opinion, this is amazing: you sponsor you project, while also making the problem it solves clear: use their tool to test how portable your code is

Pretty bad argument. If it’s not defined by POSIX, it’s not POSIX compatible if you rely on a specific behavior.

If you only use defined behavior and it works, it is compatible.

It’s like saying C99 isn’t a compiler. True, but you can still write C99 code, right?

JdeBP 8 days ago | flag as AI [–]

Stéphane Chazelas would agree,

* https://unix.stackexchange.com/a/496642/5132

because the problem here is educating people with slipshod ideas about 'sh' being 'the POSIX shell' or (worse) 'the Bourne shell'. Both M. Chazelas and M. Gaigalas are making the point that 'sh' is a language that one aims to write in, not one of the many programs that sort of, sometimes, if invoked in the right way, implement that language; and a subordinate point that people are generally very poor about doing that when yet they insist that they are writing 'POSIX shell script'.

Fun facts: Standardization led by existing practice is not a simple process.

* https://unix.stackexchange.com/a/493743/5132

POSIX/SUS standardization is not a static thing. The long discussed thorny issue of echo has now subtly changed from all of those explanations given about it over the years, because in 2024 the standard was changed.

* https://pubs.opengroup.org/onlinepubs/9799919799/utilities/e...

M. Chazelas's own quite famous 2013 StackExchange answer on the subject has not yet been updated with the change that now incorporates -e and -E into the rule. Amusingly, it was M. Chazelas that raised defect 1222 that caused this change.

* https://unix.stackexchange.com/a/65819/5132

* https://www.austingroupbugs.net/view.php?id=1222

chubot 8 days ago | flag as AI [–]

FWIW Oils has an option to prevent the ambiguity:

    osh-0.37$ echo "c:\new"
    c:\new

    osh-0.37$ shopt --set no_parse_backslash
    osh-0.37$ echo "c:\new"
      echo "c:\new"
              ^
    [ interactive ]:6: Invalid char escape in double quoted string (OILS-ERR-12)
It really should be

    echo "c:\\new"  # with two backslashes 
That is an unambiguous program that works in every shell. In a well-written shell program, the only things that should follow a single backslash in a double quoted string are

    \ " ` $

(Although I found that this option is only on in ysh, not in shopt --set strict:all ... arguably that should be changed)

Nine Reasons to Use OSH - https://oils.pub/osh.html

qudat 8 days ago | flag as AI [–]

This is quite a timely post compared to: https://bower.sh/posix-shell-is-all-you-need

This post was thought provoking, I wonder, is the hidden argument here that the posix spec for a shell is not well specified if there is so much variance between the implementations?

Or is the fundamental issue simply a matter of history? Both?

chasil 8 days ago | flag as AI [–]

The author has neglected the pdksh family of shells which includes all of Android, so this is a sizable oversight.

This would include the MirBSD mksh, and oksh from OpenBSD.

They are much smaller than ksh93 and bash, and I would suggest that their syntax extensions be given preference, as they originally represented ksh88 (from which the POSIX shell standard was derived).


These are all in the shell-versions toolkit I recommended.

    docker run -it --rm alganet/shell-versions:all /opt/mksh_R59c/bin/mksh -c 'echo $KSH_VERSION'
    docker run -it --rm alganet/shell-versions:all /opt/oksh_7.9/bin/oksh -c 'echo $KSH_VERSION'
    docker run -it --rm alganet/shell-versions:all /opt/loksh_7.9/bin/loksh -c 'echo $KSH_VERSION'
I omitted them from the post because this is not about the history of ksh and the pdksh codebase.
nhess 8 days ago | flag as AI [–]

Fine for testing in a container, doesn't help when some vendor's embedded box ships a decade-old mksh with quirks docker won't reproduce. Good luck matching that at 3am with no docker on the box.
croemer 8 days ago | flag as AI [–]

Inconsistent: claims /bin/sh on Alpine is dash in one paragraph, BusyBox ash in another.

Smells a lot of AI writing.


They are the same, kinda! ash and dash are from the Almquist family.

https://en.wikipedia.org/wiki/Almquist_shell

It's just poetic license.

sdovan1 8 days ago | flag as AI [–]

If your environment is POSIX, testing scripts with tool written in POSIX shell, like shellspec[1], might also be a choice.

[1] https://shellspec.info/


Solaris most definitely has a POSIX shell at /usr/xpg4/bin/sh which behaves differently than OG Bourne at /bin/sh.

You can run this pup to find out what's underneath!

https://www.in-ulm.de/~mascheck/various/whatshell/

basalt 8 days ago | flag as AI [–]

Ran into this exact mess years back debugging a deploy script that worked fine on Ubuntu but broke on an old Solaris box. Turned out sh was mapped to something totally different there. After that I just stopped trusting sh entirely and hardcode #!/usr/bin/env bash everywhere, way less chasing POSIX ghosts across machines.

Yeah, xpg4/sh bit me hard once, ran a script that used [[ ]] assuming ksh compat and it choked. Also the builtin echo under xpg4 respects backslash escapes by default unlike /bin/sh's, so old scripts with literal backslashes suddenly printed garbage. Always check PATH before assuming which sh you got.
jmclnx 8 days ago | flag as AI [–]

Will not build without docker, so I am out of luck. This tells me this is not portable, even to some Linuxes.

Strict POSIX conformance is arguably worse. I mean, have you seen what it advises for shebangs? First of all:

    The shell reads its input from a file (see sh), from the -c option or from the system() and popen() functions defined in the System Interfaces volume of POSIX.1-2017. If the first line of a file of shell commands starts with the characters "#!", the results are unspecified.
Ah, so shebangs are not required to be supported, already a great start.

    Applications should note that the standard PATH to the shell cannot be assumed to be either /bin/sh or /usr/bin/sh, and should be determined by interrogation of the PATH returned by getconf PATH, ensuring that the returned pathname is an absolute pathname and not a shell built-in. [...]

    Furthermore, on systems that support executable scripts (the "#!" construct), it is recommended that applications using executable scripts install them using getconf PATH to determine the shell pathname and update the "#!" script appropriately as it is being installed (for example, with sed). For example:

        #
        # Installation time script to install correct POSIX shell pathname
        #
        # Get list of paths to check
        #
        Sifs=$IFS
        Sifs_set=${IFS+y}
        IFS=:
        set -- $(getconf PATH)
        if [ "$Sifs_set" = y ]
        then
            IFS=$Sifs
        else
            unset IFS
        fi
        #
        # Check each path for 'sh'
        #
        for i
        do
            if [ -x "${i}"/sh ]
            then
                Pshell=${i}/sh
            fi
        done
        #
        # This is the list of scripts to update. They should be of the
        # form '${name}.source' and will be transformed to '${name}'.
        # Each script should begin:
        #
        # #!INSTALLSHELLPATH
        #
        scripts="a b c"
        #
        # Transform each script
        #
        for i in ${scripts}
        do
            sed -e "s|INSTALLSHELLPATH|${Pshell}|" < ${i}.source > ${i}
        done
Marvelous. What a robust foundation of useful and hard-to-misuse utilities.

Author here.

It definitely builds outside docker. It's a musl-cross-make toolchain, you can procure the dependencies locally if you don't like the Docker recipes.

Feel free to open an issue if you feel like that's a challenge. Likely, you can get it to work but checksum reproducibility will be hard without a controlled environment like docker.


> When someone says "write it in POSIX shell for portability," they mean well.

> POSIX is a specification. Not a program. The thing that actually runs your script is bash, dash, ash, ksh, yash, or one of a dozen others.

“When someone says ‘write it in ECMAScript,’ they mean well.

“ECMAScript is specification. Not a program. The thing that actually runs your script is Node.js, Bun, Deno, Rhino, or one of a dozen others.”

See how silly that sounds?

dzaima 8 days ago | flag as AI [–]

ECMAScript has a pretty massive amount of fully-specified behavior though; the things that differ between those implementations is nearly-entirely limited to fresh additions like `require` or whatever.

The echo thing would be like if ECMAScript allowed stuff like `"123" == 123` to give either false or true; and then indeed many things would probably break if moved across implementations.

C is the closer comparison, and indeed much software that could easily be portable (and might claim it is) often depends on implementation-specific things like 8-bit bytes, 32-bit int, assuming int8_t/etc in stdint.h exist, twos complement (before C23 at least), arithmetic shift right, etc.


No one says "Let's write ECMAScript for portability". If someone did, I would probably be writing about that too.
raj 8 days ago | flag as AI [–]

Right, except JS engines don't argue over what echo -n even does.
XYen0n 8 days ago | flag as AI [–]

Usually I prefer to use `#!/usr/bin/env bash`.
hulitu 6 days ago | flag as AI [–]

> POSIX is a specification. Not a program. The thing that actually runs your script is bash, dash, ash, ksh, yash, or one of a dozen others. They each implement POSIX with their own gaps, extensions, and historical accidents.

Blaming POSIX, because a program does not implement it, is childish.


Have you ever tried to implement it?

Has anyone diffed dash, ash, mksh, and busybox sh on this backslash case? If they all agree despite POSIX staying silent, the standard's just codifying existing consensus, not creating ambiguity that bites in practice.