Reputation: 328
$ 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?
Upvotes: 435
Views: 169703
Reputation: 1973
Another solution not mentioned yet is to use awk
to wrap quotes around file names (including ones containing spaces). You can do this by setting a double-quote value in awk from its ASCII value of 34
. What's nice about this solution is that it can also handle filenames containing an apostrophe (aka single-quote).
Example:
$ ls -l
-rw------- 1 jftuga jftuga 123456 Dec 11 10:01 Lemon Tree.mp3
$ ls | awk 'BEGIN {q=34} {printf("%c%s%c ",q,$0,q)}'
"Lemon Tree.mp3"
Now use awk
with either ls
or find
. The space
after the last %c
is used to add a space in between multiple file names:
$ ls | awk 'BEGIN {q=34} {printf("%c%s%c ",q,$0,q)}' | xargs mplayer
$ find . -type f | awk 'BEGIN {q=34} {printf("%c%s%c ",q,$0,q)}' | xargs mplayer
You can also append a \n
to the end of the printf
string if you prefer to have each file name on its own line instead of separated by spaces:
awk 'BEGIN {q=34} {printf("%c%s%c\n",q,$0,q)}'
Upvotes: 0
Reputation: 72756
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)"
Upvotes: 267
Reputation: 7329
ls *mp3 | xargs -I{} mplayer {}
As long as you do not mind mplayer running once per line.
It runs once per newline-terminated line. Space is no longer additionally used as a delimiter. I cannot get the MacOS/BSD xargs to not use space as a delimiter in any other way (-L 1 does not work
).
Upvotes: 0
Reputation: 835
I recently use the following solution:
$ find <some_path> | sed -e 's/^/"/' -e 's/$/"/' | xargs <command>
ex: $ ls *.mp3 | sed -e 's/^/"/' -e 's/$/"/' | xargs mplayer
this make all files accessible
Upvotes: 5
Reputation: 558
On macOS (Monterey/12 forward and I'm not sure how far back past 10.15/Catalina), if you have spaces in file names or subdirectories, you can use the following:
mdfind -0 -onlyin . -name .txt | xargs -0 grep stackoverflow | wc -l
As Jen's answer notes:
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).
Upvotes: 0
Reputation: 5659
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
Upvotes: 484
Reputation: 2014
Alternative solutions can be helpful...
You can also add a null character to the end of your lines using Perl, then use the -0
option in xargs. Unlike the xargs -d '\n' (in approved answer) - this works everywhere, including OS X.
For example, to recursively list (execute, move, etc.) MPEG3 files which may contain spaces or other funny characters - I'd use:
find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0 ls
(Note: For filtering, I prefer the easier-to-remember "| grep" syntax to "find's" --name arguments.)
Upvotes: 3
Reputation:
On macOS 10.12.x (Sierra), if you have spaces in file names or subdirectories, you can use the following:
find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l
Upvotes: 5
Reputation: 111
find . -name 'Lemon*.mp3' -print0 | xargs -0 -i mplayer '{}'
This helped in my case to delete different files with spaces. It should work too with mplayer. The necessary trick is the quotes. (Tested on Linux Xubuntu 14.04.)
Upvotes: 11
Reputation: 2272
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
Upvotes: 28
Reputation: 6050
I know that I'm not answering the xargs
question directly but it's worth mentioning find
's -exec
option.
Given the following file system:
[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush
0 directories, 4 files
The find command can be made to handle the space in Dream Theater and King's X. So, to find the drummers of each band using grep:
[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren
In the -exec
option {}
stands for the filename including path. Note that you don't have to escape it or put it in quotes.
The difference between -exec
's terminators (+
and \;
) is that +
groups as many file names that it can onto one command line. Whereas \;
will execute the command for each file name.
So, find bands/ -type f -exec grep Drums {} +
will result in:
grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"
and find bands/ -type f -exec grep Drums {} \;
will result in:
grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"
In the case of grep
this has the side effect of either printing the filename or not.
[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren
[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren
Of course, grep
's options -h
and -H
will control whether or not the filename is printed regardless of how grep
is called.
xargs
xargs
can also control how man files are on the command line.
xargs
by default groups all the arguments onto one line. In order to do the same thing that -exec \;
does use xargs -l
. Note that the -t
option tells xargs
to print the command before executing it.
[root@localhost bokeh]# find ./bands -type f | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater
Drums:Mike Mangini
grep Drums ./bands/Rush
Drums: Neil Peart
grep Drums ./bands/King's X
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth
Drums:Dirk Verbeuren
See that the -l
option tells xargs to execute grep for every filename.
Versus the default (i.e. no -l
option):
[root@localhost bokeh]# find ./bands -type f | xargs -d '\n' -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
xargs
has better control on how many files can be on the command line. Give the -l
option the max number of files per command.
[root@localhost bokeh]# find ./bands -type f | xargs -d '\n' -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]#
See that grep
was executed with two filenames because of -l2
.
Upvotes: 10
Reputation: 1371
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.
Upvotes: 18
Reputation: 27
Given the specific title of this post, here's my suggestion:
ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'
The idea is to convert blanks to any unique character, like '<', and then change that into '\ ', a backslash followed by a blank. You can then pipe that into any command you like, such as:
ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo
The key here lies in the 'tr' and 'sed' commands; and you can use any character besides '<', such as '?' or even a tab-character.
Upvotes: 1
Reputation: 63526
ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}
Note that in the command above, xargs
will call mplayer
anew for each file. This may be undesirable for mplayer
, but may be okay for other targets.
Upvotes: 4
Reputation: 20046
Try
find . -name \*.mp3 -print0 | xargs -0 mplayer
instead of
ls | grep mp3
Upvotes: 35
Reputation: 755054
It depends on (a) how attached you are to the number 7 as opposed to, say, Lemons, and (b) whether any of your file names contain newlines (and whether you're willing to rename them if they do).
There are many ways to deal with it, but some of them are:
mplayer Lemon*.mp3
find . -name 'Lemon*.mp3' -exec mplayer {} ';'
i=0
for mp3 in *.mp3
do
i=$((i+1))
[ $i = 7 ] && mplayer "$mp3"
done
for mp3 in *.mp3
do
case "$mp3" in
(Lemon*) mplayer "$mp3";;
esac
done
i=0
find . -name *.mp3 |
while read mp3
do
i=$((i+1))
[ $i = 7 ] && mplayer "$mp3"
done
The read
loop doesn't work if file names contain newlines; the others work correctly even with newlines in the names (let alone spaces). For my money, if you have file names containing a newline, you should rename the file without the newline. Using the double quotes around the file name is key to the loops working correctly.
If you have GNU find
and GNU xargs
(or FreeBSD (*BSD?), or Mac OS X), you can also use the -print0
and -0
options, as in:
find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer
This works regardless of the contents of the name (the only two characters that cannot appear in a file name are slash and NUL, and the slash causes no problems in a file path, so using NUL as the name delimiter covers everything). However, if you need to filter out the first 6 entries, you need a program that handles 'lines' ended by NUL instead of newline...and I'm not sure there are any.
The first is by far the simplest for the specific case on hand; however, it may not generalize to cover your other scenarios that you've not yet listed.
Upvotes: 3