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

nim*_*mai 5 performance grep search

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

我来到了一个 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
Run Code Online (Sandbox Code Playgroud)

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

您知道一种使用“标准”unix 工具有效执行此操作的方法吗?

Sté*_*las 5

至少可以简化为:

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
Run Code Online (Sandbox Code Playgroud)

这将在第一次出现后停止搜索,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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

第三个可以用像这样的技巧来解决,但这可能不值得付出努力。请参阅Barefoot IO 的解决方案以了解完全绕过该问题的方法。


Oth*_*eus 5

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

fgrep -rio -f /tmp/listOfKeys.txt .
Run Code Online (Sandbox Code Playgroud)

如果您只想测试是否找到至少一个匹配项,请添加-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
Run Code Online (Sandbox Code Playgroud)

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