Test Shell Scripts From a Path With Spaces
The cheapest shell bug detector? A folder name with a space in it. Unquoted variables look fine for months — right up until a path contains a space, the shell splits it into two arguments, and your script does something you never intended. You can buy that failure early, for free: run the script from a temp directory whose path has a space in it.
Why one space breaks so much
Bash scans the results of unquoted parameter expansion, command substitution, and arithmetic expansion for word splitting, using $IFS — by default space, tab, and newline. So rm $file on 01 - intro.mp3 becomes rm with three arguments, not one. ShellCheck's most famous warning, SC2086, exists precisely for this: unquoted expansions are first split on IFS, then each piece is expanded as a glob. Two silent transformations, both wrong for filenames.
The fix is one character pair — "$file" instead of $file — but the bug hides because developers test in ~/projects/myrepo, a path with zero spaces in it.
The 60-second harness
Make the failure environment the default test environment:
# 1. A temp dir with a space in its path — on purpose
dir="$(mktemp -d "${TMPDIR:-/tmp}/space test.XXXXXX")"
# 2. Run the script from there
cp myscript.sh "$dir/"
cd "$dir" && bash myscript.sh
# 3. Clean up
rm -rf "$dir"
mktemp -d creates the directory; the template just needs at least three consecutive Xs in the last component, and nothing stops you from putting a space before them. Every unquoted $dir, $PWD, $(dirname "$0"), or cd $target inside the script now fails loudly, right there — instead of quietly on a user's machine whose home folder is /Users/Ana Maria.
What to exercise once you're in there
| Failure mode | How to trigger it |
|---|---|
| Word splitting / globbing | The spaced path itself — any unquoted expansion breaks |
| Current-directory assumptions | Run from the temp dir, not the repo root; relative paths that "just worked" stop working |
| Missing env vars | env -i bash myscript.sh — an empty environment exposes every $VAR you assumed was set |
| Failed subprocesses | Point the script at a command that exits non-zero; check it stops instead of continuing |
| Repeated runs | Run it twice in the same dir — idempotence bugs (mkdir without -p, appends that double up) surface on run two |
Lint first, then run
Static analysis catches the quoting class of bug before you execute anything. ShellCheck flags unquoted expansions, and the Google Shell Style Guide recommends it "for all scripts, large or small" — alongside its own quoting rule: always quote strings containing variables, command substitutions, spaces, or shell metacharacters. The order matters: lint catches what's statically visible; the spaced-path run catches the dynamic cases lint can't see (values that arrive at runtime from find, arguments, or config).
Power-user moves
- Bake it into CI. A five-line job that copies your scripts into a
mktemp -dpath with a space and runs the test suite from there turns this from a habit into a guarantee. - Go beyond spaces. Newlines and glob characters (
*,?) in filenames break even more scripts. A directory named$(printf 'a b\nc*')is the adversarial version of this test. - Trap the cleanup.
trap 'rm -rf "$dir"' EXITremoves the temp dir even when the script fails mid-run — and yes, that"$dir"needs its quotes too. - Read BashPitfalls once a year. The classic
for f in $(ls *.mp3)pitfall — iterating over words instead of files — is the same word-splitting mechanism in loop form, and the wiki's fix list doubles as a checklist for your harness.
Resources
- ShellCheck SC2086 — double quote to prevent globbing and word splitting
- Bash Reference Manual — Word Splitting
- Greg's Wiki — Bash Pitfalls
- Google Shell Style Guide — quoting and ShellCheck
- mktemp(1) man page
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.