With GNU find
:
find /music -type d -execdir test -d {}/{} \; -print
For each file of the type directory test
will be run. The trick is in how -execdir
differs from -exec
: for a directory named something
, test
will be run in its parent directory and {}
will be expanded to ./something
. In effect the actual inside command will be test -d ./something/./something
, equivalent to test -d something/something
. Then we use the fact that -execdir … \;
(like -exec … \;
) is not only an action, it is a test that succeeds iff the exit status from whatever it runs is 0. test -d
checks if the given pathname is the name of a directory, it reports success via its exit status.
This way -print
will be evaluated only (almost only, see just below) for directories containing directories of the same name.
Notes:
test -d
succeeds also for a symlink to a directory. You can add! -execdir test -L {}/{} \;
(before-print
) to ignore cases wheresomething/something
is a symlink.In general remember that
{}
expands to whatfind
considers the pathname of the currently processed file, not necessarily to what a command likerealpath …
would print. Similarly-name
tests whatfind
considers the name, not necessarily to whatbasename "$(realpath …)"
would print. E.g. if the starting point is/music
then at some point our command will test./music/./music
inside/music/..
, so effectively/music/music
which may or may not be an existing directory and the test is conclusive, which is fine. But:If the (or a) starting point was
.
(or e.g./music/.
!), then it would be reported becausetest -d
would test./././.
which is a directory for sure (regardless of the working directory oftest
), so the test is not conclusive.! -name .
is a test that "fixes" this, i.e. it unconditionally excludes.
. But now we can miss a directory: if the current working directory is/music
and/music/music
exists, we won't detect it.In general passing
.
as a starting point tofind
is quite common. In our case, if you need to start at.
, considerfind "$PWD" …
instead.Similarly if the (or a) starting point was
..
(or e.g./music/artist/..
), then it would be reported becausetest -d
would test./.././..
which is a directory for sure (regardless of the working directory oftest
).! -name ..
is a test that "fixes" this, but again we can miss one specific directory. In general passing..
as a starting point tofind
is uncommon, still possible.
If the starting point is
/music
then there will be no problem.The successful detection occurs when
find
processes the upper (closer to/
) directory from eachsomething/something
pair, not the lower one. This has the following consequences:Our command will print paths like
/music/foo/bar/something
(as opposed to/music/foo/bar/something/something
)For
-mindepth
or-maxdepth
(if you decide to use them), the depth of the upper directory matters.If you want to automate doing something to one of the directories of the pair:
- work with the upper one by passing
{}
after-exec
or-execdir
; - but the easiest way to address the lower one is to use
{}/{}
after-execdir
like we did; in general this will not work after-exec
.
- work with the upper one by passing