从第二个文本文件中删除与第一个文本文件重复的内容

6

如何通过检查第二个文本文件(removethese.txt)来从文本文件(main.txt)中删除所有行。如果文件大于10-100mb,什么是高效的方法?[使用mac]

示例:

main.txt
3
1
2
5

删除这些行
removethese.txt
3
2
9

输出:

output.txt
1
5

示例行(这些是我正在使用的实际行 - 顺序不重要):

ChIJW3p7Xz8YyIkRBD_TjKGJRS0
ChIJ08x-0kMayIkR5CcrF-xT6ZA
ChIJIxbjOykFyIkRzugZZ6tio1U
ChIJiaF4aOoEyIkR2c9WYapWDxM
ChIJ39HoPKDix4kRcfdIrxIVrqs
ChIJk5nEV8cHyIkRIhmxieR5ak8
ChIJs9INbrcfyIkRf0zLkA1NJEg
ChIJRycysg0cyIkRArqaCTwZ-E8
ChIJC8haxlUDyIkRfSfJOqwe698
ChIJxRVp80zpcEARAVmzvlCwA24
ChIJw8_LAaEEyIkR68nb8cpalSU
ChIJs35yqObit4kR05F4CXSHd_8
ChIJoRmgSdwGyIkRvLbhOE7xAHQ
ChIJaTtWBAWyVogRcpPDYK42-Nc
ChIJTUjGAqunVogR90Kc8hriW8c
ChIJN7P2NF8eVIgRwXdZeCjL5EQ
ChIJizGc0lsbVIgRDlIs85M5dBs
ChIJc8h6ZqccVIgR7u5aefJxjjc
ChIJ6YMOvOeYVogRjjCMCL6oQco
ChIJ54HcCsaeVogRIy9___RGZ6o
ChIJif92qn2YVogR87n0-9R5tLA
ChIJ0T5e1YaYVogRifrl7S_oeM8
ChIJwWGce4eYVogRcrfC5pvzNd4

3
您需要添加一些自己的代码,以展示您至少进行了研究,以便自己解决这个问题的努力。请注意,不要改变原始意思,使内容更加通俗易懂。 - Norbert
2
看一下 grep 的 man 页面。 - Cyrus
@NorbertvanNobelen 我尝试了几种解决方案,包括这个:http://stackoverflow.com/questions/25954013/how-to-remove-both-matching-lines-while-removing-duplicates,但我的列表很大且未排序。但该解决方案不能过滤列表。 - Onichan
使用适用于Windows、Linux和Mac的文本编辑器UltraEdit,可以使用UE脚本完成此操作,请参见删除包含在另一个文件中列出的字符串的活动文档中的行 - Mofi
@mofi,UltraEdit能处理大文件(10-100MB的文件)吗? - Onichan
显示剩余9条评论
4个回答

13

有两种标准方法可以做到这一点:

使用 grep:

grep -vxFf removethese main

这里使用了:

  • -v 表示反转匹配。
  • -x 匹配整行,以防止例如 he 匹配像 hellohighway to hell 这样的行。
  • -F 使用固定字符串,使参数按原样接受,而不是解释为正则表达式。
  • -f 从另一个文件获得模式。 在这种情况下,从 removethese 文件中获取模式。

使用 awk

$ awk 'FNR==NR {a[$0];next} !($0 in a)' removethese main
1
5

我们将所有在removethese中的行存储在数组a[]中。接着,我们读取main文件并只打印那些不在该数组中的行。


我之前尝试过从另一个答案中使用grep -vFf removethese.txt main.txt >output.txt,但需要4-6个小时才能处理一个7MB的文件。您认为使用awk会得到显着更快的结果吗? - Onichan
@Emma 很难说,因为我没有任何数据样本,也不知道哪个文件更大。你可以尝试使用一个小样本并比较这两种方法。请注意,我在 grep 方法中也使用了 -x - fedorqui
我在问题中添加了一些示例行。等我的进程完成另一个答案的操作后,我会尝试这种方法。如果我添加一个样本文本文件,会有帮助吗? - Onichan
removethese 文件相当大,通常为 main 文件大小的一半。它的大小可以在 5MB 到 50MB 之间变化。 - Onichan
1
awk比grep更快! - m3asmi
显示剩余2条评论

5

使用grep

grep -vxFf removethese.txt main.txt >output.txt

使用 fgrep 命令:

fgrep -vxf removethese.txt main.txt >output.txt

fgrep已经过时。 fgrep --help中写道:

调用“fgrep”已弃用;请改用“grep -F”。

使用awk(来自@fedorqui):

awk 'FNR==NR {a[$0];next} !($0 in a)' removethese.txt main.txt >output.txt

使用 sed 命令:

sed "s=^=/^=;s=$=$/d=" removethese.txt | sed -f- main.txt >output.txt

如果removethese.txt中包含特殊字符,这种方法将失败。你需要这样做:

sed 's/[^^]/[&]/g; s/\^/\\^/g' removethese.txt >newremovethese.txt

使用newremovethese.txt文件作为sed命令的输入。但是这种方法不值得尝试,与其他方法相比太慢了。


对上述方法进行的测试:

sed方法耗费时间过长,不值得测试。

使用的文件:

removethese.txt : Size: 15191908 (15MB)     Blocks: 29672   Lines: 100233
main.txt : Size: 27640864 (27.6MB)      Blocks: 53992   Lines: 180034

命令:
grep -vxFf | fgrep -vxf | awk

测试时间:
0m7.966s | 0m7.823s | 0m0.237s
0m7.877s | 0m7.889s | 0m0.241s
0m7.971s | 0m7.844s | 0m0.234s
0m7.864s | 0m7.840s | 0m0.251s
0m7.798s | 0m7.672s | 0m0.238s
0m7.793s | 0m8.013s | 0m0.241s

平均值
0m7.8782s | 0m7.8468s | 0m0.2403s

这个测试结果表明,fgrepgrep稍微快一点。

awk方法(来自@fedorqui)轻松通过测试,仅需0.2403秒

测试环境:

HP ProBook 440 G1 Laptop
8GB RAM
2.5GHz processor with turbo boost upto 3.1GHz
RAM being used: 2.1GB
Swap being used: 588MB
RAM being used when the grep/fgrep command is run: 3.5GB
RAM being used when the awk command is run: 2.2GB or less
Swap being used when the commands are run: 588MB (No change)

测试结果:

使用awk方法。


@shellter yap 我测试过了(它可以工作)...但是你的建议很棒,可以用一行命令完成...太棒了..谢谢... - Jahid
如果您的原始解决方案有效,请将其保留在您的答案中。我对echo $(grep something output.txt) > output.txt不会覆盖output.txt的现有内容持怀疑态度,但当时已经很晚了,也许它会起作用。无论如何,我建议使用grep -vFf ...解决方案。祝大家好运! - shellter
@shelter 我已经尝试了在一个8MB的文件上使用grep方法,但它花费了超过5个小时。这种方法对于大文件来说可能是低效的吗? - Onichan
@Emma 用 while 循环的 sed 解决方案怎么样? - Jahid
@Emma 我没有测试过,所以无法确定...不幸的是,我手头没有那么大的文本文件... :D - Jahid
显示剩余7条评论

3
这里有一些我找到的简单有效的解决方案: http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/ 你需要使用其中一个“集合补集”bash命令。100MB的文件可以在几秒钟或几分钟内解决。
集合成员。
$ grep -xc 'element' set    # outputs 1 if element is in set
                            # outputs >1 if set is a multi-set
                            # outputs 0 if element is not in set

$ grep -xq 'element' set    # returns 0 (true)  if element is in set
                            # returns 1 (false) if element is not in set

$ awk '$0 == "element" { s=1; exit } END { exit !s }' set
# returns 0 if element is in set, 1 otherwise.

$ awk -v e='element' '$0 == e { s=1; exit } END { exit !s }'

集合相等性

$ diff -q <(sort set1) <(sort set2) # returns 0 if set1 is equal to set2
                                    # returns 1 if set1 != set2

$ diff -q <(sort set1 | uniq) <(sort set2 | uniq)
# collapses multi-sets into sets and does the same as previous

$ awk '{ if (!($0 in a)) c++; a[$0] } END{ exit !(c==NR/2) }' set1 set2
# returns 0 if set1 == set2
# returns 1 if set1 != set2

$ awk '{ a[$0] } END{ exit !(length(a)==NR/2) }' set1 set2
# same as previous, requires >= gnu awk 3.1.5

集合基数

$ wc -l set | cut -d' ' -f1    # outputs number of elements in set

$ wc -l < set

$ awk 'END { print NR }' set

子集测试

$ comm -23 <(sort subset | uniq) <(sort set | uniq) | head -1
# outputs something if subset is not a subset of set
# does not putput anything if subset is a subset of set

$ awk 'NR==FNR { a[$0]; next } { if !($0 in a) exit 1 }' set subset
# returns 0 if subset is a subset of set
# returns 1 if subset is not a subset of set

集合并

$ cat set1 set2     # outputs union of set1 and set2
                    # assumes they are disjoint

$ awk 1 set1 set2   # ditto

$ cat set1 set2 ... setn   # union over n sets

$ cat set1 set2 | sort -u  # same, but assumes they are not disjoint

$ sort set1 set2 | uniq

# sort -u set1 set2

$ awk '!a[$0]++'           # ditto

集合交

$ comm -12 <(sort set1) <(sort set2)  # outputs insersect of set1 and set2

$ grep -xF -f set1 set2

$ sort set1 set2 | uniq -d

$ join <(sort -n A) <(sort -n B)

$ awk 'NR==FNR { a[$0]; next } $0 in a' set1 set2

集合的补集

$ comm -23 <(sort set1) <(sort set2)
# outputs elements in set1 that are not in set2

$ grep -vxF -f set2 set1           # ditto

$ sort set2 set2 set1 | uniq -u    # ditto

$ awk 'NR==FNR { a[$0]; next } !($0 in a)' set2 set1

设置对称差集

$ comm -3 <(sort set1) <(sort set2) | sed 's/\t//g'
# outputs elements that are in set1 or in set2 but not both

$ comm -3 <(sort set1) <(sort set2) | tr -d '\t'

$ sort set1 set2 | uniq -u

$ cat <(grep -vxF -f set1 set2) <(grep -vxF -f set2 set1)

$ grep -vxF -f set1 set2; grep -vxF -f set2 set1

$ awk 'NR==FNR { a[$0]; next } $0 in a { delete a[$0]; next } 1;
       END { for (b in a) print b }' set1 set2

幂集

$ p() { [ $# -eq 0 ] && echo || (shift; p "$@") |
        while read r ; do echo -e "$1 $r\n$r"; done }
$ p `cat set`

# no nice awk solution, you are welcome to email me one:
# peter@catonmat.net

设置笛卡尔积

$ while read a; do while read b; do echo "$a, $b"; done < set1; done < set2

$ awk 'NR==FNR { a[$0]; next } { for (i in a) print i, $0 }' set1 set2

不相交集合测试

$ comm -12 <(sort set1) <(sort set2)  # does not output anything if disjoint

$ awk '++seen[$0] == 2 { exit 1 }' set1 set2 # returns 0 if disjoint
                                         # returns 1 if not

空集测试

$ wc -l < set            # outputs 0  if the set is empty
                         # outputs >0 if the set is not empty

$ awk '{ exit 1 }' set   # returns 0 if set is empty, 1 otherwise

最低要求

$ head -1 <(sort set)    # outputs the minimum element in the set

$ awk 'NR == 1 { min = $0 } $0 < min { min = $0 } END { print min }'

最大值

$ tail -1 <(sort set)    # outputs the maximum element in the set

$ awk '$0 > max { max = $0 } END { print max }'

2

我喜欢@fedorqui在内存足够容纳所有“删除这些”行的设置中使用awk :这是一种内存方法的简明表达。

但对于要删除的行的大小与当前内存相比较大,并且将该数据读入内存数据结构会导致失败或抖动的情况,请考虑一种古老的方法:sort/join。

sort main.txt > main_sorted.txt
sort removethese.txt > removethese_sorted.txt

join -t '' -v 1 main_sorted.txt removethese_sorted.txt > output.txt

注意:

  • 这种方法不能保留main.txt中的顺序:输出文件output.txt将被排序
  • 它需要足够的磁盘空间让sort工作(临时文件),并存储输入文件的相同大小的排序版本
  • 在此处使用join的-v选项正好符合我们的要求 - 从文件1中打印“无法匹配”,删除匹配项 - 这有点像意外发现
  • 它没有直接处理区域设置、排序、键等 - 它依赖于sort和join的默认值(带有一个空参数的-t)来匹配排序顺序,在我的当前机器上恰好可以工作

1
有趣的回答!不过请注意,对一个100MB的文件进行排序也可能非常耗时。 - fedorqui

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接