Bash find loops split filenames: keep NUL records and quote each path
By ON Lab · Sources checked November 8, 2026
Do not turn find's text output into shell words with for path in $(find ...). Filenames can contain spaces and even newlines. Use a record format that preserves the filename boundary, such as find -print0, and a matching NUL-delimited reader. Quote the resulting path whenever passing it to a command.
A historical reader showed a filename with spaces splitting into several loop iterations. We checked current GNU find and Bash documentation and compared a broken loop with a NUL-delimited loop on five original local files. The goal is preserving filenames as data, not renaming them to avoid the issue.
Why quoting only inside the loop is too late
for pathname in $(find . -type f); do
printf '%s\n' "$pathname"
done
The unquoted command substitution is expanded into words before the body runs. The body may receive only part of a filename. Quoting pathname later protects that already damaged piece; it cannot reconstruct the original boundary. Wildcard characters in the expanded words can also participate in pathname expansion.
Wrapping the whole command substitution in double quotes is not the solution either: that treats the entire output as one item rather than preserving each individual filename.
Use matching NUL-delimited output and input
while IFS= read -r -d '' pathname; do
printf 'File found: %q\n' "$pathname"
done < <(find . -type f -print0)
This example is Bash syntax. The first input redirection feeds the loop; the following process substitution runs the producer. The empty delimiter argument to read selects NUL. IFS= avoids field-separator splitting, and -r keeps backslashes as data rather than treating them as escapes.
The producer and consumer must agree. A NUL-aware reader cannot recover record boundaries from ordinary newline-separated output when a filename itself contains a newline. A downstream program must likewise understand the selected format if you pass the records onward.
The %q display format is Bash's escaped representation for inspection. It does not rename the file. A plain text display of a filename containing a newline can span several screen lines even when the loop has preserved it correctly. Do not count displayed lines as proof of how many filenames were processed.
What the five-file fixture showed
We created names containing a space, a newline, a literal wildcard, a backslash and a leading dash. The broken command-substitution loop produced eleven pieces or expansions. The corrected loop returned exactly five NUL-delimited records whose bytes matched the five expected full paths, and its counter reported five.
The comparison used Bash 3.2 and /usr/bin/find on macOS. GNU's current manual documents -print0, and the installed find option was exercised separately in the fixture. We did not assume every old shell or find implementation supports the same syntax.
Keep quoting at the command boundary
Inside the loop, pass the variable as one quoted argument, for example "$pathname". Keep the receiving command's filename and option rules in mind as well. Preserving a loop record does not automatically make an unquoted expansion in a later command safe from splitting or glob expansion.
When adding a find name pattern such as 'foo*', quote the pattern so find receives it rather than asking the shell to expand it first. Start with a small known directory and a read-only display before connecting any operation that changes files.
Counter scope and producer errors are separate issues
The process-substitution form keeps the loop's assignments in the current Bash process. A pipe into the loop can create a subshell and leave an outer counter unchanged. The Bash counter and pipeline guide explains that different boundary.
Also check whether find completed successfully. A loop that reads the records it received does not prove the scan encountered no permission errors or concurrent file changes. If completeness is required, choose a workflow that records and checks the producer's result before trusting a manifest. This small loop is not a complete transactional backup or file-migration tool.
Completion checks
- Test known names with spaces and a newline, not just simple words.
- Use the same NUL delimiter at production and consumption.
- Compare actual records or escaped representations rather than displayed line counts.
- Quote each filename when handing it to the next command.
- Check producer failures and the scope of any counter separately.
Checked November 8, 2026 with five original regular files, Bash 3.2 and macOS find. No destructive operation, symlink traversal, concurrent modification, producer-failure protocol or universal-shell compatibility was tested.