5

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]
Eric Lilja
  • 3,323
  • 4
  • 18
  • 15
  • Why isn't `./a/foo` the unwanted hit? Are we interested in that hit that is less deeper? What if `mkdir -p a/a1 a/b1; touch a/a1/foo a/b1/foo`. Then is `a/a1/foo` or `a/b1/foo` the unwanted hit? Or it doesn't matter, at least one should do it, no matter where? – KamilCuk Feb 07 '19 at 18:21
  • @KamilCuk as I understood, in the example you give, both `a/a1/foo` and `a/b1/foo` should be returned. – gniourf_gniourf Feb 07 '19 at 18:25

2 Answers2

9

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
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
  • 1
    And i think we can even get away from posixly undefined by just invoking the shell and passing path by parameter `-exec sh -c 'test -f "$1"/foo' -- {} \;`. – KamilCuk Feb 07 '19 at 18:31
  • @chepner: that wouldn't work: the `-prune` predicate must be applied to a directory. Regarding the output, it's quite easy to modify the statement to append `/foo` to it. – gniourf_gniourf Feb 07 '19 at 19:07
  • Oops, for some reason in my tests I was making directories instead of regular files as the candidate matches, so `-prune` was coincidentally working for me. – chepner Feb 07 '19 at 19:12
2

it can't be done easily with find -prune because it works on directories and find's basic conditions are over current file.

an alternative could be to do it with bash programmatically, using recursive function, basically

rec_run() {
    local file
    for file in "${1:-.}"/*; do
        # to filter 'file=*' when no match is found
        if [[ ! -e $file ]]; then
            continue
        fi

        # do something with file
        echo "$file"

        # to filter known hard links
        if [[ $file = */. ]] || [[ $file = */.. ]]; then
            continue
        fi

        # if is a directory recursive call
        if [[ -d $file ]]; then
            rec_run "$file";
        fi
    done
}

and changing the part # do something with

    if [[ -f $file/foo ]]; then
        echo "$file/foo"
        continue
    fi

here foo is hardcoded but could be passed as second function argument

Note ${1:-.} is to take the first argument as root directory or . if not passed

Nahuel Fouilleul
  • 18,726
  • 2
  • 31
  • 36