On Linux, filenames can contain not only spaces but also newline characters. If you treat the output of find as text split on newlines or spaces, a single filename can therefore be split incorrectly into multiple arguments.
The basic rule for safely using GNU find and xargs is not to reinterpret filenames as strings. find -print0 and xargs -0 use the NUL character as a delimiter. find -exec can also pass search results directly as command arguments.
This article creates files containing spaces and newlines and verifies one-at-a-time processing, batch processing, and parallel processing.
Step 1: Create test files containing spaces and newlines
Section titled “Step 1: Create test files containing spaces and newlines”First, create a normal filename, a filename containing a space, a filename containing a newline, and additional files for checking batch processing.
In Bash, the $'...' form interprets \n as a newline character, so you can create a filename that contains an actual newline between line and break.txt.
set -euo pipefailrm -rf /tmp/find-xargs-examplemkdir -p /tmp/find-xargs-example/inputprintf 'alpha\n' > '/tmp/find-xargs-example/input/plain.txt'printf 'bravo\n' > '/tmp/find-xargs-example/input/two words.txt'printf 'charlie\n' > $'/tmp/find-xargs-example/input/line\nbreak.txt'for n in $(seq -w 01 12); do printf 'batch-%s\n' "$n" > "/tmp/find-xargs-example/input/batch file ${n}.txt"doneprintf 'setup-ok\n'This creates a total of 15 files.
It is not safe to pass plain find output directly to processing that splits on spaces or newlines. In particular, with a filename containing a newline, one visible line does not correspond to one file.
Step 2: Pass filenames safely with -print0 and xargs -0
Section titled “Step 2: Pass filenames safely with -print0 and xargs -0”find -print0 terminates each pathname with a NUL character instead of a newline. The corresponding xargs -0 treats only NUL as the input delimiter, so spaces and newlines inside filenames are preserved.
In the following example, the shell first changes to /tmp, which is accessible to a regular user, and then runs find. This avoids depending on permission to return to the original working directory. Each file is then passed to bash individually, and the command checks that the received path refers to an existing file.
set -euo pipefailcd /tmpfind /tmp/find-xargs-example/input -maxdepth 1 -type f -print0 | xargs -0 -r -n 1 bash -c 'test -f "$1"' _test "$(find /tmp/find-xargs-example/input -maxdepth 1 -type f -printf x | wc -c)" -eq 15printf 'nul-pipeline-safe\n'The _ after bash -c supplies a value for $0. The first pathname appended by xargs is received as $1.
When using -print0, the receiving side must also use the matching -0 option. Specifying only one side does not make the pipeline safe.
Step 3: Process files one at a time with find -exec
Section titled “Step 3: Process files one at a time with find -exec”When xargs is unnecessary, find -exec is another option.
With the {} \; form, the command is run once for each pathname found. Because the pathname is passed from find directly as a command argument, it does not need to be reparsed using spaces or newlines as delimiters.
If a regular user cannot access the directory from which the command was started, find can fail when it tries to return there after executing a command. The next example therefore changes to /tmp before processing.
set -euo pipefailcd /tmpproof=/tmp/find-xargs-example/exec.count: > "$proof"find /tmp/find-xargs-example/input -maxdepth 1 -type f \ -exec bash -c 'test -f "$1"; printf x >> "$2"' _ {} "$proof" \;test "$(wc -c < "$proof")" -eq 15printf 'exec-safe\n'For a small number of targets, or when a command must be run separately for each file, {} \; is straightforward. For a large number of files, however, starting an external command for every file increases process creation overhead.
Step 4: Batch files with -exec {} +
Section titled “Step 4: Batch files with -exec {} +”For large numbers of files, -exec ... {} + can pass multiple pathnames to one command invocation.
To avoid depending on permissions for the original working directory, this example also changes to /tmp before running find.
The example processes every argument supplied to each command invocation with for path do. Quoting it as "$path" prevents a pathname that has already been received as one argument from being split again.
set -euo pipefailcd /tmpresult=$( find /tmp/find-xargs-example/input -maxdepth 1 -type f \ -exec bash -c ' for path do test -f "$path" printf x done ' _ {} +)test "${#result}" -eq 15printf 'batch-safe\n'With {} +, find does not unconditionally put every file into a single command. It divides the pathnames across invocations according to the usable argument size, which can reduce the number of external process launches compared with {} \;.
Step 5: Run processing in parallel with xargs -P
Section titled “Step 5: Run processing in parallel with xargs -P”If each file can be processed independently, xargs -P can run multiple commands in parallel.
To avoid depending on permissions for the original working directory, this example again changes to /tmp before running find.
The example uses -n 1 to pass one file per invocation and -P 4 to limit concurrency to at most four processes. Input still flows through -print0 and -0, so parallel execution does not change how filenames containing spaces or newlines are delimited.
set -euo pipefailcd /tmpresult=$( find /tmp/find-xargs-example/input -maxdepth 1 -type f -print0 | xargs -0 -r -n 1 -P 4 bash -c 'test -f "$1"; printf x' _)test "${#result}" -eq 15printf 'parallel-safe\n'Parallel execution does not guarantee completion order. Do not simply add -P when output must remain in input order or when multiple processes may update the same file.
Parallelism can also increase CPU, memory, storage I/O, and load on connected services, so choose a concurrency level appropriate for the workload.
Choosing between the methods
Section titled “Choosing between the methods”For safe filename processing, choose the method that fits the task.
- To pass pathnames through a pipeline to another command, combine
find -print0withxargs -0 - To run a command directly once per file, use
find -exec ... {} \; - To process multiple files in batches, use
find -exec ... {} + - To parallelize independent work, combine
find -print0,xargs -0, andxargs -P - In shell code, always quote received pathnames such as
"$path"and"$1"
Even when filenames are passed safely, expanding variables without quotes afterward can reintroduce word splitting and pathname expansion. It is important to preserve argument boundaries not only with NUL delimiters but throughout the receiving shell code.
Step 6: Remove the test files
Section titled “Step 6: Remove the test files”After verification, remove the temporary files that were created.
set -euo pipefailrm -rf /tmp/find-xargs-exampleprintf 'cleanup-ok\n'When processing large numbers of filenames that may contain spaces or newlines, the fundamental rule is not to treat the pathname list as newline-delimited text. Preserve argument boundaries with NUL delimiters or find -exec, and keep pathnames quoted in subsequent shell processing to build safe handling for unusual filenames.