你在正确的轨道上。问题是,您对“此目标中是否不存在以下目录”的测试无法在 find 的条件中以仅返回顶级目录的方式表示。所以你需要以一种或另一种方式嵌套。
一种策略是在 bash 中使用 for 循环:
$ mkdir foo bar baz one two
$ mkdir bar/bin baz/bin
$ for d in /home/*/; do find "$d" -type d -name bin | grep -q . || echo "$d"; done
foo/
one/
two/
这使用路径名扩展(globbing)来生成要测试的目录列表,然后检查“bin”是否存在。如果该检查失败(即 find 没有输出),则打印该目录。请注意/home/*/ 上的尾部斜杠,它确保您只会在目录中搜索,而不是在/home/ 中可能意外存在的文件。
如果您不想依赖 bash,另一种可能是使用嵌套的 finds:
$ find /home/ -type d -depth 1 -not -exec sh -c "find {}/ -type d -name bin -print | grep -q . " \; -print
/home/foo
/home/one
/home/two
这大致复制了上面 bash for 循环的效果,但是通过将 find 嵌套在 find -exec 中。它使用grep -q . 将find 的输出转换为可用作外部 find 的条件的退出状态。
请注意,由于您正在寻找 bin 目录,我们希望使用 test -d 而不是 test -e(它也会检查 bin 文件 ,这对你来说可能无关紧要。)
另一种选择是使用 bash 进程重定向。多行以便于阅读:
cd /home/
comm -3 \
<(printf '%s\n' */ | sed 's|/.*||' | sort) \
<(find */ -type d -name bin | cut -d/ -f1 | uniq)
不幸的是,这需要您在运行前更改到/home 目录,因为它会剥离子目录。如果您愿意,当然可以将其折叠成一条大而长的单线。
这个comm 解决方案还存在在名称中包含特殊字符(如换行符)的目录上失败的风险。
最后一个选项是 bash-only,但不仅仅是单线。它涉及从完整列表中减去包含“bin”的目录。它使用关联数组和globstar,因此它依赖于 bash 版本 4。
#!/usr/bin/env bash
shopt -s globstar
# Go to our root
cd /home
# Declare an associative array
declare -A dirs=()
# Populate the array with our "full" list of home directories
for d in */; do dirs[${d%/}]=""; done
# Remove directories that contain a "bin" somewhere inside 'em
for d in **/bin; do unset dirs[${d%%/*}]; done
# Print the result in reproducible form
declare -p dirs
# Or print the result just as a list of words.
printf '%s\n' "${!dirs[@]}"
请注意,我们将目录存储在数组 index 中,这 (1) 使我们可以轻松查找和删除项目,以及 (2) 确保唯一的条目,即使一个用户拥有其主目录下有多个“bin”目录。