6

How can I find a directory with a certain name but only if it is in another directory with a certain name? For example when I have the following directory structure

a
├── b
│   └── e
├── c
│   └── e
└── d

I'd like to find the directory 'e', but only when it is located in a directory called 'c'. If possible with the find command alone without using grep.

simon
  • 289

4 Answers4

19

Use GNU find with -path that searches the entire path for a match:

$ find . -path '*/c/e'
./a/c/e

That will match any file or directory called e which is in a directory called c.

Alternatively, if you don't have GNU find or any other that supports -path, you can do:

$ find . -type d -name c -exec find {} -name e \;
./a/c/e

The trick here is to first find all c/ directories and then search only in them for things called e.

terdon
  • 242,166
  • -path is specified in POSIX find. – Quasímodo Nov 01 '20 at 14:34
  • 2
    Ah, thanks @Quasímodo. I had checked https://pubs.opengroup.org/onlinepubs/009695399/utilities/find.html which didn't have it and hadn't realized that the more recent version of POSIX includes it. – terdon Nov 01 '20 at 14:49
  • 4
    The latter would find e anywhere within c, right? Not just immediately below. I guess the same could be done with -path with something like -path '*/c/*/e' -o -path '*/c/e'. (Can't seem to do it with just one pattern.) – ilkkachu Nov 01 '20 at 20:39
  • @ilkkachu yes, good point. The second will find e anywhere in a directory tree under c. – terdon Nov 02 '20 at 09:23
  • 2
    For an emulation of -maxdepth 1 in POSIX find see this answer. – Ruslan Nov 02 '20 at 15:53
  • @Ruslan why would we want maxdepth 1 here? The whole point of this question is to make it recursive. If we wanted -maxdepth 1 we could just do echo */c/e. – terdon Nov 02 '20 at 15:54
  • Ah, I was trying to find how to address ilkkachu's comment, and erroneously though that -maxdepth 1 is the solution. – Ruslan Nov 02 '20 at 15:58
  • @Ruslan ah, I see. You may be right, actually. There might be some -prune magic to be done here, but I admit I never use -prune so I don't really know how to. – terdon Nov 02 '20 at 16:09
  • maybe, for the non-gnu solution: find . -name 'c' -exec find '{}/e' -type d \( -name 'e' -ls -o -prune \) \; 2>/dev/null : it seems to work on my machine (to test, I also added files under each directories as otherwise some solutions would list also files under any "c/e" subdirs). to test: in a subdir: mkdir -p a b c a/b a/c a/c/e a/c/d/e a/c/d/e/c/e/f/g ; for i in $(find */ -type d -ls); do ( cd "$i" && touch a b c d e ) ; done (creates wherever it can up to five files under each subdir, except those matching a dir name) (I corrected the previous comment) – Olivier Dulac Nov 02 '20 at 17:04
  • @OlivierDulac sounds like you have the makings of an answer there. Shame to let it be lost in the comments! – terdon Nov 02 '20 at 17:07
  • @terdon thanks, I added an answer with this. I thought you could modify yours (as I stole the idea from you ^^) – Olivier Dulac Nov 02 '20 at 17:16
  • @OlivierDulac -prune is deep dark magic for me, I wouldn't dare write an answer with it! :) – terdon Nov 02 '20 at 17:17
5

Since you have tagged Bash, an alternative is to use globstar:

shopt -s globstar # Sets globstar if not already set
# Print the matching directories
echo **/c/e/
# Or put all matching directories in an array
dirs=(**/c/e/)
Quasímodo
  • 18,865
  • 4
  • 36
  • 73
2

In addition to @terdon 's solution, I provide here an alternative version for those without GNU find (that I only could find by following his idea!):

find . -type d -name 'c' -exec find '{}/e' -type d \( -name 'e' -ls -o -prune \) \; 2>/dev/null 

This seems to work on my machine

To test:

# add files under each directories as otherwise some solutions would 
# list also files under any "c/e" subdirs ... 
# in a subdir : do  : 
mkdir -p a b c a/b a/c a/c/e a/c/d/e a/c/d/e/c/e/f/g
for i in $(find */ -type d -ls); do ( cd "$i" && touch a b c d e ) ; done 
# this will creates several files under each subdirs, wherever it can (ie, when they don't match a subdir's name).
# then test:
find . -type d -name 'c' -exec find '{}/e' -type d \( -name 'e' -ls -o -prune \) \; 2>/dev/null 
# and it answers only the 2 matching subdirs that match name "c/e":
inode1 0 drwxr-xr-x   1 uid  gid   0 nov.  2 17:57 ./a/c/e
inode2 0 drwxr-xr-x   1 uid  gid   0 nov.  2 18:02 ./a/c/d/e/c/e
0

Using the Fd tool:

fd -t d --full-path /c/e$

https://github.com/sharkdp/fd

Zombo
  • 1
  • 5
  • 44
  • 63