我有 FILE_A 超过 300K 行,FILE_B 超过 30M 行。我创建了一个 bash 脚本,该脚本对 FILE_A 中的每一行进行 grep 到 FILE_B 中,并将 grep 的结果写入新文件。
整个过程需要5个多小时。
我正在寻找您是否认为有任何方法可以提高我的脚本性能的建议。
我使用 grep -F -m 1 作为 grep 命令。 FILE_A 看起来像这样:
123456789
123455321
FILE_B 是这样的:
123456789,123456789,730025400149993,
123455321,123455321,730025400126097,
因此,在 bash 中,我有一个 while 循环,它会选择 FILE_A 中的下一行并在 FILE_B 中对其进行 grep 。当在 FILE_B 中找到该模式时,我将其写入 result.txt。
while read -r line; do
grep -F -m1 $line 30MFile
done < 300KFile
预先非常感谢您的帮助。
答案1
性能的关键是只读取一次大文件。
您可以通过将多个模式放在单独的行上来将它们传递给 grep。这通常是通过告诉 grep 从文件中读取模式来完成的:
grep -F -f 300KFile 30MFile
这会按照大文件的顺序输出匹配项,并且仅打印一次匹配多个模式的行。此外,这会在行中的任何位置查找模式;例如,如果模式文件包含1234
,则123456,345678,2348962342
和等478912,1211138,1234
行将匹配。
您可以通过预处理模式来限制精确的列匹配。例如,如果模式不包含任何特殊字符()?*+\|[]{}
:
<300KFile sed -e 's/^/(^|,)/' -e 's/$/($|,)/' |
grep -E -f - 30MFile
如果只保留每个模式的第一个匹配很重要,请进行第一次传递以仅提取上述相关行,然后在 awk 或 perl 中进行第二次传递以跟踪已经看到的模式。
<300KFile sed -e 's/^/(^|,)/' -e 's/$/($|,)/' |
grep -E -f - 30MFile |
perl -l -F, -ape '
BEGIN {
open P, "300KFile" or die;
%patterns = map {chomp; $_=>1} <P>;
close P;
}
foreach $c (@F) {
if ($patterns{$c}) {
print;
delete $patterns{$c};
}
}
'
答案2
你能运行以下命令吗?
grep -Ff FILE_A FILE_B > FILE_C
现在您只能在文件 A 和 C 上运行脚本。
更新:等等...它会保留顺序吗?
另一个更新:需要进行更多处理才能保持订单。这给了我与你的原始脚本相同的结果。在 FILE_A 中的 300K 行和 FILE_B 中仅 300K 行上进行测试,分别为 125 分钟和 14 秒。
#! /bin/bash
grep -Ff FILE_A FILE_B > FILE_B_TMP
grep -oFf FILE_A FILE_B_TMP > FILE_A_SHUFF
grep -Ff FILE_A_SHUFF FILE_A > FILE_A_TMP
while read -r line; do
grep -F -m1 "$line" FILE_B_TMP
done < FILE_A_TMP > result.txt
答案3
我相信comm可以做出更好的性能:
comm -12 300KFile <(sed 's/,.*//' 30MFile)
附注我不确定 300KFile 中的字符串是否应该与30M 文件中的123123
字符串匹配。gdwyedg,123123,hfsjdkfh
正如在您的脚本中它匹配,但在我的脚本中它不匹配。
答案4
我相信基于 grep 的解决方案仍然需要将 FILE_A 中的每条记录与 FILE_B 中的每条记录进行比较。由于 FILE_A 中至少有 N-1 条记录与 FILE_B 中的特定记录不匹配,因此这种方法存在大量冗余。另一方面,如果对文件进行排序,则可以在每次比较时放弃大量测试。因此,类似......
#!/bin/bash
# NB a faster solution would be to sort the smaller file in a seperate process
# you might also want to set a buffer size for large files
sort $1 > /tmp/$$.a
sort $2 > /tmp/$$.b
join -j1 -t',' /tmp/$$.a /tmp/$$.b
rm -f /tmp/$$.?
(未测试)
但请注意,条目的顺序将被更改,这预先假设您想要匹配 FILE_B 中的特定数据列,并且排序也会引入开销 - 但对于这些文件大小,结果应该更快。