11

@StephaneChazelas posted the following solution to this Q&A: Having some trouble using “find -exec {} +”.

$ find . -iname "*.extension" -exec sh -c '
  exec <command> "$@" <additional parameters>' sh {} +

What exactly is going on here? Specifically what does the last sh {} do? It seems as though it's just there to pacify find's -exec command so that it has something to do, a NOOP.

I could just as easily put echo {} there and it appears to work just fine.

1 Answer 1

12

The syntax is:

find ... -exec cmd {} +

find will find a number of files based on the criteria in ... and run cmd with that list of file paths as arguments, as many as possible without surpassing the limit on the size of the arguments to a command.

If needed it may split the list of files and call cmd several times. For instance, it may end up calling:

cmd ./file1 ./file2 ... ./file3000
cmd ./file3001 ./file3002 ... ./file4321

A limitation with that is that {} has to be last. You can't for instance write:

find ... -exec cmd {} other args +

like you could with ';' instead of '+'.

You can write:

find ... -exec echo foo {} +

but not:

find ... -exec echo {} foo +

So, if you do need to add some extra arguments to cmd after the list of files, you have to resort to calling a shell. (Other reasons why you'd need to call a shell would be any time you need to use a shell feature like redirections, pipes, some string expansions....)

In sh -c 'inline-script' x a b c, for the inline-script, $0 is x, $1 is a, $2 is b... so "$@" is the list of those 3 arguments: a, b and c. So in:

find ... -exec sh -c 'cmd "$@" other arg' find-sh {} +

For the inline script, $0 (which is used for instance when displaying error messages) is set to find-sh and "$@" is the list of files (what find expands {} to).

By using the exec special builtin of the shell:

find ... -exec sh -c 'exec cmd "$@" other arg' find-sh {} +

We tell the shell not to fork an extra process to run cmd, but instead to run it in the same process (replacing the running shell process with that command). Some shells like zsh and some implementations of ksh do that implicitly for the last command in an inline script (also bash when there's only one command in the inline script like here).

3
  • Could you use a subshell instead of exec there? -exec sh -c '(cmd1; cmd2;)' find-sh {} +?
    – slm
    Commented Oct 2, 2013 at 19:13
  • So if I understand you correctly, the find-sh {} are arguments to the sh -c '...' command, right?
    – slm
    Commented Oct 2, 2013 at 19:22
  • @slm, find will call /bin/sh with as arguments ("sh", "-c", "...", "find-sh", "./file1", "./file2"...). And inside ..., that maps (the shells maps that) to $0` being "find-sh", and the positional parameters ($1, $2... which you could say are the arguments to the inline script) being ./file1, ./file2. Commented Oct 2, 2013 at 19:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .