【问题标题】:How to print directories containing two files matching two different patterns?如何打印包含匹配两种不同模式的两个文件的目录?
【发布时间】:2016-01-11 13:48:54
【问题描述】:

我想编写一个 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。

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

【问题讨论】:

    标签: bash unix find


    【解决方案1】:

    我想不出单独使用 find 条件的方法,但当然你总是可以使用-exec 来构造这样的东西:

    find . -name '*.py' -exec bash -c 'test -f $(dirname "$1")/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 一样,通过这个解决方案,我们可以使用参数扩展来代替运行dirname 的子shell。正如您所要求的,输出是包含符合您的条件的文件的目录列表。如果单个目录中存在多个 *.py 匹配项,则通过 uniq 过滤输出。

    根据 Etan 的评论更新:

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

    【讨论】:

    • 如果您的 find-exec \+ 可能有用,因为您可以循环内联脚本中的参数并单独测试每个参数,然后直接从那里进行打印(并跳过 -print )。
    • 很好的建议。在 FreeBSD 中运行良好,但我将保留这个答案,因为我不知道 -exec ... + 的背景。
    • 在我的答案中添加了我建议修改的未经测试的版本。
    • 这不是和find . -name '*.py' -exec test -f $(dirname {})/Makefile \; -print一样吗?
    • @JeffY - 不,不是。如果 $(..) 在评估 find 命令行时由 shell 扩展,则不会为第一个条件找到的每个 '*.py' 实例扩展它。您需要在子shell 中运行dirname 才能使其工作,因为$(..) 是shell 语法,而不是/bin/test 的选项。 -exec 选项搜索 $PATH 以查找要运行的命令,它不知道调用 shell 中的内置函数。
    【解决方案2】:

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

    注意:-printf 需要 GNU find

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

    对于非GNU find 选项(修改自ghoti's handiwork(未经测试):

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

    -exec ; 为每个匹配的文件运行一次命令。 -exec + 将包含尽可能多的文件组合到单个命令行中,以便在单个命令中运行 许多 较少的 shell 实例以获取大量匹配文件。

    【讨论】:

    • 我的find 没有-printf 选项。你使用的是和海报一样的操作系统吗?
    • @ghoti 他没有具体说明,所以我不知道。我没有意识到-printf 是一个 GNU 选项(尽管我可能应该有)。
    • 尝试了最后一次编辑,似乎不起作用,似乎一遍又一遍地列出同一个目录。
    • @Bitdiot 抱歉,忘记将 $1 更改为 $file。再试一次?
    • 试过了。作品!极好的。喜欢解决方案。我想在这里给每个正确的答案打勾。我会把它给最受欢迎的?希望你不要介意。
    【解决方案3】:

    我建议在while read 循环中使用进程替换 来使用这个find

    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

    【讨论】:

    • 你为什么会建议这个?该解决方案对 ghoti 和 Cyrus 的 bash 解决方案有何改进?
    • 因为 1. 它避免了为子目录中的每个 python 文件创建一个子 shell。 2. 由于globstar 是在 BASH 4 中引入的,因此也可以与旧 bash 版本 (
    【解决方案4】:

    用 bash 试试这个:

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

    输出:

    路径1 路径 3/路径 4

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 这将找到与 either 谓词匹配的文件,但不会找到包含与 both 匹配的文件的目录。
      • @EtanReisner 啊,我明白了。对不起,我完全误解了你的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-08
      • 2015-04-14
      • 2018-07-12
      • 2014-08-28
      • 2016-10-09
      • 2017-07-30
      • 2020-04-16
      相关资源
      最近更新 更多