From 6dc9a754568b726c1bdc3a53d1ccf9cda80aa935 Mon Sep 17 00:00:00 2001 From: cnYui Date: Mon, 13 Jul 2026 06:14:16 +0900 Subject: [PATCH 1/2] docs: document unmatched Bash globs --- examples/bash/04-nullglob-empty-match.bad.sh | 18 ++++++++++++++++++ examples/bash/04-nullglob-empty-match.good.sh | 17 +++++++++++++++++ examples/bash/README.md | 1 + reference/bash.md | 16 ++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100755 examples/bash/04-nullglob-empty-match.bad.sh create mode 100755 examples/bash/04-nullglob-empty-match.good.sh 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..daac8f0 --- /dev/null +++ b/examples/bash/04-nullglob-empty-match.good.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# +# GOOD: nullglob expands an unmatched pattern to zero words, so an empty +# directory produces no phantom filename. +set -euo pipefail + +main() ( + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + 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 From e2e48f6d70f5abe4db0c9cc07792765e3f313c43 Mon Sep 17 00:00:00 2001 From: cnYui Date: Mon, 13 Jul 2026 07:08:23 +0900 Subject: [PATCH 2/2] fix: clean up nullglob example temp directory --- examples/bash/04-nullglob-empty-match.good.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/bash/04-nullglob-empty-match.good.sh b/examples/bash/04-nullglob-empty-match.good.sh index daac8f0..85c496e 100755 --- a/examples/bash/04-nullglob-empty-match.good.sh +++ b/examples/bash/04-nullglob-empty-match.good.sh @@ -4,9 +4,10 @@ # directory produces no phantom filename. set -euo pipefail +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT + main() ( - tmp=$(mktemp -d) - trap 'rm -rf "$tmp"' EXIT cd "$tmp" shopt -s nullglob