如何在大代码目录中有效搜索字符串列表

如何在大代码目录中有效搜索字符串列表

我有一个字符串列表,对于每个字符串,我想检查它是否出现在一个大的源代码目录中。

我找到了一个 GNU grep 解决方案,它给了我我想要的东西:

for key in $(cat /tmp/listOfKeys.txt); do
    if [ "$(grep -rio -m 1 "$key" . | wc -l)" = "0" ]; then
        echo "$key has no occurence"; 
    fi
done

但是,它效率很低,因为它总是 grep 目录下的每个文件,即使它早早就找到了匹配项。由于要查找的键很多,要搜索的文件也很多,因此它无法按原样使用。

您知道如何使用“标准”unix 工具有效地完成此操作吗?

答案1

至少可以简化为:

set -f # needed if you're using the split+glob operator and don't want the
       # glob part

for key in $(cat /tmp/listOfKeys.txt); do
   grep -riFqe "$key" . ||
    printf '%s\n' "$key has no occurrence"
done

这将在第一次出现 后停止搜索,key并且不将键视为正则表达式(或 的可能选项grep)。

为了避免多次读取文件,并假设您的键列表是每行一个键(而不是上面代码中的空格/制表符/换行符分隔),您可以使用 GNU 工具:

find . -type f -size +0 -printf '%p\0' | awk '
  ARGIND == 2 {ARGV[ARGC++] = $0; next}
  ARGIND == 4 {a[tolower($0)]; n++; next}
  {
    l = tolower($0)
    for (i in a) if (index(l, i)) {
      delete a[i]
      if (!--n) exit
    }
  }
  END {
    for (i in a) print i, "has no occurrence"
  }' RS='\0' - RS='\n' /tmp/listOfKeys.txt

它经过优化,key一旦看到 a 就会停止寻找,一旦找到所有键就会停止,并且只读取文件一次。

它假设键在listOfKeys.txt.它将以小写形式输出密钥。

上面的 GNUisms 是-printf '%p\0',以及处理 NUL 分隔记录ARGIND的能力。awk前两个问题可以通过以下方式解决:

find . -type f -size +0 -exec printf '%s\0' {} + | awk '
  step == 1 {ARGV[ARGC++] = $0; next}
  step == 2 {a[tolower($0)]; n++; next}
  {
    l = tolower($0)
    for (i in a) if (index(l, i)) {
      delete a[i]
      if (!--n) exit
    }
  }
  END {
    for (i in a) print i, "has no occurrence"
  }' step=1 RS='\0' - step=2 RS='\n' /tmp/listOfKeys.txt step=3

第三个问题可以用类似的技巧来解决这个,但这可能不值得付出努力。看Barefoot IO的解决方案寻找一种完全绕过该问题的方法。

答案2

GNU grep(以及我所知道的大多数变体)提供了一个-f选项,它完全可以满足您的需要。该fgrep变体将输入行视为普通字符串而不是正则表达式。

fgrep -rio -f /tmp/listOfKeys.txt .

如果您只想测试是否找到至少一个匹配项,请添加-q选项。根据 Stéphane 的评论,如果您需要知道哪些字符串是不是找到后,添加-h选项,然后通过这个常见的 awk 习惯用法进行管道传输:

fgrep -h -rio -f /tmp/listOfKeys.txt . |
awk '{$0=tolower($0)}; !seen[$0]++' |
fgrep -v -i -x -f - /tmp/listOfKeys.txt

第二个fgrep现在使用第一个fgrep的输出(不区分大小写的唯一),反转含义,并显示密钥文件中的不匹配字符串。

答案3

Stéphane Chazelas 的 gawk 方法的可移植、符合 POSIX 标准的翻译:

find . -type f -exec cat {} + |
awk '
    FNR==NR {keys[tolower($0)]; n++; next}
    {
        s = tolower($0)
        for (k in keys) 
            if (index(s, k)) {
                delete keys[k]
                if (!--n)
                    exit
            }
    }
    END {
        for (k in keys) print k, "has no occurrence"
    }
' /tmp/listOfKeys.txt -

除非您的源文件不寻常,因为它们的名称始终比内容长,否则 Stéphane 的解决方案应该更有效,因为通过管道传输的数据更少(这涉及通过内核在两个进程的缓冲区之间进行复制)。

相关内容