如何打印包含两个匹配两种不同模式的文件的目录?

How to print directories containing two files matching two different patterns?

我想编写一个 bash 脚本(除其他外)查找 Makefile,其中 Makefile 与 python 文件(看起来像 *.py 的文件)位于同一目录中。

通过执行两个单独的查找并比较输出,我可以通过几个步骤相当不优雅地完成它,但我认为可能有一种方法可以执行一行查找命令?只是好像应该有办法?

所以

path1/Makefile
path1/Some.py
path2/Makefile
path3/Makefile
path3/path4/Makefile
path3/path4/Another.py

path1 将被打印出来。

path2 不会。

path3 不会被打印出来。 b/c python 个文件需要处于同一级别。

path3/path4 将被打印出来。

所以一般的问题是,我可以使用 find 来查找包含至少两个文件的目录,一个匹配一个模式,另一个匹配另一个模式,但是两个模式必须由不同的文件满足吗?

谢谢。

顺便说一句,我正在使用 查找 (GNU findutils) 4.4.2.

但是,我只对人们提出的答案感兴趣。我已经完成了不优雅的解决方案,但很高兴看到。它总是有帮助和教育意义。

find 有一个 -o 标志来组合多个模式。像这样:

find \( [pattern for path1] -o [pattern for path2] \)

假设您只想打印出直接的公共父目录而不是匹配的文件名,那么我认为这会起作用。 (虽然我觉得必须有更好的方法来做到这一点。)

注意:-printf 需要 GNU find

find "$topdir" -name '*.py' -printf '%h[=10=]' | xargs -0 -I {} find {} -mindepth 1 -maxdepth 1 -name Makefile -printf '%h\n' | sort -u

对于非GNU find选项(修改自(未测试):

find . -name '*.py' -exec sh -c 'for file; do d=$(dirname "$file"); test -f "$d"/Makefile && printf "%s\n" "$d"; done' -- {} \+

-exec ; 为每个匹配的文件运行一次命令。 -exec + 将单个命令行与尽可能多的文件组合成单个命令,因此运行 许多 个较少的 shell 实例以获取大量匹配文件。

我想不出单独使用查找条件来做到这一点的方法,但当然你总是可以使用 -exec 来构建这样的东西:

find . -name '*.py' -exec bash -c 'test -f $(dirname "")/Makefile' -- {} \; -print

这将打印在同一目录中具有 Makefile 的 *.py 文件列表。如果在最后一个正斜杠后去掉所有内容,您将只得到包含这些文件的目录,例如通过管道 sed 's:/[^/]*$::'.

此解决方案的优点是只有 运行 一个 find,代价是为找到的每个 .py 文件生成一个 shell。请注意,test 是大多数 shell 中的内置函数。


您也可以单独在 bash 中执行此操作,完全绕过 find

shopt -s globstar
for file in **/./*.py; do
  test "${file%/*}/Makefile" && echo "${file%/*}"
done | uniq

"globstar" 选项允许 **/*.py 搜索子目录,就像 find 那样,通过这个解决方案,我们可以使用参数扩展来代替子目录 shell运行 dirname。正如您所要求的,输出是包含符合您条件的文件的目录列表。如果单个目录中存在多个 *.py 匹配项,则通过 uniq 过滤输出。

根据 Etan 的评论更新:

请注意,为了适应 current(最顶层)目录包含 *.py 和 Makefile 的可能性,glob 是 **/./*.py 而不是 **/*.py。结果导致每个匹配的路径都以点结尾。虽然这仍然会找到所有目标目录(/foo/bar/./foo/bar 相同),但如果它困扰您,您可以通过在 [=21] 之后添加 | sed 's:/\.::' 来去除尾随的点路径段=].添加这样的过滤器将使输出看起来与基于 dirname 的解决方案相同。

用 bash 试试这个:

shopt -s globstar nullglob
for i in **/Makefile; do i="${i%/*}"; x=( "$i"/*.py ); [[ -n ${x[0]} ]] && echo "$i"; done

输出:

path1
path3/path4

我建议在 while read 循环中使用 进程替换 :

shopt -s nullglob

while IFS= read -rd '' dir; do
   ary=("$dir"/*.py)
   [[ -f "$dir"/Makefile && ${#ary[@]} -gt 0 ]] && echo "$dir"
done < <(find . -type d -print0)

find 命令将从当前目录和 while 循环中查找所有目录,我们正在检查每个目录中是否存在 *.py 文件和 Makefile那些目录。