Use find instead of ls to iterate files
Parsing `ls` output breaks on filenames with spaces, newlines, or quotes. Use `find` with NUL delimiters.
Problem
`ls` is for humans. Its output format is locale-dependent and unstable, and a filename can contain any byte except NUL — including newlines. Scripts that loop over `ls` output silently corrupt on the first weird filename.
Bad
for f in $(ls *.log); do
process "$f"
doneGood
# Globbing inside the shell — safe for spaces:
shopt -s nullglob
for f in *.log; do
process "$f"
done
# Or NUL-delimited with find for recursive cases:
find . -name '*.log' -print0 | while IFS= read -r -d '' f; do
process "$f"
doneExplanation
The shell's own globbing handles spaces correctly. `nullglob` makes a no-match glob expand to nothing instead of staying literal. For recursive traversals, `find -print0` plus `read -d ''` is the only fully safe pattern.
Related
SC2045
SC2086
When It Matters
ls formats output for humans: it may add colour escape codes, replace unprintable characters with question marks, quote names containing spaces, and change column layout depending on whether output is a terminal. Parsing it means your script depends on all of that, and none of it is stable. Filenames containing newlines break line-based parsing of ls entirely, and unlike most theoretical concerns, they arrive routinely from extracted archives and user uploads.
Second Example
Note
nullglob matters: without it, a glob that matches nothing is passed through literally and the loop runs once with the pattern as the value.
Exceptions
ls is the right tool when a human is going to read the output, and ls -t or ls -S with a careful NUL-free assumption is still occasionally the shortest route to a sorted list. Where correctness matters, find -printf or stat gives the same data in a parseable form.
Faq
Q
How do I sort files by modification time without ls?
A
find . -maxdepth 1 -printf '%T@ %p\0' | sort -zn gives time-sorted, NUL-delimited output that survives any filename.
Q
What does nullglob do?
A
It makes an unmatched glob expand to nothing rather than to the literal pattern, which stops loops running once with a bogus value.
Q
How do I count files in a directory?
A
Collect them into an array with a glob and read ${#arr[@]}, or use find ... -printf . | wc -c. Counting lines of ls is wrong for names with newlines.