Search code examples
bashshellgnu-findutils

Find file recursively excluding subfolders with specific substring


I need to get all paths which has a specific file:

find apps  -type f -name "project.json"

This returns something like

apps/sub/frontend-e2e/project.json
apps/sub/frontend/project.json
apps/sub/backend/project.json

But I want to exclude all paths with -e2e in the last folder.

I tried something like

find apps  -type f -name "project.json" -not \( -path "*-e2e" -prune \)

Also I need to remove apps/ and /project.json from the beginning and the end of each path. So the result should be:

sub/frontend
sub/backend

In JS I would do

glob.sync('apps/**/project.json', {
    ignore: ['apps/**/*-e2e/project.json']
}).map((path) => {
    // do replacements
})

Solution

  • If you've got Bash 4.3 (released in 2014) or later, try this Shellcheck-clean code:

    shopt -s dotglob extglob globstar nullglob
    for path in apps/**/!(*-e2e)/project.json; do
        p=${path#*/}
        printf '%s\n' "${p%/*}"
    done
    
    • shopt -s ... enables some Bash settings that are required by the code:
      • dotglob enables globs to match files and directories that begin with .. find shows such files by default.
      • extglob enables "extended globbing" (including patterns like !(*-e2e)). See the extglob section in glob - Greg's Wiki.
      • globstar enables the use of ** to match paths recursively through directory trees. This option was introduced with Bash 4.0 but it is dangerous to use in versions before 4.3 because it follows symlinks.
      • nullglob makes globs expand to nothing when nothing matches (otherwise they expand to the glob pattern itself, which is almost never useful in programs).
    • See Removing part of a string (BashFAQ/100 (How do I do string manipulation in bash?)) for an explanation of ${path#*/} and ${p%/*}.
    • See the accepted, and excellent, answer to Why is printf better than echo? for an explanation of why I used printf instead of echo to print the outputs.

    Note that this code will not output anything for the path apps/project.json. It's not clear what you would want to output in that case anyway.