r/bash • u/ClickOk5811 • 12h ago
set -e silently does nothing inside a function called from an if, and I only found out in production
I had a cleanup script that always started with the usual set -euo pipefail. One day it reported success while half the work hadn’t happened. Here’s a stripped-down version of what bit me:
#!/usr/bin/env bash
set -euo pipefail
cleanup_old() {
rm "$1/old.log" # fails if the file is missing
echo "cleaned $1"
}
if cleanup_old /tmp/nope; then
echo "all good"
fi
Output:
rm: cannot remove '/tmp/nope/old.log': No such file or directory
cleaned /tmp/nope
all good
The rm fails, but the function keeps going, and the echo at the end gives it a zero exit status. The bash manual says -e is ignored for any command in a context where the exit status is being tested (if, while, &&/|| lists, !). That also applies to everything inside a function called from such a context. set -e at the top of the file doesn’t help, and nothing warns you.
What I do now:
cleanup_old() {
rm "$1/old.log" || return 1
echo "cleaned $1"
}
The explicit || return is noisier, but it works regardless of how the function gets called. For anything where partial success is dangerous, I also stopped using functions as if conditions and run them as plain statements, checking $? or using || on the call site deliberately.
Related one that bit me the same week: local out=$(some_cmd) always returns the exit status of local, not some_cmd. Splitting it into local out; out=$(some_cmd) fixes it.
I’ve been reading a lot of generated and copy-pasted scripts lately, and nearly all of them treat set -euo pipefail as a safety net. After this, I think of it as a default that works in the simple cases and quietly stops working in exactly the places where functions get composed.
For those who write larger bash scripts: do you rely on set -e at all, or do you handle errors explicitly everywhere and keep -e only as a backstop?
