Bash & Shell Scripting DISCUSSION

Why does my Bash script break on filenames with spaces even though the variable holds the right name?

Started by drakkor Bash quotingword splittingfilenames with spacesdouble quotesBash arrays
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

Why does my Bash script break on filenames with spaces even though the variable holds the right name?

drakkor Bash & Shell Scripting Forum
#1

My backup script does for f in $(ls *.csv); do cp $f $dest; done. It worked for months, then failed on a file called sensor log 2026.csv with cannot stat 'sensor'. The file exists and ls shows it correctly.

When exactly does Bash split a value into several words, and what is the correct way to loop over files and pass them to commands?

Community replies 4

Re: Why does my Bash script break on filenames with spaces even though the variable holds the right name?

#2

After an unquoted $var or $(command) is expanded, Bash performs two more steps on the result: word splitting on the characters in IFS (space, tab and newline by default) and then filename expansion of any *, ? or [ in the pieces. So cp $f $dest with that file name would run cp with four arguments: sensor, log, 2026.csv and the destination.

Double quotes suppress both steps: cp "$f" "$dest" passes each value as exactly one argument. Quote every expansion unless you specifically want splitting.

Re: Why does my Bash script break on filenames with spaces even though the variable holds the right name?

#3

The loop has the same fault one level up. $(ls *.csv) turns the file list into one blob of text, which is then split on whitespace, so the name is already broken into three loop items before the body runs. Let the shell produce the list directly: for f in *.csv; do cp -- "$f" "$dest"; done. A glob expands to one word per file regardless of what characters the names contain.

Two details: if no file matches, the pattern is left as the literal text *.csv, so either test [ -e "$f" ] inside the loop or set shopt -s nullglob. The -- stops a file whose name begins with a dash from being read as an option.

Re: Why does my Bash script break on filenames with spaces even though the variable holds the right name?

#4

For script arguments, use "$@". It expands to each original argument as its own word, so for arg in "$@" and mytool "$@" preserve spaces. "$*" joins all arguments into a single word, and unquoted $@ or $* splits everything again.

When you need to hold a list in a variable, use an array, not a string: files=("sensor log 2026.csv" "run2.csv"), then cp -- "${files[@]}" "$dest". Here ${#files[@]} is 2, whereas the same names stored in a plain string and expanded unquoted would give 4 words.

Re: Why does my Bash script break on filenames with spaces even though the variable holds the right name?

#5

Quotes stored inside a variable do not help. With opts="--exclude 'old data'", expanding $opts gives the words --exclude, 'old and data': the single quotes are ordinary characters by then, because quote processing happens before expansion, not after. Build option lists as arrays too: opts=(--exclude "old data") and rsync "${opts[@]}" src/ dst/.

For files found recursively, avoid parsing find output line by line. Use find . -name '*.csv' -exec cp -- {} "$dest" \; or the null-delimited form find . -name '*.csv' -print0 | xargs -0 ..., which is safe for any name.

TEP COMMUNITY