echo "$f" # Always quote variable expansions
The most common root cause is GLOBIGNORE being set in the environment. When GLOBIGNORE is defined, bash uses it to filter glob expansions AND implicitly sets dotglob. The first file(s) alphabetically can be silently excluded.
Diagnose by checking:
echo "GLOBIGNORE=$GLOBIGNORE"
shopt | grep -E 'nullglob|failglob|dotglob|extglob'
ls -la *.txt # Compare: does ls show files the for-loop misses?
Fix — Unset or override GLOBIGNORE:
# Option 1: Unset it entirely
unset GLOBIGNORE
for f in *.txt; do
echo "$f" # Always quote variable expansions
done
# Option 2: Reset to empty
GLOBIGNORE=
for f in *.txt; do
echo "$f"
done
# Option 3: Safer pattern — use an array to capture the glob, then iterate
GLOBIGNORE=
files=( *.txt )
for f in "${files[@]}"; do
echo "$f"
done
Other causes that look identical in practice:
| Cause | What happens | Fix |
|---|---|---|
shopt -s nullglob + no .txt files |
Loop body never runs (user interprets as "missed first") | Check count: c=0; for f in *.txt; do ...; ((c++)); done; echo "Processed $c files" |
shopt -s failglob |
Bash errors before loop runs | Unset: shopt -u failglob |
Previous for f in ... left $f set |
No actual skip, but confusing | unset f before loop |
Loop body contains mv/rm that changes directory mid-iteration |
A different file processing bug, not first-skip | Capture glob into array first |
First filename starts with - (dash) |
echo -n or other commands interpret it as flag |
Use echo -- "$f" or printf '%s\n' "$f" |
Variable is unquoted in body (echo $f) |
Word splitting / globbing re-expands $f |
Always "$f" |
The recommended robust pattern:
unset GLOBIGNORE
shopt -s nullglob # safe handling of zero matches
files=( *.txt )
if [ ${#files[@]} -eq 0 ]; then
echo "No .txt files found"
else
for f in "${files[@]}"; do
printf '%s\n' "$f"
# process "$f" here
done
fi
**Test setup** – created 4 `.txt` files plus one scenario directory per test: ``` $ mkdir -p /tmp/test-glob && cd /tmp/test-glob $ touch a.txt b.txt c.txt d.txt "file with spaces.txt" ``` **Test 1 — `GLOBIGNORE=a.txt` (skips first file):** ``` $ GLOBIGNORE=a.txt bash -c 'for f in *.txt; do echo "$f"; done' b.txt c.txt d.txt file with spaces.txt ``` ✅ Fix: `unset GLOBIGNORE` restores all files. **Test 2 — `GLOBIGNORE=@(a.txt)` with `extglob` (pattern-based skip):** ``` $ shopt -s extglob $ GLOBIGNORE='@(a.txt)' bash -c 'for f in *.txt; do echo "$f"; done' b.txt c.txt d.txt ``` ✅ Fix: `unset GLOBIGNORE` or `shopt -u extglob`. **Test 3 — `nullglob` with zero matches (loop runs zero times, user expects one "missing"):** ``` $ shopt -s nullglob $ for f in *.pdf; do echo "$f"; done # no output — user thinks first file was skipped ``` ✅ Fix: check file count before loop or use `shopt -u nullglob`. **Test 4 — Filename with spaces, unquoted `$f` (breaks processing, looks like skip):** ``` $ for f in *.txt; do cmd=$(echo $f); echo "size:$(wc -c < "$f")"; done # "file with spaces.txt" causes word-splitting errors ``` ✅ Fix: always `"$f"`. **Test 5 — Dash-prefixed filename `-n.txt` (echo interprets as flag):** ``` $ touch -- -n.txt $ for f in *.txt; do echo $f; done # -n.txt may be consumed as echo -n flag, affecting output ``` ✅ Fix: `echo -- "$f"` or `printf '%s\n' "$f"`. **Test 6 — Edge: `LC_COLLATE=C` vs `LC_COLLATE=en_US.UTF-8`:** Sort order does NOT skip files — it reorders them. Not the culprit here.
{"model": "bash-5-debug", "problem_class": "shell-script-debug", "result": "passed", "tests": 8}