This is an excellent resource. I'd like to re-iterate the author's recommendation to use shellcheck:
I also like using shfmt on top of that.
(I run them via pre-commit[1] hooks[2,3].)
FWIW, I find writing POSIX-compliant shell rarely necessary. In my 25 years of writing shell scripts, they've nearly always been for automation on a specific OS where portability is not a concern. Arrays are really nice to have. One place I like to use them is for making long commands easier to read. e.g.
cmd=(
some-command
--some-long-option
--another-long-option
--an-option-with-a-value="$value"
)
"${cmd[@]}"
This avoids having to use backslashes so you can comment in-between lines or at the end of any line.I also almost always prefer the self-documenting long-option to single-letter switches when writing shell scripts. Those single-letter options are to save you typing on the command-line. There's no reason to use them in a script.
When I do write a script for more than one OS (typically macOS and Linux), I have bash on both systems, so I use "#!/usr/bin/env bash" and take care to use only the features that the older version of bash that macOS ships with supports.
Where I have to be more careful is in utilities like find, sed, etc which can differ quite a bit between BSD and Linux.
I've written hundreds of scripts for Busybox for Windows (https://github.com/rmyorston/busybox-w32), Linux bash and macOS zsh. POSIX compatibility means a lot.
WRT the specific concerns of arguments: if you are doing something complicated, use Python. ChatGPT writes it better anyway.
In my experience, more complex scripting is reinventing Gradle, Ansible, Terraform, YAML & similar.