/flags/find/find-print0-xargs-0
find -print0 | xargs -0: handle file names with spaces and newlines
- tool
- find
- command
- find
- flag
- -print0
- default
- off (-print ends each path with a newline)
find . -name '*.txt' -print0 | xargs -0 wc -l passes every file name to wc intact, even names with spaces, quotes or newlines. -print0 prints each path followed by a NUL byte instead of a newline, and xargs -0 (--null) splits its input on NUL bytes only and treats quotes and backslashes as ordinary characters. NUL is the only byte that cannot appear in a Unix file name, so the pair is always unambiguous.
Without and with -print0
mkdir -p proj
touch proj/a.txt 'proj/my file.txt'
echo '-- newline separated'; find proj -name '*.txt' | xargs ls 2>&1 | sort
echo '-- NUL separated'; find proj -name '*.txt' -print0 | xargs -0 ls | sort
echo '-- NUL shown as |'; find proj -name '*.txt' -print0 | sort -z | tr '\0' '|'; echoOutput
-- newline separated ls: cannot access 'file.txt': No such file or directory ls: cannot access 'proj/my': No such file or directory proj/a.txt -- NUL separated proj/a.txt proj/my file.txt -- NUL shown as | proj/a.txt|proj/my file.txt|
Plain xargs splits proj/my file.txt into proj/my and file.txt, and ls fails on both. With -print0 and -0 the name arrives as one argument.
Characters that break the newline form
| Name contains | find | xargs | find -print0 | xargs -0 |
|---|---|---|
| space or tab | split into several arguments | one argument |
| single quote (don't.txt) | xargs: unmatched single quote | one argument |
| double quote or backslash | quote removed or error | one argument |
| newline | split into two names | one argument |
| leading dash (-x.txt) | safe with find . (names start with ./) | same: -0 does not change the name itself |
The single-quote case is the xargs: unmatched single quote error.
Useful companions
| Option | Tool | What it does |
|---|---|---|
| -0, --null | xargs | Input items end with NUL; quotes and backslashes are literal |
| -r, --no-run-if-empty | GNU xargs | Do not run the command if find printed nothing |
| -n N | xargs | At most N names per command invocation |
| -P N | GNU/BSD xargs | Run up to N commands in parallel |
| -z | GNU grep, sort | Read and write NUL-separated lines, so pipelines keep the NUL format |
| read -r -d '' | bash | Read one NUL-terminated name per loop iteration |
mkdir -p loop
touch 'loop/one two.txt' loop/three.txt
find loop -type f -print0 | sort -z | while IFS= read -r -d '' f; do printf '[%s]\n' "$f"; doneOutput
[loop/one two.txt] [loop/three.txt]
-print0 or -exec ... +
find ... -exec cmd {} + gives the same safety without a pipe and works with every POSIX find; see find -exec + vs ;. The pipe form is worth it when you need xargs features such as -P for parallel runs or -n to control batch size. Either way the command receives as many names per invocation as fit in the system limit, which avoids Argument list too long.
Pitfalls
-print0must be the action: infind . -name a -o -name b -print0it applies only to the second test. Group the tests:find . \( -name a -o -name b \) -print0.- Every program in the pipe must understand NUL:
head,grepwithout-z, orxargswithout-0will mangle it. - On Git Bash, names still follow Windows rules (no
:or*), but spaces are common, so the pair matters there too.
FAQ
Why does xargs say unmatched single quote?
Without -0, xargs splits on blanks and interprets quotes and backslashes, so a name such as don't.txt breaks it. Use find -print0 | xargs -0, which passes names unchanged.
Is -print0 portable?
GNU, BSD/macOS and BusyBox find all support it, and POSIX.1-2024 added find -print0 and xargs -0. Older strictly POSIX systems may lack it; find -exec cmd {} + works everywhere.
How do I read -print0 output in a bash loop?
Use while IFS= read -r -d '' f; do ...; done < <(find . -type f -print0). The empty -d '' makes read stop at each NUL byte.