bashstyle
bashstyle copied to clipboard
Why to always use "if [[ ... ]]; then; fi" instead of a conditional statement
Statements joined with &&
don't end a script even when it's running with set -e
, but if they're the last statement in your file, their non-zero exit code will be your script's exit code (think of it like an implicit return
), and if that script was getting called by something with set -e
, the whole house of cards will come tumbling down.
In other words, while it's an easily-avoided ledge, it still needs a railing, because a single careless bump will send you way the hell crash tumbling down.
I used to remember this pitfall, but then I went away for a few months, and when I came back I couldn't remember why I didn't do this, and I did it all over my code, and immediately got bitten by this. In general, if you're using set -e
, only use tests to test things.
Like, my boilerplate header in Plushu is
set -eo pipefail; [[ -n "$PLUSHU_TRACE" ]] && set -x
and right now I'm considering changing that, because if the rest of the file is empty and tracing isn't enabled, this will crash its caller. (And then you have a wonderful Heisenbug where the problem only manifests itself when debugging isn't on!)
If the rest of the file is empty? When would you have a script that has nothing but this? Otherwise, it seems totally reasonable to understand this behavior. I can imagine maybe a tip to remind people that this happens, but I don't think it's worth a rule against conditional expressions.
If the rest of the file is empty? When would you have a script that has nothing but this?
When I'm creating stub scripts before filling them in.
When would you have a script that has nothing but this? Otherwise, it seems totally reasonable to understand this behavior. I can imagine maybe a tip to remind people that this happens, but I don't think it's worth a rule against conditional expressions.
Okay, so in the middle of responding to a long-standing issue that's been sitting in the queue for weeks/months/years, you're refactoring a file that does this at the end:
[[ -f "$EXTRA_STUFF_FILE" ]] && cat "$EXTRA_STUFF_FILE"
printf "FINAL_VAR=%q\n" "$(finalization_code)"
To do this instead:
printf "FINAL_VAR=%q\n" "$(finalization_code)"
[[ -f "$EXTRA_STUFF_FILE" ]] && cat "$EXTRA_STUFF_FILE"
Is this specific combination of caveats (that Bash exits with the last exit code normally, and that set -e
doesn't exit with &&
ed expressions), which you last thought about four months ago, going to occur to you right now, in this exact moment where you're preoccupied with a design refactor? Or is it going to come to you later, after an hour of debugging with various env vars cleared and set, clobbering configs, turning it off and back on again, spinning it upside down and shaking it, flipping the "magic" switch to "more magic", and just generally not recognizing why the calling script is suddenly tanking out of nowhere?
(Hint: I went through this 36 hours ago. It's the second one.)
Curious about your debugging process for this ... if you had tracing on, wouldn't that lead to this line? I guess not if your outer scripts suppress the nonzero exit? I'm imagining that -f
failed causing it to exit, bubbling up to stop the command there, and if you're tracing it would show you that line and you'd realize what's going on.
Except tracing doesn't show any difference between tests inside conditions and tests outside them, so all you see is a series of -f
, -e
, -z
, and/or -n
tests, that suddenly ends in the middle of the thread of execution for no apparent reason. And then, yeah, you can go into the file, and step backwards through your logic to find the exact combination of circumstances that led to this code path being taken, and then see that this was the last command that was executed and maybe understand that this is what happened if you're already super well versed in every weird thing Bash does, including a behavior you will never otherwise see because set -e
doesn't normally allow expressions that return non-zero.
Or you could just take the ten extra keystrokes it takes to type if ; then ; fi
in the first place and never even see this problem because all your mid-script exit codes are 0.
Not to mention always using if
is, stylistically, more consistent (and isn't consistent style the name of the game here?)
Okay, I'm starting to see the light...