Pattern: Use of for
loop over find
output
Issue: -
for var in $(find ...)
loops rely on word splitting and will evaluate globs, which will wreck havoc with filenames containing whitespace or glob characters.
find -exec
for i in glob
and find
+while
do not rely on word splitting, so they avoid this problem.
Example of incorrect code:
for file in $(find somedir -mtime -7 -name '*.mp3')
do
let count++
echo "Playing file no. $count"
play "$file"
done
echo "Played $count files"
This will fail for filenames containing spaces and similar, such as Some File.mp3
, and has a series of potential globbing issues depending on other filenames in the directory like (if you have SomeFile2.mp3
and SomeFile[2014].mp3
, the former file will play twice and the latter will not play at all).
Example of correct code:
There are many possible fixes, each with its pros and cons.
The most general fix (that requires the least amount of thinking to apply) is having find
output a \0
separated list of files and consuming them in a while read
loop:
while IFS= read -r -d '' file
do
let count++
echo "Playing file no. $count"
play "$file"
done < <(find somedir -mtime -7 -name '*.mp3' -print0)
echo "Played $count files"
In usage it's very similar to the for
loop: it gets its output from a find
statement, it executes a shell script body, it allows updating/aggregating variables, and the variables are available when the loop ends.
It requires Bash, and works with GNU, Busybox, OS X, FreeBSD and OpenBSD find, but not POSIX find.
If you know about and carefully apply IFS=$'\n'
and set -f
, you could choose to ignore this message.