bashgnu-findutils

Stop recursion of find command if match found


Using GNU findutils, I need to search a directory tree for a certain file. If the file has been found for a given branch, I want to prevent find from recursing further into the branch. Say I want to find the file foo, and this is my directory tree:

├── a
│   ├── a1
│   │   └── foo
│   └── foo
├── b
└── c
    └── foo

Given I am searching the tree above, I want to find a/foo and c/foo. However, I don't want to find a/a1/foo since I already found foo in a parent directory to a1. It seems I should use the -prune flag to the find command and I found this link https://unix.stackexchange.com/questions/24557/how-do-i-stop-a-find-from-descending-into-found-directories, for example, but I cannot make it work. My attempts include:

$ find -name foo -type f -prune
./a/a1/foo <- Unwanted hit
./a/foo
./c/foo

and

$ find -name foo -type f -prune -exec find ../foo -type f {} \;
find: paths must precede expression: ./a/a1/foo
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
find: paths must precede expression: ./a/foo
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
find: paths must precede expression: ./c/foo
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]

Solution

  • This will print the directories that contain foo, and will not recurse in their subdirectories:

    find -type d -exec test -f {}/foo \; -print -prune
    

    The behavior for {}/foo is explicitly left undefined by POSIX:

    If a utility_name or argument string contains the two characters "{}", but not just the two characters "{}", it is implementation-defined whether find replaces those two characters or uses the string without change.

    but works as expected with GNU find (and you tagged the question with ). As Kamil Cuk rightly suggests in the comments, if you're using non-GNU find or if you want a more portable solution, use:

    find -type d -exec sh -c 'test -f "$1"/foo' -- {} \; -print -prune