find: -exec vs xargs (aka Why does "find | xargs basename" break?)
Solution 1
Because basename
wants just one parameter... not LOTS of. And xargs
creates a lot of parameters.
To solve your real problem (only list the filenames):
find . -name '*.deb' -printf "%f\n"
Which prints just the 'basename' (man find):
%f File's name with any leading directories
removed (only the last element).
Solution 2
Try this:
find . -name '*.deb' | xargs -n1 basename
Solution 3
xargs
can be forced to just pass one argument as well...
find . -name '*.deb' -print | xargs -n1 basename
This works, however the accepted answer is using find
in a more appropriate way. I found this question searching for xargs basename
problems as I'm using another command to get a list of file locations. The -n1
flag for xargs
was the ultimate answer for me.
Solution 4
basename only accepts a single argument. Using -exec
works properly because each {}
is replaced by the current filename being processed, and the command is run once per matched file, instead of trying to send all of the arguments to basename in one go.
quack quixote
Updated on September 17, 2022Comments
-
quack quixote over 1 year
I was trying to find all files of a certain type spread out in subdirectories, and for my purposes I only needed the filename. I tried stripping out the path component via
basename
, but it did't work withxargs
:$ find . -name '*.deb' -print | xargs basename basename: extra operand `./pool/main/a/aalib/libaa1_1.4p5-37+b1_i386.deb' Try `basename --help' for more information.
I get the same thing (exactly the same error) with either of these variations:
$ find . -name '*.deb' -print0 | xargs -0 basename $ find . -name '*.deb' -print | xargs basename {}
This, on the other hand, works as expected:
$ find . -name '*.deb' -exec basename {} \; foo bar baz
This happens on up-to-date Cygwin and Debian 5.0.3. My diagnosis is that xargs is for some reason passing two input lines to basename, but why? What's going on here?
-
akira over 14 yearsthis is not the explanation, this is a workaround. and the workaround is as good as just calling 'basename' via -exec for any file found.
-
quack quixote over 14 years+1 ... while not an explanation, this would lead me to investigate the xargs switch you show, which would eventually lead me to the forehead-slapping motion i just used reading akira's and john t's answers...
-
quack quixote over 14 yearsoooh.... /slaps forehead again/ i think i need a "find for dummies" book...
-
Ryan C. Thompson over 14 yearsThis is how I do it. I don't feel like learning all the ins and outs of the
find
command, so I only use it for finding and listing files, and I use xargs for everything else. -
WindowsMaker over 7 yearsi thought the point of
xargs
is that it creates a list of arguments and feeds each to the command that comes after? otherwise what's the difference between that andfind . -name '*.deb' | basename
? -
bishop about 7 yearsGNU basename now has a
-a
option: "support multiple arguments and treat each as a name". -
8bittree over 6 years@WindowsMaker
xargs
convertsstdin
to command arguments. In a way, it's the opposite ofecho
, which converts its arguments tostdout
. The difference betweenfind ... | xargs -n1 basename
orfind ... | xargs basename -a
andfind ... | basename
is that the former two will work with implementations ofbasename
that ignorestdin
. -
soMuchToLearnAndShare almost 4 years@WindowsMaker, if my understanding is that you use
-I {}
xargs option, like this| xargs -I {} basename -s .json {} |
, it will feed each argument separately