我有两个大文件(文件名集)。每个文件大约有30,000行。我试图找到一种快速查找file1中不存在于file2中的行的方法。
例如,如果这是file1:
line1
line2
line3
这是file2:
line1
line4
line5
然后我的结果/输出应为:
line2
line3
这有效:
grep -v -f file2 file1
但是在我的大文件上使用它时非常非常慢。
我怀疑使用diff()有一个很好的方法可以做到这一点,但是输出应该只是这些行,没有别的,我似乎找不到那个开关。
任何人都可以使用bash和基本的linux二进制文件帮助我找到一种快速的方法吗?
编辑:要跟进我自己的问题,这是我到目前为止使用diff()找到的最佳方式:
diff file2 file1 | grep '^>' | sed 's/^>\ //'
当然,必须有更好的方法吗?
答案 0 :(得分:182)
comm命令(" common"的缩写)可能有用comm - compare two sorted files line by line
#find lines only in file1
comm -23 file1 file2
#find lines only in file2
comm -13 file1 file2
#find lines common to both files
comm -12 file1 file2
man
文件实际上对此非常易读。
答案 1 :(得分:173)
您可以通过控制GNU diff
输出中旧/新/未更改行的格式来实现此目的:
diff --new-line-format="" --unchanged-line-format="" file1 file2
输入文件应按排序,以使其正常工作。使用bash
(和zsh
),您可以使用流程替换<( )
进行就地排序:
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
在上面的 new 和未更改的行被抑制,因此只输出 (即在你的情况下删除的行)。您还可以使用其他解决方案未提供的一些diff
选项,例如-i
忽略大小写,或各种空白选项(-E
,-b
,{{ 1}}等)用于不太严格的匹配。
<强>解释强>
选项-v
,--new-line-format
和--old-line-format
可让您控制--unchanged-line-format
格式化差异的方式,类似于diff
格式说明符。这些选项分别格式化 new (已添加),旧(已删除)和未更改行。将一个设置为空“”会阻止输出那种行。
如果您熟悉统一差异格式,可以使用以下部分重新创建它:
printf
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
说明符是有问题的行,我们在前面添加“+”“ - ”或“”,如%L
(请注意,它仅输出差异,但缺少每个分组更改顶部的diff -u
---
和+++
行。
您还可以使用此功能执行其他有用的操作,例如number each line和@@
。
%dn
方法(以及其他建议diff
和comm
)仅使用排序输入生成预期输出,但您可以使用{{1排序到位。这是一个简单的join
(nawk)脚本(受Konsolebox答案中链接的脚本的启发),它接受任意排序的输入文件,和按照它们在file1中出现的顺序输出缺失的行
<(sort ...)
这将在行号索引数组awk
中逐行存储file1的全部内容,并在行内容索引关联数组# output lines in file1 that are not in file2
BEGIN { FS="" } # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; } # file1, index by lineno
(NR!=FNR) { ss2[$0]++; } # file2, index by string
END {
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}
中逐行存储file2的全部内容。读取两个文件后,迭代ll1[]
并使用ss2[]
运算符确定file1中的行是否存在于file2中。 (如果存在重复,这将与ll1
方法具有不同的输出。)
如果文件足够大以至于存储它们都会导致内存问题,则可以通过仅存储file1并在读取file2时删除匹配来为内存交换CPU。
in
上面将file1的全部内容存储在两个数组中,一个由行号diff
索引,一个由行内容BEGIN { FS="" }
(NR==FNR) { # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) { # file2
if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}
索引。然后,当读取file2时,将从ll1[]
和ss1[]
中删除每个匹配的行。最后输出file1的剩余行,保留原始顺序。
在这种情况下,如上所述,您还可以使用GNU ll1[]
来划分和征服(过滤是GNU扩展),重复运行file1块并读取file2完全每次:
ss1[]
请注意split
在split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
命令行中-
的使用和位置。这是由{1}}从文件1提供的,每次调用20000行。
对于非GNU系统上的用户,几乎可以肯定会有一个GNU coreutils包,包括OSX作为提供GNU stdin
的{{3}}工具的一部分,gawk
,虽然只有POSIX / BSD split
而不是GNU版本。
答案 2 :(得分:19)
像konsolebox建议的那样,海报grep解决方案
grep -v -f file2 file1
如果只是添加-F
选项,实际上效果很好(快),将模式视为固定字符串而不是正则表达式。我在一对必须比较的~1000行文件列表中验证了这一点。使用-F
需要0.031秒(实际),而没有需要2.278秒(实际),将grep输出重定向到wc -l
。
这些测试还包括-x
开关,它是解决方案的必要部分,以确保在file2包含与file1中的一行或多行匹配但不是全部的行的情况下完全准确
因此,一个不需要对输入进行排序的解决方案,快速,灵活(区分大小写等),并且(我认为)适用于任何POSIX系统:
grep -F -x -v -f file2 file1
答案 3 :(得分:10)
什么是排序和差异的速度?
sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted
答案 4 :(得分:5)
$ join -v 1 -t '' file1 file2
line2
line3
-t
确保它比较整行,如果你在某些行中有空格。
答案 5 :(得分:5)
如果您缺少“精美工具”,例如在一些最小的Linux发行版中,有一个仅包含cat
,sort
和uniq
的解决方案:
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique
测试:
seq 1 1 7 | sort --random-sort > includes.txt
seq 3 1 9 | sort --random-sort > excludes.txt
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique
# Output:
1
2
与grep
相比,这也相对快。
答案 6 :(得分:2)
使用fgrep或向grep添加-F选项可能有所帮助。但是为了更快的计算,你可以使用Awk。
您可以尝试以下其中一种Awk方法:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
答案 7 :(得分:2)
您可以使用Python:
python -c '
lines_to_remove = set()
with open("file2", "r") as f:
for line in f.readlines():
lines_to_remove.add(line.strip())
with open("f1", "r") as f:
for line in f.readlines():
if line.strip() not in lines_to_remove:
print(line.strip())
'
答案 8 :(得分:1)
我通常这样做的方法是使用--suppress-common-lines
标记,但请注意,这仅适用于并排格式的情况。
diff -y --suppress-common-lines file1.txt file2.txt
答案 9 :(得分:1)
使用combine
包中的moreutils
,这是一个集实用程序,支持not
,and
,or
,xor
操作
combine file1 not file2
即给我在file1中但不在file2中的行
或者给我file1中的行减去file2中的行
注意:combine
在执行任何操作之前在两个文件中排序并查找唯一的行,但是diff
则不行。因此,您可能会发现diff
和combine
的输出之间存在差异。
实际上你是在说
在file1和file2中找到不同的行,然后给我file1中的行减去file2中的行
根据我的经验,它比其他选项快得多
答案 10 :(得分:1)
这对我来说似乎很快:
matchUrl = matchUrl.Replace("\u0026", "&")
答案 11 :(得分:-1)
我发现对我来说,使用正常的if和for循环语句可以很好地工作。
for i in $(cat file2);do if [ $(grep -i $i file1) ];then echo "$i found" >>Matching_lines.txt;else echo "$i missing" >>missing_lines.txt ;fi;done