查找所有不包含其他目录的目录

5

当前:

$ find -type d
./a
./a/sub
./b
./b/sub
./b/sub/dub
./c/sub
./c/bub

我需要:

$ find -type d -not -contains -type d
./a/sub
./b/sub/dub
./c/sub
./c/bub

如何排除包含其他(子)目录但不为空(包含文件)的目录?


你基本上想要执行 find -type d,但只对树的叶节点感兴趣。这里有一个方法:执行 find -type d|sort 并将结果传递到循环中。每当你有两个连续的行 X 和 Y,并且行 Y 的长度不大于行 X 的长度时,行 X 必须是你感兴趣的目录。 - user1934428
3
您对以下内容感兴趣:find -type d -links 2 ! -empty。(参见(U&L) How to find only directories without subdirectories?)请注意,对于某些文件系统(如BTRFS),此命令可能无法正常工作。 - kvantour
2个回答

5
你可以查找只有2个链接(或更少)的叶目录,然后检查每个找到的目录是否包含一些文件。
类似于这样:
# find leaf directories
find -type d -links -3 -print0 | while read -d '' dir
do
    # check if it contains some files
    if ls -1qA "$dir" | grep -q .
    then
        echo "$dir"
    fi
done

或者简单点:
find -type d -links -3 ! -empty

请注意,在某些文件系统(例如CD-ROM或某些MS-DOS文件系统)上,您可能需要使用-noleaf选项进行查找。但在WSL2中,它可以不用。

文件系统中,目录始终只有1个链接,因此在那里使用-links是行不通的。

一个更慢但与文件系统无关的基于find的版本:

prev='///' # some impossible dir

# A depth first find to collect non-empty directories
readarray -d '' dirs < <(find -depth -type d ! -empty -print0)

for dir in "${dirs[@]}"
do
    dirterm=$dir'/'

    # skip if it matches the previous dir
    [[ $dirterm == ${prev:0:${#dirterm}} ]] && continue

    # skip if it has sub directories
    [[ $(find "$dir" -mindepth 1 -maxdepth 1 -type d -print -quit) != '' ]] && continue

    echo "$dir"
    prev=$dir
done # add "| sort" if you want the same order as a "find" without "-depth"

似乎在WSL中无法使用,因为所有目录都具有"links == 1"的特征。 - Philippe
@Philippe 这可能是WSL1的限制。在WSL2中可以工作。 - Ted Lyngmo
1
这在btrfs上不起作用,因为btrfs中目录的链接计数与经典Unix文件系统不同。 - M. Nejat Aydin
@M.NejatAydin 如果使用“-noleaf”选项,一个空的btrfs目录会显示多少个链接?小于2吗?如果是这样,那么可能会通过我的最新编辑进行修复。 - Ted Lyngmo
我明白了。 我猜在这些情况下,人们必须做更多的工作。 如果 OP 愿意,我在几个小时后回家时可以补充说明。 - Ted Lyngmo
显示剩余6条评论

2

你没有告诉我们这些目录中哪些包含文件,哪些不包含。由于您指定了文件,因此我假设您只想要没有子目录但有文件的目录。

shopt -s dotglob nullglob globstar     # customize glob evaluation
for d in **/                           # loop directories only
do for s in "${d}"*/                   # check subdirs in each
   do [[ -h "$s" ]] || continue 2      # skip dirs with subdirs
   done
   for f in "${d}"*                    # check for nondirs in each
   do echo "$d"                        # there's something here!
      continue 2                       # done with this dir, check next
   done
done
dotglob 选项包括以“点”(.foo)开头的“隐藏”文件。
nullglob 选项使 no*such 返回 nothing 而不是字符串 'no*such'。
globstar 选项使 **/ 匹配任意深度——例如,./x/./x/y/./x/y/z/
for d in **/ 循环遍历所有子目录,包括子目录的子目录,尽管尾随的 / 表示只报告目录,而不是文件。
for s in "${d}"*/ 循环遍历 $d 的所有子目录(如果有)。nullglob 意味着如果没有子目录,则不会执行循环。如果我们看到一个子目录,[[ -h "$s" ]] || continue 2 表示如果它进入了这个循环,符号链接是可以的,但其他任何内容都会使 $d 失效,所以跳过 2 个封闭循环并将顶级推进到下一个目录。

如果到达这一步,就没有无效的真实子目录,因此我们必须确认存在某种类型的文件,即使它们只是指向其他目录的符号链接。 for f in "${d}"* 循环遍历目录中的任何其他内容,因为我们知道没有子目录。由于 nullglob 的原因,如果目录没有任何东西,它甚至不会进入循环,因此如果它进入循环,则其中任何内容都是报告该目录(echo "$d")作为非空的原因。完成后,就没有继续检查的理由了,所以 continue 2 再次将顶级循环推进到要检查的下一个目录!

我希望 **/ 能够工作,但在我的 Windows/Git Bash 模拟器上它无法获取任何子目录。**/*/ 忽略当前目录的子目录,这就是我最初使用 */ **/*/ 的原因,但 **/ 在正确的 Centos VM 上运行时可以避免冗余。请使用它。


“**/”应递归地扩展到目录和子目录。我刚测试过,它按预期工作。 - M. Nejat Aydin
1
这些问题的问题在于OP从未澄清如何处理指向现有目录的符号链接。Ted的答案将这样的符号链接视为常规文件,而您的答案将其视为目录,两者都是合理的。 - oguz ismail
1
这是个公平的决定。我倾向于在这种情况下不包括隐藏文件,除非它们受到明确的请求(毕竟它们是隐藏的 XD)。但这是一个很好的方法来获取它们,对此进行讨论可以增加价值,并且在代码中使用 # 很容易打开/关闭该功能。 - Paul Hodges
1
符号链接和其他文件应被视为常规文件,如 -not -contains -type d 所示(除目录外的所有内容)。之前没有澄清,敬请谅解。 - u15p7fgy863eiq5
编辑以将指向目录的符号链接视为文件条目,而不是实际目录。 - Paul Hodges
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接