---
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.
