[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"post-content-bash-strict-mode-set-euo-pipefail":3},"\u003Cp>Last week my deploy script told me everything was fine. It wasn't.\u003C\u002Fp>\n\n\u003Cp>The build had failed about forty lines in, but bash just kept going. It ran the rest of the script against a half-built artifact, uploaded the mess, printed \"Deploy complete!\" and exited 0. My CI pipeline turned green. Production was broken for an hour before anyone noticed.\u003C\u002Fp>\n\n\u003Cp>That's the day I stopped trusting bash to fail on its own.\u003C\u002Fp>\n\n\u003Ch2>Why Bash Doesn't Fail (Unless You Ask It To)\u003C\u002Fh2>\n\n\u003Cp>Here's the thing nobody tells you when you start writing shell scripts: bash doesn't stop when a command fails. It just keeps going. Every command's exit status gets checked by \u003Cem>you\u003C\u002Fem> — or it doesn't get checked at all.\u003C\u002Fp>\n\n\u003Cp>Most of us don't check. I sure didn't.\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">#!\u002Fbin\u002Fbash\ncd \u002Fvar\u002Fwww\u002Fmy-app\nnpm run build\nrsync -avz dist\u002F user@server:\u002Fvar\u002Fwww\u002Fmy-app\u002F\necho \"Deploy complete!\"\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>If \u003Ccode>npm run build\u003C\u002Fcode> fails, that script happily rsyncs the old dist folder and prints success. The exit code is whatever \u003Ccode>echo\u003C\u002Fcode> returns, which is always 0. Your script \"works\" — right up until it doesn't.\u003C\u002Fp>\n\n\u003Ch2>The Three Flags That Fix It\u003C\u002Fh2>\n\n\u003Cp>There's a one-line fix most of my scripts were missing. It's called strict mode, and it looks like this:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">#!\u002Fbin\u002Fbash\nset -euo pipefail\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Three flags, one line. Changes everything about how your script behaves.\u003C\u002Fp>\n\n\u003Ch3>set -e: Stop on Error\u003C\u002Fh3>\n\n\u003Cp>\u003Ccode>set -e\u003C\u002Fcode> makes the script exit immediately when any command returns a non-zero status. That deploy script above? With \u003Ccode>-e\u003C\u002Fcode>, a failed build kills the script right there. No rsync, no fake success message, no broken production.\u003C\u002Fp>\n\n\u003Cp>Sounds perfect. It's not, and I'll get to why in a second.\u003C\u002Fp>\n\n\u003Ch3>set -u: Catch Unset Variables\u003C\u002Fh3>\n\n\u003Cp>\u003Ccode>set -u\u003C\u002Fcode> treats a reference to an unset variable as an error. Without it, a typo like \u003Ccode>${ENVIRNOMENT}\u003C\u002Fcode> silently expands to an empty string. Your script runs, does nothing, and you spend an hour wondering why.\u003C\u002Fp>\n\n\u003Cp>With \u003Ccode>-u\u003C\u002Fcode>, bash screams at you the moment you touch a variable that doesn't exist. It's annoying at first. It's worth it.\u003C\u002Fp>\n\n\u003Ch3>set -o pipefail: The Pipeline Trap\u003C\u002Fh3>\n\n\u003Cp>This one's sneaky. \u003Ccode>set -e\u003C\u002Fcode> doesn't catch failures inside a pipeline — only the exit code of the last command matters.\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">set -e\nnpm run build | tee build.log   # build fails, but tee exits 0\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>The build fails, \u003Ccode>tee\u003C\u002Fcode> succeeds, and \u003Ccode>-e\u003C\u002Fcode> sees a zero. \u003Ccode>pipefail\u003C\u002Fcode> fixes that by making the pipeline return the rightmost non-zero exit code. Now a failure anywhere in the pipe fails the whole thing.\u003C\u002Fp>\n\n\u003Ch2>Where Strict Mode Bites Back\u003C\u002Fh2>\n\n\u003Cp>Look, I'm not going to pretend strict mode is all sunshine. It has sharp edges, and you'll hit them in the first week.\u003C\u002Fp>\n\n\u003Cp>The big one: \u003Ccode>set -e\u003C\u002Fcode> doesn't play nice with commands you \u003Cem>expect\u003C\u002Fem> to fail. Checking if a directory exists is fine — \u003Ccode>-e\u003C\u002Fcode> is disabled inside \u003Ccode>if\u003C\u002Fcode> conditions. But a bare \u003Ccode>grep\u003C\u002Fcode> that finds nothing exits 1, and \u003Ccode>-e\u003C\u002Fcode> kills your script. The fix is \u003Ccode>|| true\u003C\u002Fcode> when you genuinely don't care:\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">grep \"TODO\" src\u002F || true\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>Or wrap a block in \u003Ccode>set +e\u003C\u002Fcode> and \u003Ccode>set -e\u003C\u002Fcode> when you want it to survive. Ugly, but it works.\u003C\u002Fp>\n\n\u003Ch2>What Every Script I Write Starts With Now\u003C\u002Fh2>\n\n\u003Cpre>\u003Ccode class=\"language-bash\">#!\u002Fbin\u002Fbash\nset -euo pipefail\nIFS=$'\\n\\t'\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>The \u003Ccode>IFS\u003C\u002Fcode> line is a bonus — it stops word-splitting on spaces and tabs, which is a whole other class of bug. Filenames with spaces stop breaking your \u003Ccode>for\u003C\u002Fcode> loops.\u003C\u002Fp>\n\n\u003Cp>That's it. Two lines. They've saved me more times than I can count, and the deploy script that broke production last week? It's got them now. I keep a copy of my go-to script template in \u003Ca href=\"\u002Fsnippetark\u002F\">Snippet Ark\u003C\u002Fa> so I never retype it — and so the next script I write starts safe instead of starting from scratch.\u003C\u002Fp>\n\n\u003Cp>Strict mode won't make your scripts perfect. But it'll make them honest. They'll fail loudly when they fail, instead of smiling at you while they break things.\u003C\u002Fp>\n",1787363539308]