verifyfirst

A command's return status · pattern-survives-as-a-literal · documented

An unmatched pattern is passed through as a literal filename

NS-082 documentedpattern-survives-as-a-literal

An unmatched pattern is passed through as a literal filename

reads as
`for f in releases/*.tar.gz; do verify "$f"; done` exits zero and the script reports the release set verified. Conclusion drawn: every archive was checked.
actually
bash(1): if no matching filenames are found, and the shell option nullglob is not enabled, the word is left unchanged. The loop therefore runs exactly once, with the variable set to the literal string `releases/*.tar.gz`, which names nothing. Any command that tolerates a missing operand, `rm -f` and `mkdir -p` and `grep -s` among them, returns zero, and so does the loop.
blind because
Zero iterations and one iteration over a path that does not exist yield the same exit status. The difference lives in a count that nothing reports, and a wrong directory, a typo in an extension and an empty build all produce it identically.
the check
Count what the pattern matched instead of what the loop returned: `shopt -s nullglob; files=(releases/*.tar.gz); echo "${#files[@]}"`, and fail on zero. Observed on bash 5.2.21 in an empty directory: `for fn in *.log; do echo "[$fn]"; done` printed `[*.log]` and exited 0; `rm -f *.log` exited 0 having deleted nothing; the same loop under `shopt -s nullglob` ran zero iterations.
cost of missing
A cleanup, upload or signing step reports success over an empty set, so the artefacts it was meant to handle survive untouched, and the next stage consumes the previous release without noticing it is stale.
mitigation
Enable `nullglob` and assert on the array length, or `failglob` where an empty match is always a bug.
generalises to
Every operation over a collection that is silently empty: globs, empty pipelines into xargs, queries returning no rows, iterations over an unset list.
source
man7.org

Reported as

Others this instrument misses

plain text · full registry