Bash Script xargs Failing on Filenames Containing Spaces or Special Characters
xargs failing on filenames with spaces isn't a bug — by default, both find's output and xargs's input parsing treat whitespace as a delimiter between separate items, which silently breaks any filename that itself contains a space, splitting it into multiple, incorrect arguments.
The Problem
A command piping filenames into xargs behaves unexpectedly for any file with a space in its name:
find . -name "*.txt" | xargs rm
rm: cannot remove 'My': No such file or directory
rm: cannot remove 'Document.txt': No such file or directory
A file genuinely named My Document.txt gets split into two separate arguments, My and Document.txt, neither of which exists as an actual filename on its own.
Why It Happens
By default, both the output of commands like find and the way xargs parses its input treat whitespace (spaces, tabs, newlines) as separators between distinct items — this works fine for filenames without spaces, but any file with a space, or other special characters like quotes, is fundamentally ambiguous under this default parsing, since there's no way to distinguish "one filename containing a space" from "two separate filenames" using whitespace-delimited text alone.
The Fix
Use null-byte delimited output and input instead of whitespace, which unambiguously separates items regardless of what characters (including spaces) the filenames themselves contain, since the null byte is not a character that can appear in a valid filename:
find . -name "*.txt" -print0 | xargs -0 rm
-print0 tells find to separate output items with null bytes instead of newlines, and -0 tells xargs to expect and parse its input the same way — together, this combination correctly handles filenames containing spaces, newlines, or virtually any other character that would otherwise be ambiguous with whitespace-based parsing.
If you're not using find as the source, but generating a list of filenames some other way, make sure that source also uses null-byte separation if you want the same safety, since the fix requires both sides of the pipe to agree on the delimiter:
ls -1 *.txt | tr '\n' '\0' | xargs -0 rm
# though for filenames specifically, `find -print0` is generally more robust than parsing `ls` output at all
For simple cases where you're processing every match from find without needing the flexibility of a full pipeline, find's own -exec flag avoids the xargs parsing question entirely, since it operates directly on each matched file without any intermediate text-based handoff:
find . -name "*.txt" -exec rm {} \;
This is simpler for straightforward one-command-per-file operations, though it's generally less efficient for a very large number of files than batching with xargs, since -exec ... \; invokes the command once per file rather than batching multiple files into fewer command invocations. For a batched version that still uses -exec safely:
find . -name "*.txt" -exec rm {} +
The + instead of \; tells find to batch as many matched files as possible into fewer invocations of the command (similar in spirit to how xargs batches), while still handling filenames safely without needing to go through any whitespace-delimited intermediate text representation at all.
If you're working with a variable containing a list of filenames rather than piping directly from find, be cautious about how that list was originally constructed — if it was built by splitting text on whitespace at any earlier point in your script, the space-in-filename problem may have already been introduced upstream, before xargs is even involved, and needs to be fixed at that earlier point instead.
Still Not Working?
If you're processing filenames within a Bash array rather than through find/xargs directly, use Bash's own array handling, which correctly preserves each element (including any embedded spaces) as a single, distinct item without needing any null-byte workaround at all:
files=(*.txt)
for f in "${files[@]}"; do
rm "$f"
done
The key detail here is quoting "$f" and "${files[@]}" — without these quotes, Bash would re-split the values on whitespace during expansion, reintroducing the exact same problem in a different form. Consistently quoting variable expansions throughout a script is a good general habit that prevents this entire category of bug from appearing in contexts beyond just xargs specifically.