find: missing argument to `-exec' when using + form of find

3

I want to execute a command on the paths found with the find command and I want to use + to reduce the number of times the external command is launched.

Critically, the command has a fixed value final argument. Here's a concrete example with echo as the command, although in the real :

mkdir blah && cd blah
touch fooA
touch fooB
find . -name 'foo*' -exec echo {} second +

I would expect this to print:

./fooA ./fooB second

but instead I get the error find: missing argument to-exec'. I've tried all sorts of permutations to get it to work with +. Why isn't this working? It works find with the\;` variant:

find . -name 'foo*' -exec echo {} second \;
./fooB second
./fooA second

... but that's not what I'm after.

find --version reports:

find (GNU findutils) 4.4.2
Copyright (C) 2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Eric B. Decker, James Youngman, and Kevin Dalley.
Built using GNU gnulib version e5573b1bad88bfabcda181b9e0125fb0c52b7d3b
Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS() CBO(level=0) 

Here's an excerpt from the man pages covering the + and ; forms:

   -exec command ;
          Execute  command;  true  if 0 status is returned.  All following arguments to find are taken to be arguments to the command until an argument consisting of `;' is encoun‐
          tered.  The string `{}' is replaced by the current file name being processed everywhere it occurs in the arguments to the command, not  just  in  arguments  where  it  is
          alone,  as  in  some  versions of find.  Both of these constructions might need to be escaped (with a `\') or quoted to protect them from expansion by the shell.  See the
          EXAMPLES section for examples of the use of the -exec option.  The specified command is run once for each matched file.  The command is executed in  the  starting  direc‐
          tory.   There are unavoidable security problems surrounding use of the -exec action; you should use the -execdir option instead.

   -exec command {} +
          This  variant  of  the  -exec  action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the
          total number of invocations of the command will be much less than the number of matched files.  The command line is built in much the same way that xargs builds its  com‐
          mand lines.  Only one instance of `{}' is allowed within the command.  The command is executed in the starting directory.

BeeOnRope

Posted 2015-03-16T23:44:51.647

Reputation: 744

What shell are you using? – roaima – 2015-03-16T23:59:01.553

According to the man page the -exec command expects the {} at the very end of the expression since it tries to concatenate the files found. Putting the second before the {} will work but, of course, that does not really help you. The error message is a little misleading. – Marcus Rickert – 2015-03-17T00:02:04.133

Where in the man do you see that? I included the man output on my system in question. For the \; form it is clear that multiple {} are allowed, implying they don't have to be at the end of the command. The + form says the command line is built by appending each selected file name at the end - but I read "the end" here as the end of the string which will ultimately be used to replace the {}. If it was always at the end, why use {}? The text Only one instance of{}' is allowed within the command` imlies that one instance can be anywhere (or else why not restrict it)? – BeeOnRope – 2015-03-18T23:48:55.853

Answers

1

Here is a similar solution with find | xargs:

find . -name 'foo*' -print0 | xargs -0 -n1 -I{} echo {} second

7yl4r

Posted 2015-03-16T23:44:51.647

Reputation: 113

1

I couldn't find a solution with find -exec + or find | xargs, but GNU Parallel can do the job.

mkdir blah && cd blah
touch fooA
touch fooB
find . -name 'foo*' -print0 | parallel -0 -j1 -X echo {} second

Produces:

./fooA ./fooB second

Note, the -j1 option limits parallel to one job at a time, which is only used here in an attempt to reproduce the behavior that would have been expected from find -exec +.

zackse

Posted 2015-03-16T23:44:51.647

Reputation: 532