14

How can I run a subcommand inside find's exec? For example, if I want to get the filename only from the full path and print it, I would fire,

find ./ -name "*.csv" -exec echo $(basename {}) \;

where the echo is parent command of child command basename.

But the result is same as this,

find ./ -name "*.csv" -exec echo {} \;

What should I do ?

Brad Koch
  • 19,267
  • 19
  • 110
  • 137
JohnG
  • 807
  • 1
  • 12
  • 20

3 Answers3

23

This is what you are looking for:

find . -name "*.csv" -exec sh -c 'echo $(basename "$1")' sh {}  \;
jlliagre
  • 29,783
  • 6
  • 61
  • 72
  • 1
    I don't get the point with `$1` and the second `sh`. Why not use `find . -name "*.csv" -exec sh -c 'echo $(basename "$0")' {} \;`. This works for me perfectly. – Tik0 Dec 17 '17 at 22:00
  • 3
    @Tik0 The point is consistency. Using $0 would indeed work here but doing it breaks the convention of $0 meaning the name of the running process name. Top or ps would, for example, report odd command names. More complex structures like a for loop would fail. – jlliagre Dec 17 '17 at 22:39
  • 4
    ah ok got it, so the second `sh` is just a placeholder to keep the convention that the called program is `sh` (s.t. the first one). – Tik0 Dec 19 '17 at 14:24
  • @jlliagre Whats for loop has to do with "$0"? – Talespin_Kit Aug 23 '22 at 12:09
  • @Talespin_Kit A `for` loop rightly ignores `$0`. That's my point. – jlliagre Aug 23 '22 at 18:36
  • I see. "$@" does not include "$1". Example bash for loop here https://stackoverflow.com/a/255913/579689. Thanks – Talespin_Kit Aug 24 '22 at 11:32
  • @Talespin_Kit More precisely, "$@", $* or the default `for` parameters do not include **$0**, they start at $1. – jlliagre Aug 24 '22 at 13:22
0

As always, invoke a shell to run it.

find ... -exec bash -c "... $1 ..." subshell {} \;
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

@Ignacio's is strictly correct, albiet biased towards a specific shell instead of suggesting a POSIX standard shell....

However, what you're trying to do is much more efficiently done with a filter. I.e. you're trying to find a way to have find transform each pathname it would be about to print, but that causes find to fork a shell process for every filename it matches!

To be more efficient you should instead try to find a way to transform the list of pathnames that find might produce into the form you would like to see them in, and to do so with only one additional process.

For example if all you want to see is the base name (i.e. the final filename component) of each pathname then you can do that trivially with a (relatively) simple sed command that you could pipe your find output through to produce the desired list of filenames:

sed '/^[^/]*$/p;s/.*\/\([^/]*\)$/\1/p'
Greg A. Woods
  • 2,663
  • 29
  • 26
  • well greg, thnx for the reply. I do want to use the base name as first argument of some other command which should run inside the -exec body of find. any other way u suggest ?? – JohnG Dec 12 '12 at 06:55
  • Well, it depends on what the command is, and how it will operate on just the base filename (especially without knowing where the file is actual file is located). If the command can accept multiple filenames as parameters all at once then you could pipe the output of `sed` to `xargs other_command`, for example. If you really do need to invoke the command separately for each name (why?) then Ignacio's method may be best, though you can save a shell invocation per name by starting the command from the parent shell with a `while read fname; do` loop after the `sed`. – Greg A. Woods Dec 12 '12 at 19:32