12

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?

quack quixote
  • 42,186
  • 14
  • 105
  • 129

5 Answers5

26

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).
Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
akira
  • 61,009
  • 17
  • 135
  • 165
  • 1
    oooh.... */slaps forehead again/* i think i need a "find for dummies" book... – quack quixote Oct 08 '09 at 16:17
  • 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` ? – WindowsMaker Feb 01 '17 at 00:14
  • 1
    GNU basename now has a `-a` option: "support multiple arguments and treat each as a name". – bishop Mar 01 '17 at 18:35
  • 1
    @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`. – 8bittree Dec 18 '17 at 18:36
  • @WindowsMaker, if my understanding is that you use ```-I {}``` xargs option, like this ```| xargs -I {} basename -s .json {} | ```, it will feed each argument separately – soMuchToLearnAndShare Jun 17 '20 at 09:15
22

Try this:

find . -name '*.deb' | xargs -n1 basename
perlguy9
  • 389
  • 1
  • 5
  • 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. – akira Oct 08 '09 at 16:07
  • 4
    +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 Oct 08 '09 at 16:14
  • 1
    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. – Ryan C. Thompson Oct 08 '09 at 22:52
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.

John T
  • 163,373
  • 27
  • 341
  • 348
4

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.

Peter Mortensen
  • 12,090
  • 23
  • 70
  • 90
Flet
  • 141
  • 1
0

The reason is that by default xargs does not use just one input item to build one command invocation.

In fact, xargs uses as many input items as possible every time it executes the command, where the max number of items used to build one command invocation is a system-defined value and can be overwritten by the -n or -L options. For example, if you run echo a b | xargs ls, instead of executing ls twice with a and b respectively, xargs will build and run a single command ls a b.

So for a command that accepts only one parameter (like basename), you should use the -n or -L option if you want to stick to xargs.

ebk
  • 101
  • 1