Skip to content
AgileLogics
5 min read

Handling File Names Safely

Prerequisites: Finding files: find and xargs, Arrays

File names can contain spaces, quotes, newlines, and leading dashes. Scripts that handle "normal" names but break on unusual ones are a common source of bugs — and occasionally of security problems.

The rules

  1. Quote every expansion: "$file", "${files[@]}".
  2. Loop over globs, not ls output: for f in *.txt.
  3. Use null separators with find: -print0 and read -d ''.
  4. Mark the end of options with --, so names starting with - aren't read as options.

Reading find results safely

mkdir -p data
touch "data/plain.txt" "data/with space.txt" "data/-dash.txt"

count=0
while IFS= read -r -d '' file; do
  echo "found: [$file]"
  (( count += 1 ))
done < <(find data -name "*.txt" -print0 | sort -z)
echo "$count files"
found: [data/-dash.txt]
found: [data/plain.txt]
found: [data/with space.txt]
3 files
  • find -print0 ends each name with a null byte, which can't appear in a file name.
  • read -d '' reads up to the next null; IFS= and -r keep the name exactly as it is.
  • sort -z sorts null-separated input.

Leading dashes

touch -- "-report.txt"
ls -- -report.txt
rm -- "-report.txt"
ls
echo "removed"
-report.txt
removed

Without --, rm -report.txt would be read as options. Using ./-report.txt also works.

Collecting files into an array

mkdir -p pics
touch "pics/a b.jpg" "pics/c.jpg"
mapfile -d '' -t images < <(find pics -name "*.jpg" -print0 | sort -z)
echo "${#images[@]} images"
printf '[%s]\n' "${images[@]}"
2 images
[pics/a b.jpg]
[pics/c.jpg]

mapfile -d '' (Bash 4.4+) reads null-separated names straight into an array.

Test with nasty names

Before trusting a script with real files, try it on a folder containing names with spaces, quotes, a leading dash, and non-ASCII characters. ShellCheck flags most of the patterns that break.