SecondBrain/reference/bash-pipefail-silent-exit-g...

60 lines
2.5 KiB
Markdown

---
type: reference
title: Bash pipefail silent-exit gotcha (grep in command substitution)
summary: Under set -euo pipefail, VAR=$(grep ... | ...) silently kills the script when grep finds no match — and the debugging lesson that "works interactively, fails in cron" claims must be tested by re-running the full script under its own shell options.
tags:
- scope/global
- type/reference
- domain/bash
- domain/debugging
---
# Bash pipefail silent-exit gotcha
## The failure mode
Under `set -euo pipefail`, a variable assignment via command substitution that
contains a grep pipeline:
```bash
VAR=$(grep -oE 'pattern' "$FILE" 2>/dev/null | tail -1 | awk '{print $2}')
```
**silently terminates the script when grep finds no match:**
1. No match → `grep` exits 1
2. `pipefail` propagates that through the pipeline (`tail`/`awk` exiting 0 doesn't rescue it)
3. The assignment's exit status is the substitution's exit status → nonzero
4. `set -e` exits the script — with **zero error output** (`set -e` prints nothing,
and here grep's stderr was `/dev/null`'d anyway)
**Symptom signature:** script halts silently mid-run, no error anywhere in any log,
everything before the halt point succeeded.
**Guard pattern:**
```bash
VAR=$(pipeline || true) # || true inside the substitution rescues pipefail
```
Or delete the line if it's dead code (a value that can never match on that host).
## The debugging lesson (bigger than the gotcha)
Confirmed in production 2026-07-04: the ovh-prod `backup.sh` halted silently every
night for ~2 months (2026-05-08 → 2026-07-04) and was **misattributed to a
cron-vs-interactive environment difference** across multiple debugging sessions.
Why the misattribution survived: each suspect command (`find -delete`, the notifier)
was re-run individually over SSH and worked — but the grep pipeline was never
re-tested **under the script's own shell options**. The failure had nothing to do
with cron; it reproduced identically in an interactive shell the moment the exact
pipeline was run under `set -euo pipefail`.
**Rule:** when a script "works interactively but fails under cron/systemd", before
blaming the environment, re-run the *full script* (or the exact failing region under
the same `set -euo pipefail` flags) interactively. Testing commands in isolation
strips the shell-option context that may be the actual killer. Cheap first check:
`bash -c 'set -euo pipefail; <suspect line>; echo REACHED'`.
Incident write-up: `~/servers/ovh-prod/docs/incidents.md`, 2026-07-04 entry.