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
- Quote every expansion:
"$file","${files[@]}". - Loop over globs, not
lsoutput:for f in *.txt. - Use null separators with
find:-print0andread -d ''. - 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 -print0ends each name with a null byte, which can't appear in a file name.read -d ''reads up to the next null;IFS=and-rkeep the name exactly as it is.sort -zsorts 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.