4

I want to find all directories and subdirectories in my current folder, excluding those that are hidden (or belonging to those that are hidden). The following do not work:

find . -type d -name "[!.]*"

because I think it only avoids empty hidden folders. Something like this is matched

./.cache/totem
fich
  • 330

4 Answers4

5

I'm assuming you're classing directories that start with a dot as "hidden". To avoid descending into such directories you should use -prune.

find . -mindepth 1 -type d \( -name '.*' -prune -o -print \)

This starts in the current directory (we could have specified * here but that presupposes your wildcard is not set to include dot files/directories - for example bash's dotglob). It then matches only on directories, but not considering . itself. The section in brackets tells find that if the name matches .* then it's to be pruned, so that neither it nor its descendants are to be considered further; otherwise print its name.

If you don't have the (non-POSIX) -mindepth option you could use this alternative. Arguably this is better than the original solution I've suggested but I'm going to leave both in the answer

find . -type d \( -name '.?*' -prune -o -print \)
Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • Perfect. One more question: I don't understand why if I skip the -mindepth 1 part in your line, I get zero matches. – fich Sep 27 '20 at 18:09
  • 6
    @fich Because the current working directory, ., is the first one seen by find. It is matched by -type -d and -name '.*', hence it is pruned, i.e. is not descended into (nor printed, because -prune succeeds). . is at depth 0, -mindepth 1 prevents find from testing it. – fra-san Sep 27 '20 at 18:14
  • -o is an "or" operation, where if the first argument is true the second argument is not evaluated. – NeilG May 29 '23 at 07:09
  • @NeilG yes, that's right. What's the issue? – Chris Davies May 29 '23 at 09:16
  • No issue, @roaima. Information. – NeilG May 30 '23 at 04:51
5

With zsh:

print -rC1 -- **/*(N/)

(zsh globs skip hidden files by default).

Or to do anything with those dirs:

for dir (**/*(N/)) anything with $dir

or, if anything can take more than one file at a time, with GNU xargs or compatible:

xargs -r0a <(print -rNC1 -- **/*(N/)) anything with

POSIXly:

LC_ALL=C find . -name '.?*' -prune -o -type d -print

LC_ALL=C is needed otherwise it would fail to skip hidden dirs whose name contains sequences of bytes that don't form valid character in the user's locale. See also how the order of the predicates makes sure we avoid applying -type d (which potentially involves an extra expensive lstat() system call) on those files whose name starts with ..

That one also outputs . (the current working directory), add a ! -name . before -type if you don't want it or change it to:

LC_ALL=C find . ! -name . \( -name '.*' -prune -o -type d -print \)

Do do anything with the files, replace -print with -exec anything with {} ';' or -exec anything with {} + if anything can take more than one file at once.

2

simply use:

find . ! -path '*/.*' -type d
Kusalananda
  • 333,661
αғsнιη
  • 41,407
  • 1
    Your answer is more simple than roaima's, but it matches . . This is solved with -mindepth 1 as in roaima's answer: find . -mindepth 1 ! -path '*/.*' -type d. Thanks! – fich Sep 27 '20 at 18:08
  • @fich . represent your current directory and not represent that is hidden to exclude; however you could use find * ! -path '*/.*' -type d to avoid that to be reported. – αғsнιη Sep 28 '20 at 05:20
  • If you have shopt dotglob set then * will also match dot files. Using find . -mindepth 1 avoids that – Chris Davies Sep 28 '20 at 08:28
  • It's also missing a list of files/dirs for find to descend on (some find implementations will assume you meant . there but not all). – Stéphane Chazelas Sep 28 '20 at 14:17
  • 1
    While ! -path '*/.*' will exclude hidden files and file in hidden dirs (at least when their names are valid text in the user's locale), it will still descend into hidden directories, so it's less efficient than solutions that prune those hidden directories. – Stéphane Chazelas Sep 28 '20 at 14:19
  • @αғsнιη, my fault not having specified that I wanted . to be avoided. Your new line is very simple (easier to remember) and it works in my case. But due to roaima's and Stéphane's answers, I think it is better to use the -mindepht and -prune options. – fich Sep 28 '20 at 21:29
2

An alternative is the open source tool Fd:

https://github.com/sharkdp/fd

by default hidden items are excluded, so you can simply do:

fd -t d

If you want to unhide items, then you can use one or both of these:

-H, --hidden            Search hidden files and directories
-I, --no-ignore         Do not respect .(git|fd)ignore files
Zombo
  • 1
  • 5
  • 44
  • 63