我有两个文本文件。一种是包含姓名、电子邮件地址和其他字段的文本文件。一些行来自file1
:
John:[email protected]:johnson123:22hey
Erik:[email protected]:johnson133:22hey
Robert:[email protected]:johnson123:21hey
Johnnny:[email protected]:johnson123:22hey
另一个仅包含电子邮件地址。示例来自file2
:
[email protected]
[email protected]
[email protected]
[email protected]
我希望输出是其中的每一整行file1
都有一个电子邮件地址file2
。例如,[email protected]
is in file2
,所以我想从 中看到以下行file1
:
John:[email protected]:johnson123:22hey
有没有一种简单的方法来搜索file1
并输出与“电子邮件地址列表”匹配的行file2
?
我已经搜索了几个小时,但我的 Google 搜索(和 StackOverflow 搜索)以及命令行上的努力到目前为止还没有效果。
我尝试过并认为可行的命令:
fgrep -f file2.txt file1.txt > matched.txt
grep -F -f ....
grep -F -x -f file1 file2 > common
等等,但他们都得到了grep memory exhausted
- 我匹配的文件是 4.8GB ( file1
) 和 3.2GB ( file2
,仅包含电子邮件地址)。我认为这些命令会耗尽内存。我发现一种方法可以find
更顺利地执行命令,但没有让它发挥作用。
总览;需要匹配file2
,file1
如果其中有一行file2
与 中的一行匹配file1
,则输出它。文件很大,我需要一种安全的方法来不耗尽所有内存。
谢谢你,一整天都在寻找这个并进行了实验,不想放弃(5小时+)。
答案1
操作大文件相当困难,但您可以通过三个步骤完成:
种类文件1按第二个字段
sort -k2,2 -t: file1 >file1.sorted
种类文件2
sort file2 >file2.sorted
通过电子邮件字段加入 2 个文件
join -t: -2 2 file2.sorted file1.sorted -o 2.1,0,2.3,2.4 >matched.txt
答案2
我正在提交这个问题的第二个答案(这是一个有趣的问题)。这与我的 SQLite 解决方案完全不同,也与开始出现的看起来很有前途的解决方案sort
完全不同:join
使用您最初的方法grep -f
,但实际上稍微减少了问题。让我们使用 .txt 将“查询文件”拆分file2
为可管理的块split
。
该split
实用程序能够根据行数将一个文件拆分为多个较小的文件。
3.2 GB 文件,其中包含平均行长度为 20 个字符有大约 172,000,000 行(除非我犯了算术错误)。分成 2000 个文件,每个文件 85000 行是可行的。
所以,
$ mkdir testing
$ cd testing
$ split -l 85000 -a 4 ../file2
该-a 4
选项指示split
在首字母后使用四个字符x
来为新文件创建文件名。这些文件将被称为xaaaa
、xaaab
等。
然后在这些上运行原始版本grep -f
:
for f in x????; do
grep -F -f "$f" ../file1
done
这可能使得grep
能够在内存中保存现在小得多的查询模式集。
更新:有 145,526,885 行,用于split -l 72000 -a 4
创建大约 2000 个文件。
testing
请记住每次尝试创建一组新的拆分文件时都要清除该目录。
请注意,此答案中的拆分文件可单独用作您可能获得此问题的任何其他答案的输入。
答案3
鉴于您的具体问题,科斯塔的答案可能是最好的,因为您有一个 100% 匹配的字段。
但如果你的问题确实曾是greping 数十亿行中的数百万个正则表达式,然后 GNU Parallel 描述了如何做到这一点:https://www.gnu.org/software/parallel/man.html#示例:-Grepping-n-lines-for-m-regular-expressions
grep 大量正则表达式的大文件的最简单解决方案是:
grep -f regexps.txt bigfile
或者如果正则表达式是固定字符串:
grep -F -f regexps.txt bigfile
有 3 个限制因素:CPU、RAM 和磁盘 I/O。
RAM 很容易测量:如果 grep 进程占用了大部分可用内存(例如运行 top 时),那么 RAM 就是一个限制因素。
CPU 也很容易测量:如果 grep 在 top 中占用 >90% 的 CPU,那么 CPU 是一个限制因素,并行化将加快这一速度。
很难看出磁盘 I/O 是否是限制因素,并且根据磁盘系统,并行化可能会更快或更慢。唯一确定的方法是测试和测量。
限制因素:内存
无论大文件的大小如何,正常的 grep -f regexs.txt 大文件都可以工作,但是如果 regexps.txt 太大而无法放入内存,那么您需要将其拆分。
grep -F 大约需要 100 字节的 RAM,而 grep 每 1 字节的正则表达式大约需要 500 字节的 RAM。因此,如果 regexps.txt 占 RAM 的 1%,那么它可能太大了。
如果您可以将正则表达式转换为固定字符串,请执行此操作。例如,如果您在大文件中查找的行全部如下所示:
ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2
那么你的 regexps.txt 可以从以下内容转换:
ID1.*Identifier1
ID2.*Identifier2
进入:
ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2
这样,您可以使用 grep -F,它占用的内存减少了大约 80%,而且速度更快。
如果它仍然不适合内存,您可以这样做:
parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'
1M 应该是您的可用内存除以核心数量,对于 grep -F 则除以 200,对于普通 grep 除以 1000。在 GNU/Linux 上你可以这样做:
free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-cores)))k
parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'
如果您可以忍受重复的行和错误的顺序,那么执行以下操作会更快:
parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile
限制因素:CPU
如果 CPU 是限制因素,则应在正则表达式上进行并行化:
cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'
该命令将为每个 CPU 启动一个 grep 并为每个 CPU 读取一次大文件,但由于这是并行完成的,除了第一个读取之外的所有读取都将缓存在 RAM 中。根据 regexp.txt 的大小,使用 --block 10m 而不是 -L1000 可能会更快。
一些存储系统在并行读取多个块时性能更好。对于某些 RAID 系统和某些网络文件系统来说确实如此。并行读取大文件:
parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt
这会将 bigfile 分割成 100MB 的块,并对每个块运行 grep。要并行读取 bigfile 和 regexp.txt,请使用 --fifo 将两者结合起来:
parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
\| parallel --pipe -L1000 --round-robin grep -f - {}
如果一行与多个正则表达式匹配,则该行可能会重复。
更大的问题
如果问题太大而无法解决,那么您可能已经准备好使用 Lucene了。
答案4
如果您需要避免使用数据库解决方案(不知道为什么,这对我来说似乎是最好的主意),您可以通过对电子邮件地址上的两个文件进行排序,然后使用该join
命令来实现,该命令近似于数据库的功能。
这就是我所做的:
sort -t: +1 file1 -o file1
sort file2 -o file2
join -t: -o 1.1,1.2,1.3,1.4 -1 2 file1 file2
这似乎对您的样本数据做了正确的事情。它对文件进行排序到位。如果您不想这样做,请将s-o
上的选项更改sort
为临时文件名,然后在联接中使用它们。另外,如果第一个文件中实际上有 4 个字段以外的字段,则必须在 选项中考虑到这-o
一点join
。
有关更多详细信息,请参阅手册页。