Or go the other direction: stop trying to do fancy things and write simpler code that avoids errors.
#!/bin/sh
[ "${DEBUG:-0}" = "1" ] && set -x
set -u
foo="$( my-external-program | pipe1 | pipe2 | pipe3 )"
if [ -z "$foo" ] ; then
echo "Error: I didn't get any output; exiting!"
exit 1
fi
echo "Well I got something back. Was it right?"
if ! printf "%s\n" "$foo" | grep -q -E 'some-extended-regex' ; then
echo "Error: '$foo' didn't match what I was looking for; exiting!"
exit 1
fi
echo "Do the thing now..."
A lot of programs will either produce valid output as STDOUT, or if they encounter an error, not produce STDOUT. So for the most part you just need to 1) look for any STDOUT at all, and then 2) filter it for the specific output you're looking for. For anything else, just die with an error. If you need to find out why it didn't run, re-run with DEBUG=1.
Advanced diagnosis code won't make your program work better, but it will make it more complicated. Re-running with tracing enabled works just as well 99% of the time.
I actually tried rewriting this in Python, but gave up since Python's startup latency is atrocious if you have even a few imports (and using a socket to a pre-existing server is fundamentally unable to preserve enough process context related to the terminal). Perl would probably be a better fit but it's $CURRENTYEAR and I've managed to avoid learning Perl every year so far, and I don't want to break my streak just for this.
The Bash code is not only fast but pretty easy to understand (other than perhaps the header, which I never have to change).
I think you should be able to get rid of the is-oil part, because set -E was implemented last year
$ osh -c 'set -E; set -o |grep errtrace'
set -o errtrace
I'd be interested in any bug reports if it doesn't behave the same way
(The Oils runtime supports FUNCNAME BASH_SOURCE and all that, but there is room for a much better introspection API. It actually has a JSON crash report with a shell stack dump, but it probably needs some polish.)
Yes. This also means that if you use a third-party shell library which uses “trap” internally (like shunit2), you can’t use “trap” in your own script at all.
Take care that set -o pipefail will not work on older dash (including IIRC the current Ubuntu LTS), and neither will set -o pipefail || true if set -e is in effect. (For some reason that I’m too lazy to investigate, a failing set invocation will crash the script immediately rather than proceed into the second branch.) The best I could think of to opportunistically enable it was to use a subshell:
if (set -o pipefail 2>/dev/null); then set -o pipefail; fi
Or you can just target bash, I guess.
(I rather dislike shellcheck because it combines genuine smells with opinions, such as insisting on $(...) instead of `...`. For the same reason, with Python I regularly use pyflakes but can’t stand flake8. But to each their own.)
Well, maybe. But using `set -euo pipefail` here does not make it any worse as far as i understand? The script still does broken things, but the more correct way to be safe is not broken by set -euo pipefail
It is not that `set -e` is bad, it is that bash is a bit weird in this area and you have to know when things eat errors and when they don't. This is not really changed by `set -e`: you already had to know them to make safe code. `set -e` does not wave a magic wand saying you don't have to understand bash error control.
But having `set -e` is almost universally better for people who do not understand it (and I would argue also for people who do). Without it you are responsible for implementing error handling on almost every line.
As other have already said: this is one of those things that generally pushes me to other languages (in my case often Python), as the error handling is much more intuitive, and much less tricky to get right.
Is that a safety point of view? Is shell supposed to be input-safe? I may have limited shell skills but it doesn’t seems like it’s designed to be safe.
(I'm sure this is lovely Bash, but for all the people who rejected Perl for its modem line noise vibe...what do ya think of this?)
As an aside, I actually wonder if Bash's caller() was inspired by Perl's.
There is also Carp and friends, plus Data::Dumper when you not only need the stack trace but also the state of objects and data structures. Which is something that I don't think Bash can really do at all.
reply