diff --git a/examples/bash/04-nullglob-empty-match.bad.sh b/examples/bash/04-nullglob-empty-match.bad.sh new file mode 100755 index 0000000..c349869 --- /dev/null +++ b/examples/bash/04-nullglob-empty-match.bad.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# +# BAD: without nullglob, an unmatched pattern remains the literal string +# "*.txt", so the loop runs once for a file that does not exist. +# expect-shellcheck: none +set -euo pipefail + +main() ( + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + cd "$tmp" + + for file in *.txt; do + printf 'found: %s\n' "$file" + done +) + +main "$@" diff --git a/examples/bash/04-nullglob-empty-match.good.sh b/examples/bash/04-nullglob-empty-match.good.sh new file mode 100755 index 0000000..85c496e --- /dev/null +++ b/examples/bash/04-nullglob-empty-match.good.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# +# GOOD: nullglob expands an unmatched pattern to zero words, so an empty +# directory produces no phantom filename. +set -euo pipefail + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + +main() ( + cd "$tmp" + + shopt -s nullglob + files=(*.txt) + printf 'matched %s file(s)\n' "${#files[@]}" +) + +main "$@" diff --git a/examples/bash/README.md b/examples/bash/README.md index ba8530b..9c168b8 100644 --- a/examples/bash/README.md +++ b/examples/bash/README.md @@ -10,3 +10,4 @@ safe to run and declares the ShellCheck code it triggers. | `01-mapfile-not-subshell` | `mapfile -t` reads lines safely | `arr=($(...))` word-splits | `SC2207` | | `02-array-not-scalar` | `"${arr[@]}"` expands all elements | bare `$arr` = first only | `SC2128` | | `03-quote-args` | `"$@"` forwards args intact | unquoted `$@` re-splits | `SC2068` | +| `04-nullglob-empty-match` | `nullglob` makes an empty match produce zero words | an unmatched glob stays literal | none | diff --git a/reference/bash.md b/reference/bash.md index 3ccd294..43d05bb 100644 --- a/reference/bash.md +++ b/reference/bash.md @@ -53,6 +53,22 @@ fi See the runnable pairs in [`examples/bash/`](../examples/bash/). +## Globbing and empty matches + +By default, a glob that matches nothing remains unchanged. In an empty +directory, `for file in *.txt` therefore runs once with the literal string +`*.txt`, even though no file exists. + +- Enable `nullglob` when no matches should produce zero words: `shopt -s + nullglob`. This is useful for loops and arrays that should simply stay empty. +- Enable `failglob` when no matches should be an error instead. This is useful + when the missing input signals a broken assumption. + +Both options are Bash-specific and affect subsequent expansions in the current +shell, so enable them deliberately and keep their scope narrow. See the Bash +manual's [Filename Expansion](https://www.gnu.org/software/bash/manual/bash.html#Filename-Expansion) +section and the runnable pair in [`examples/bash/`](../examples/bash/). + ## Safe temp files Create with `mktemp`, remove with an `EXIT` trap set immediately afterward. Keep