So, it depends on the context. "Organization" is a very broad term. If the team you are in is familiar with the shell, and the shell is the right tool for the job, there isn't that much of a problem.
I'm not arguing for writing large shell scripts when there are better alternatives. But, sometimes, it's the right choice and it can be written properly. And even if it isn't... 500 lines of poorly written shell aren't that many in any case, and they aren't that bad compared to 5000 lines of poorly written Python for example.
And the problem with bash is similar to C: many people think they can write correct C code, but as it has been shown plenty of times, that doesn’t really work out in the end. Languages have different tradeoffs and bash really has no redeeming qualities, imo.
Because Bourne shell is not a Python-class language like Ruby or perhaps even Scheme or AWK are. Things that are easy in shell can be hard in Python and vice versa: awkward wrangling of subprocess.Popen in Python can be a single line of shell; simple tree munging with lxml in Python can easily be a hundred or more lines of wasteful xmlstarlet invocations in shell. Parallel processing, even without the heavy artillery of make or GNU parallel, is easy in shell but hard in Python. Structured data is hard in shell but easy in Python.
(Similarly, I wouldn’t use ML or Haskell to write a web scraper, and I wouldn’t use Python to write a compiler.)
Perhaps a better way to put it would be that sloppy concurrency is easy. This doesn’t sound all that good, but it’s easy enough that I find myself parallelizing one-off things in shell that would be such a hassle to do nonsequentially in Python (compared to the magnitude of the problem) that the option wouldn’t even surface in my brain. (If I rewrite the shell script in Python later, the rewritten version is often sequential.)
The shell error handling story for “run stream processing steps in lockstep” parallelism is much better than for “spawn a bunch of identical children” parallelism, though, and that’s often enough.
#!/bin/bash -eux -o pipefail
TMPDIR=$(mktemp -d tempdirXXXXX)
trap “rm -rf $TMPDIR” SIGINT SIGTERM ERR EXIT
(
cd $TMPDIR
foo | grep x > foo.out &
bar > bar.out &
baz $TMP | tee baz.out &
)
wait
tar -cf - -C $TEMPDIR | zstd -6 -z - | pv | ssh server.com bash -c “cat - > out.tag.zstd” ... | ssh server.com dd of=out.tag.zstd $ shellcheck myscript
Line 1:
#!/bin/bash -eux -o pipefail
^-- SC2096 (error): On most OS, shebangs can only specify a single parameter.
Line 4:
trap "rm -rf $TMPDIR" SIGINT SIGTERM ERR EXIT
^-- SC2064 (warning): Use single quotes, otherwise this expands now rather than when signalled.
Line 6:
cd $TMPDIR
^-- SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean: (apply this, apply all SC2086)
cd "$TMPDIR"
Line 9:
baz $TMP | tee baz.out &
^-- SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean: (apply this, apply all SC2086)
baz "$TMP" | tee baz.out &
Line 13:
tar -cf - -C $TEMPDIR | zstd -6 -z - | pv | ssh server.com bash -c "cat - > out.tag.zstd"
^-- SC2086 (info): Double quote to prevent globbing and word splitting.
^-- SC2153 (info): Possible misspelling: TEMPDIR may not be assigned. Did you mean TMPDIR?
Did you mean: (apply this, apply all SC2086)
tar -cf - -C "$TEMPDIR" | zstd -6 -z - | pv | ssh server.com bash -c "cat - > out.tag.zstd"Came here to say this.
At one end you have things like Haskell, OCaml, Rust, (or if you go really extreme Dafny etc.).
At the other end you have Bash.
There's no way a 500 line Bash script would require 5000 lines of Python.
Also, to understand someone else’s python, you have to read at least 10x more lines of line noise, so it somehow manages to be less maintainable than shell.
Concrete example: Every python code base I’ve encountered has some function named exec that poorly reimplements shell job control and argument passing. As the python code bases grow, they end up with more than one of these, all of which have different bugs.
I usually discover this about 6 hours after the script has been sent to me, since someone who has heard the words “supply chain vulnerability” locked down whatever machine I need to run the script on.
The last ten times this has happened (at multiple companies, with multiple teams) it ended up being faster to figure out what shell commands the python script wanted to invoke, port a few things to jq and then reimplement the rest in a mix of shell and perl.
The result is always 1-10% the lines of code of the python, and actually runs without downloading an entire python distribution, dependency manager, and hundreds of broken packages at runtime.
Completely untrue. You're conflating static typing with strong typing. And pytest is a great test framework.
And you're recommending shell for better typing and testability? The language where everything is a string and there are no proper arrays? You have to be trolling.
pytest is terrible, at least from how I’ve seen people use it. It hides the diagnostic error explaining why the test failed in a random place. Its output is not parseable. If you have a long running test that times out, then it buffers the stdout and stderr so you have to wait for the fallback test time out every. single. time.
Mypy has had strict Optional checking on as default since 2018, almost entirely eliminating any None-related errors, meaning it is actually stricter and safer than Java or other traditionally considered static languages in that regard, (bar any dynamic misuse in deserialization boundaries). Assuming you use mypy of course, but that's a given today imo, like using shellcheck is for sh.
It’s untyped, untestable, arcane, and unmaintabale. Even the link says don’t do this. Just don’t.
Congrats on having the chops (and neckbeard) to pull this off. If you want to do it in a personal project, more power to you. But it doesn’t scale at organizations.