【问题标题】:recursively list files that contain m of n regex递归列出包含 m of n 正则表达式的文件
【发布时间】:2020-10-12 09:04:03
【问题描述】:

我有一个包含很多文件的目录。我有 n 搜索模式,并想列出与其中 m 匹配的所有文件。

示例:从下面的文件中,列出至少包含 两个str1str2str3str4

$ ls -l dir/
total 16
-rw-r--r--. 1 me me 10 Jun 22 14:22 a
-rw-r--r--. 1 me me  5 Jun 22 14:22 b
-rw-r--r--. 1 me me 10 Jun 22 14:22 c
-rw-r--r--. 1 me me  9 Jun 22 14:22 d
-rw-r--r--. 1 me me 10 Jun 22 14:22 e
$ cat dir/a
str1
str2
$ cat dir/b
str2
$ cat dir/c
str2
str3
$ cat dir/d
str
str4
$ cat dir/e
str2
str4

我设法通过在find 结果上产生n grep 进程的相当丑陋的for 循环来实现这一点,这显然是超级低效的,并且在包含大量文件的目录上会花费很长时间:

for f in $(find dir/ -type f); do
  c=0
  grep -qs 'str1' $f && let c++
  grep -qs 'str2' $f && let c++
  grep -qs 'str3' $f && let c++
  grep -qs 'str4' $f && let c++
  [[ $c -ge 2 ]] && echo $f
done

我很确定我可以以更好的方式实现这一目标,但我不知道如何解决它。根据我从手册页(即-e-m)中了解到的信息,仅使用grep 是不可能的。

什么是合适的工具? awk 可以做到这一点吗?

奖励:通过使用find,我可以更精确地定义要搜索的文件(即-prune 某些子目录或仅搜索带有-iname '*.txt' 的文件),我也想使用其他解决方案。


更新

关于以下不同实现的性能的一些统计数据。


find + awk

(来自this答案的脚本)

real    0m0,006s
user    0m0,002s
sys     0m0,004s

python

(我是python noob,请告知是否可以优化):

import os

patterns = []
patterns = ["str1", "str2", "str3", "str4"]

for root, dirs, files in os.walk("dir"):
    for file in files:
        c = int(0)
        filepath = os.path.join(root, file)
        with open(filepath, 'r') as input:
            for pattern in patterns:
                for line in input:
                    if pattern in line:
                        c += 1
                        break
        if ( c >= 2 ):
            print(filepath)
real    0m0,025s
user    0m0,019s
sys     0m0,006s

c++

(来自this答案的脚本)

real    0m0,002s
user    0m0,001s
sys     0m0,001s

【问题讨论】:

  • 您发布的计时结果是否是第三次运行计时以消除结果中的缓存影响?我假设脚本需要 0.002s 还是 0.006s 对你来说并不重要,因为它们都在视线范围内 - 如果你有更大的文件,性能很重要,你可以使用这些来测试时间吗?此外,您正在将具有硬编码 str1 等的 C++ 程序与从文件中读取值的 awk 程序进行比较——这显然不是苹果对苹果的比较。

标签: regex bash search awk grep


【解决方案1】:
$ cat reg.txt
str1
str2
str3
str4
$ cat prog.awk
# reads regexps from the first input file
# parameterized by `m'
# requires gawk or mawk for `nextfile'
FNR == NR {
  reg[NR] = $0
  next
}
FNR == 1 {
  for (i in reg)
    tst[i]
  cnt = 0
}
{
  for (i in tst) {
    if ($0 ~ reg[i]) {
      if (++cnt == m) {
        print FILENAME
        nextfile
      }
      delete tst[i]
    }
  }
}
$ find dir -type f -exec awk -v m=2 -f prog.awk reg.txt {} +
dir/a
dir/c

【讨论】:

  • 这就像一个魅力,谢谢。我仍在努力使用awk 语法。你介意解释一下脚本吗?
  • @David 解释语法会浪费时间,因为它都写在手册中。
【解决方案2】:

由于编程语言并不像性能那么重要,这里有一个 C++ 版本。不过,我自己并没有将它与awk 进行比较。

#include <cstddef>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace fs = std::filesystem;

int main() {
    const fs::path dir = "dir";
    std::vector<std::string_view> strs{   // or: std::array<std::string_view, 4>
        "str1",
        "str2",
        "str3",
        "str4",
    };

    std::string line;
    int count;     // matches in a file
    size_t strsco; // number of strings to check in strs

    // a lambda to find a match on a line
    auto matcher = [&](const fs::directory_entry& de) {
        for(size_t idx = 0; idx < strsco; ++idx) {
            if(line.find(strs[idx]) != std::string::npos) {
                // a match was found

                if(++count >= 2) {
                    std::cout << de.path() << '\n';
                    // or the below if the quotation marks surrounding the path are
                    // unwanted:
                    // std::cout << de.path().native() << '\n';
                    return false;
                }

                // swap the found string_view with the last in the vector
                // to remove it from future matches in this file.
                --strsco;
                std::swap(strs[idx], strs[strsco]);
            }
        }
        return true;
    };

    // do a "find dir -type f"
    for(const fs::directory_entry& de : fs::recursive_directory_iterator(dir)) {
        if(de.is_regular_file()) { // -type f

            // open the found file
            if(std::ifstream file(de.path()); file) {
                // reset counters
                count = 0;
                strsco = strs.size();
                // read line by line until the file stream is depleated or matcher()
                // returns false
                while(std::getline(file, line) && matcher(de));
            }
        }
    }
}

将其保存到prog.cpp 并像这样编译(如果你有g++):

g++ -std=c++17 -O3 -o prog prog.cpp

如果您使用其他编译器,请务必打开优化速度,并且它需要 C++17。

【讨论】:

  • 哇,这真的非常快!非常感谢。
【解决方案3】:

这是一个使用 awk 的选项,因为你也用它标记了它:

find dir -type f -exec \
awk '/str1|str2|str3|str4/{c++} END{if(c>=2) print FILENAME;}' {} \;

但是它会计算重复,所以一个文件包含

str1
str1

将被列出。

【讨论】:

  • @David 不客气!速度差异很奇怪,但当速度很重要时,我根本不会使用awk :-)
  • @David 为了速度,我会使用 C++ 并让程序在内部同时执行 find 和模式匹配——但这需要的不仅仅是单行。 perlpython 通常已经足够好了。我不知道go,所以我不能这么说。我永远不会使用php
  • 不了解 perl,但使用 python 的解决方案会比 awk 慢。
  • 在某个地方,有人应该有一个站点,其中包含多种语言的时间统计信息,用于执行一些非常有趣的任务。例如,他们可以从rosettacode.org/wiki/Rosetta_Code 获取代码,并为每个任务添加每个工具/语言的时间。我当然不是志愿服务 :-)。
  • @EdMorton 我也传了一个 :-)
猜你喜欢
  • 2011-01-19
  • 2012-12-02
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 2021-03-31
  • 1970-01-01
相关资源
最近更新 更多