18

Is there a way to write an find so that it breaks if one of the -exec operations fails on a file?

E.g. (javac is conveniently used as a program that can return an exit code of 1 on some files and for no other reason):

$ echo "public classXX A{}" >> A.java
$ echo "public class B{}" >> B.java
$ find . -iname \*.java -exec javac {} \;
./A.java:1: error: class, interface, or enum expected
public classXX A{}
       ^
1 error

In the example above, although the exec on the file A.java failed (and returned an exit code of 1), the find command proceeded and compiled file B.java as well. Is there a way to break out of find or should I use some form of for instead?

My version of find:

$ find -version  | head -1
find (GNU findutils) 4.4.2

4 Answers 4

6

I think...

find ... -exec sh -c 'cmd "$0" || kill $PPID' \{\} \;

...should do the trick for just about any find.

5
  • Thanks @Gilles. What does the $0 do?
    – mikeserv
    Jun 6, 2014 at 0:23
  • 1
    $0 is the first argument to sh -c ($1 is the second one, etc.) — conventionally the name of the program being executed, but here you're executing a snippet, and passing the file name that's being processed as $0 makes some sense (e.g. you easily see it in ps). There's a school of thought (which I obviously don't belong to) that states that $0 should always be executable and you should write sh -c 'cmd "$1" …' sh {} or some such. Jun 6, 2014 at 0:27
  • @Gilles - pretty slick. Thanks a lot. I figured it out in the car on the way to the preschool, but I had to think about it for several minutes. That's smart - I'm gonna use it. What actually clicked it over for me was an edit I remembered reading of Stephane's on one of your own posts in which he added a find-sh $0 to the end of a find statement. This seems more useful in my opinion, though I guess it's what you're after in the long run that matters most.
    – mikeserv
    Jun 6, 2014 at 0:46
  • It's not working for the case above. After find . -iname \*.java -exec sh -c 'javac "${0}" || kill '$$ {} \; file A.java fails and, still, file B.java gets compiled. Jun 6, 2014 at 19:42
  • @MarcusJuniusBrutus - I fixed it. I should've seen your comment earlier. Again, sorry.
    – mikeserv
    Jun 8, 2014 at 0:37
5

At least with GNU find you can use:

find ... -exec ... -o -quit
5
  • I seem to have trouble making it clear where the exec stops and the -o starts. Both find . -iname \*.java -exec javac {} -o -quit \; and find . -iname \*.java -exec javac {} \; -o -quit \; fail (in different ways) Jun 5, 2014 at 17:36
  • @MarcusJuniusBrutus \; is needed at the and of -exec (i.e. before -o -quit in this case) but may be nowhere else. Jun 5, 2014 at 17:47
  • 2
    Using -exec ... -o -quit does not work for me. It does not process any files. However this line works: find . -iname \*.java -not -exec javac '{}' \; -quit: -quit is called if -exec is false, ie. returns with non-zero.
    – savanto
    Jun 5, 2014 at 18:31
  • find . -iname \*.java -exec javac {} \; -o -quit, too, fails to process any files. Strangely, find . -iname \* -exec echo 'foo' \; -o -quit does "process" the files. Jun 5, 2014 at 19:01
  • 1
    find ... \( -exec ... -o -quit \) will prevent quit from running when something in the first ... evaluates to false. Apr 20, 2020 at 23:15
0

Consult your find manual. @Hauke's answer motivated me to look and QNX's find has an -abort option that seems similar to the GNU -quit option. It works for me provided I use \( and \) to group expressions:

find . -mtime -4 -name <pattern> \( -exec my_cmd {} \; -o  -abort \) 

Note: without the parens, it aborts on the first file.

0
find --version
find (GNU findutils) 4.6.0

find . -name '*'   \( -exec bash -c 'echo "$0" ; true' {} \; -o -quit \)

will list everything in current dir

find . -name '*'   \( -exec bash -c 'echo "$0" ; false' {} \; -o -quit \)

will list 1st item found (.) and stop

The commands being executed on the found paths here are

echo "$0" ; true # echoes path and succeeds / returns true

and

echo "$0" ; false  # echoes path and fails / returns false

The expressions being evaluated by find are

-exec bash -c 'echo "$0" ; true/false' {} \; 

and

-quit

-o is logical OR (short-circuiting)

You need \( and \) so the shell doesn't attempt to evaluate the contents before passing everything to find:

find . -name '*'   '(' -exec bash -c 'echo "$0" ; true' {} \; -o -quit ')'

and

find . -name '*'   '(' -exec bash -c 'echo "$0" ; false' {} \; -o -quit ')'

give the same results as the \( \) versions above.

find interprets "( <expression> -o -quit )" as (expr || stop) so the first time expr fails the whole process stops.

NB There does not appear to be any way to pass the/a failing exit code back to find's caller which renders the functionality (-exec {} etc) near useless for scripting purposes.

You must log in to answer this question.

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