我有一个字符串列表,对于每个字符串,我想检查它是否出现在一个大的源代码目录中。
我找到了一个 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 的解决方案应该更有效,因为通过管道传输的数据更少(这涉及通过内核在两个进程的缓冲区之间进行复制)。