Make xargs handle filenames that contain spaces
$ ls *mp3 | xargs mplayer
Playing Lemon.
File not found: 'Lemon'
Playing Tree.mp3.
File not found: 'Tree.mp3'
Exiting... (End of file)
My command fails because the file "Lemon Tree.mp3" contains spaces and so xargs thinks it's two files. Can I make find + xargs work with filenames like this?
The xargs
command takes white space characters (tabs, spaces, new lines) as delimiters.
You can narrow it down only for the new line characters ('\n') with -d
option like this:
ls *.mp3 | xargs -d '\n' mplayer
It works only with GNU xargs.
For MacOS:
ls *.mp3 | tr \\n \\0 | xargs -0 mplayer
The more simplistic and practically useful approach (when don't need to process the filenames further):
mplayer *.mp3
The xargs utility reads space, tab, newline and end-of-file delimited strings from the standard input and executes utility with the strings as arguments.
You want to avoid using space as a delimiter. This can be done by changing the delimiter for xargs. According to the manual:
-0 Change xargs to expect NUL (``\0'') characters as separators, instead of spaces and newlines. This is expected to be used in concert with the -print0 function in find(1).
Such as:
find . -name "*.mp3" -print0 | xargs -0 mplayer
To answer the question about playing the seventh mp3; it is simpler to run
mplayer "$(ls *.mp3 | sed -n 7p)"
Try
find . -name \*.mp3 -print0 | xargs -0 mplayer
instead of
ls | grep mp3
xargs on MacOS doesn't have -d option, so this solution uses -0 instead.
Get ls to output one file per line, then translate newlines into nulls and tell xargs to use nulls as the delimiter:
ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer
Dick.Guertin's answer [1] suggested that one could escape the spaces in a filename is a valuable alternative to other solutions suggested here (such as using a null character as a separator rather than whitespace). But it could be simpler - you don't really need a unique character. You can just have sed add the escaped spaces directly:
ls | grep ' ' | sed 's| |\\ |g' | xargs ...
Furthermore, the grep is only necessary if you only want files with spaces in the names. More generically (e.g., when processing a batch of files some of which have spaces, some not), just skip the grep:
ls | sed 's| |\\ |g' | xargs ...
Then, of course, the filename may have other whitespace than blanks (e.g., a tab):
ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...
That assumes you have a sed that supports -r (extended regex) such as GNU sed or recent versions of bsd sed (e.g., FreeBSD which originally spelled the option "-E" before FreeBSD 8 and supports both -r & -E for compatibility through FreeBSD 11 at least). Otherwise you can use a basic regex character class bracket expression and manually enter the space and tab characters in the []
delimiters.
[1] This is perhaps more appropriate as a comment or an edit to that answer, but at the moment I do not have enough reputation to comment and can only suggest edits. Since the latter forms above (without the grep) alters the behavior of Dick.Guertin's original answer, a direct edit is perhaps not appropriate anyway.