如何打印包含两个匹配两种不同模式的文件的目录?
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
那些目录。
我想编写一个 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
那些目录。