find: -exec vs xargs (aka Why does "find | xargs basename" break?)

20,041

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.

Share:
20,041
quack quixote
Author by

quack quixote

Updated on September 17, 2022

Comments

  • quack quixote
    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 with xargs:

    $ 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
    akira over 14 years
    this 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
    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
    quack quixote over 14 years
    oooh.... /slaps forehead again/ i think i need a "find for dummies" book...
  • Ryan C. Thompson
    Ryan C. Thompson over 14 years
    This 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
    WindowsMaker over 7 years
    i 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 and find . -name '*.deb' | basename ?
  • bishop
    bishop about 7 years
    GNU basename now has a -a option: "support multiple arguments and treat each as a name".
  • 8bittree
    8bittree over 6 years
    @WindowsMaker xargs converts stdin to command arguments. In a way, it's the opposite of echo, which converts its arguments to stdout. The difference between find ... | xargs -n1 basename or find ... | xargs basename -a and find ... | basename is that the former two will work with implementations of basename that ignore stdin.
  • soMuchToLearnAndShare
    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