Shell was among the first things I got familiar with when I was introduced to Linux. It’s not a typical programming language, primarily due to lack of easy-to-use high-level data structures such as hashes and arrays (anticipating your objection to this - note I said “easy-to-use”). This may explain why I often get funny looks from folks when I mention that I use shell quite a bit, often in quite non-trivial systems.
And here are my reasons.
Shell scripts are excellent in managing their memory and one has to try real hard to cause a shell script to leak memory. This makes shell a very convenient tool for long running processes, supervisors in multiple-workers models, daemons and so on. There is an easy explanation for this. In shell, there are only a handful of built-in primitives - everything else is an external command, which gets started and then finishes before giving control back to your script. If there is a memory leak in that command, it won’t damage your calling script and will usually be insignificant because it will return quickly.
This is a double edged sword, and you need to be careful how you exploit this “weakness.” This feature allows me to write compact code which is easy to understand without enclosing every single command in “try… except”. For naysayers, I would like to point out that a strict mode exists, where every error is treated as fatal and causes the script to exit (set -e).
In general, not all unforeseen error conditions warrant a crash, like you get in Python or Ruby when an unhandled exception gets propagated all the way to the top. If a problem is transient, it may be better to ignore it temporarily.
To assure a Ruby or Python script doesn’t crash on some unforeseen transient problem, many people often end up enclosing their entire program in a wildcard try… except block to catch any exception - but to me this approach is dangerous, even though I sometimes end up using it myself.
If you are writing a daemon process to perform some action in a loop, shell is often by far the most stable alternative.
When Not To Use Shell
My personal rule of thumb is don’t use shell when you expect to need high-level data structures like hashes or arrays beyond what for loop can give you, or when you can see potential for code reuse following OOP patterns like inheritance, or when your program needs to participate in some orchestration schemes that go beyond creating and removing files on the filesystem.
I wouldn’t overlook shell if I were you.