When I was done I looked over it and thought: this is a nice piece of code right now. It's well documented. It's self contained. It passes my tests. I could maintain this code.
But it made use of quite a few bash'isms: arrays and so forth. I wasn't confident in the ability of my colleagues to keep it in good state.
So I rewrote it in Python. The Python version ended up being a little longer, but that was mostly due to formatting difference. The Python version in addition I could reformat with black and isort, I added type annotations so I could test it with mypy.
(I had formatting the shell version with shfmt and checked it with shellcheck, but still.)
The Python version is much easier to read, and I think will be more maintainable.
I consider myself competent in both languages so it's a courtesy to my colleagues if nothing else to ship the Python version.
But now that I can expect python3 to be available on the systems I care about, and with the fairly comfortable ergonomics of subprocess.run, I find myself using Python in places I would've previously used bash.
A good heuristic is the moment a bash script needs to use curl + jq, it's time for python. Especially on macOS it's nice having things like plistlib so I can avoid `defaults` and `PlistBuddy`.
Bash is just too damn fast to whip things up in (even at the risk of endless footguns). Build up a pipeline of commands, translate into script, done. It's powerful and can scale if common conventions and good practices are used.
For easier debugging I always add a simple flag along the lines of: if $1 = -v then set -x; shift
Obviously shellscripts are a terrible or highly suspect idea when production-grade transactional integrity is critical, but in many cases the swiftness of shell development outweighs the cons when the goal is to Get Things Done.
The shell is a pretty sweet interface.
Python is pretty safe, but your code would have to work with both 2 & 3 and not pull in any external dependencies. And recommended practices have changed so drastically on how to install Python over the years that a random dev could have theirs in a fairly weird state.
Perl would work, but hacked-together Perl has the similar drawbacks to hacked-together Bash, especially when it's me doing the coding.
Ruby has problems similar to Python.
Make & awk have problems similar to Perl & Bash.
Therefore my latest rust package contains bash scripts.
Python sucks for building processing pipelines.
How many lines of Python code would you need to implement this?
paste -d \\n <(foo …) <(bar …) | while read -r f && read -r b; do
baz “$f” “$b”
done
Mind you, `foo` and `bar` run in parallel.replace 200+ bash script with multiple subroutines in python? yes
from subprocess import Popen, PIPE, run
with Popen('foo', stdout=PIPE) as foo, Popen('bar', stdout=PIPE) as bar:
for f, b in zip(foo.stdout, bar.stdout):
run(['baz', f, b])
(there is a slight difference in behaviour)