从一个文件中删除另一文件中的行


126

我有一个文件f1

line1
line2
line3
line4
..
..

我想删除另一个文件中的所有行f2

line2
line8
..
..

我用cat和尝试了一些东西sed,这甚至与我的意图不符。我怎样才能做到这一点?



如果您要从文件中删除“甚至包含”另一个文件中的字符串的行(例如部分匹配项),请参见unix.stackexchange.com/questions/145079/…–
rogerdpack

Answers:


154

grep -v -x -f f2 f1 应该可以。

说明:

  • -v 选择不匹配的行
  • -x 只匹配整行
  • -f f2 从中获取模式 f2

人们可以改用grep -Ffgrep匹配固定的字符串f2,而不是模式(如果你想删除线的“如果你看到什么,你会得到什么”的方式,而不是治疗的线条f2为正则表达式模式)。


22
这具有O(n²)的复杂度,一旦文件包含多于K行,将需要花费数小时才能完成。
Arnaud Le Blanc

11
找出哪个SO建议算法具有O(n ^ 2)复杂度仅具有O(n)复杂度,但仍可能需要数小时才能竞争。
HDave 2012年

2
我只是在每个大约2k行的2个文件上尝试了此操作,然后它被操作系统杀死(当然,这不是一个功能强大的VM)。
Trebor Rude 2014年

1
我喜欢这种优雅;我更喜欢Jona Christopher Sahnwal的回答速度。
亚历克斯·霍尔

1
@ arnaud576875:您确定吗?这取决于的实现grep。如果f2在开始搜索之前进行了适当的预处理,则搜索将仅花费O(n)时间。
HelloGoodbye

56

尝试使用comm(假设f1和f2已“已排序”)

comm -2 -3 f1 f2

5
我不确定comm解决方案的问题是否表示行f1已排序是使用的先决条件comm
gabuzo 2011年

1
这对我很有用,因为我的文件已排序,其中一个文件包含250,000+行,另一个文件中只有28,000行。谢谢!

1
当此方法起作用(对输入文件进行排序)时,这非常快!
Mike Jarvis 2015年

与arnaud576875的解决方案一样,对于使用cygwin的我来说,这消除了第二个文件中可能要保留的重复行。
亚历克斯·霍尔

8
当然,您可以使用进程替换对文件进行排序: comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

要排除不太大的文件,可以使用AWK的关联数组。

awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt 

输出将与“ from-this.txt”文件的顺序相同。tolower()如果需要,该函数使其不区分大小写。

算法复杂度可能为O(n)(不包括这些.txt的大小)+ O(n)(来自此.txt的大小)


您为什么说文件不是太大?这里的担心是(我假设)awk将系统用尽系统内存以创建哈希,还是有其他限制?
rogerdpack 2015年

对于追随者而言,还有其他更激进的选项可以“清理”行(因为必须使用关联数组精确比较),例如unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack:较大的排除文件将需要较大的哈希数组(以及较长的处理时间)。较大的“ from-this.txt”仅需要较长的处理时间。
暂停,直到另行通知。

1
如果exclude-these.txt为空,则失败(即不产生任何输出)。在这种情况下,下面的@ jona-christopher-sahnwaldt答案有效。您还可以指定多个文件,例如awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell

11

与Dennis Williamson的答案类似(主要是语法上的更改,例如,显式设置文件号而不是NR == FNR技巧):

awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt

访问将r[$0]创建该行的条目,而无需设置值。

假设awk使用具有恒定查找和(平均)恒定更新时间的哈希表,则其时间复杂度将为O(n + m),其中n和m是文件的长度。就我而言,n约为2500万,m约为14000。awk解决方案比sort快得多,我也更喜欢保持原始顺序。


这与丹尼斯·威廉姆森的答案有何不同?它不对哈希进行赋值的唯一区别是,它比这快一点吗?算法复杂度和他一样吗?
rogerdpack 2015年

区别主要是句法上的。我发现变量f比清晰NR == FNR,但这只是个问题。分配给散列的速度应该如此之快,以至于两个版本之间没有可测量的速度差异。我认为我错了复杂性-如果查找为常数,则更新也应为常数(平均)。我不知道为什么我认为更新将是对数的。我将编辑答案。
jcsahnwaldt恢复莫妮卡

我尝试了很多这样的答案,而这个答案很快就实现了。我的文件包含数十万行。像魅力一样工作!
T先生

1
这是我的首选解决方案。它适用于多个文件,也可以清空排除文件,例如awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out。而另一种awk解决方案失败,排除文件为空,只能使用一个。
Graham Russell

5

如果您有Ruby(1.9+)

#!/usr/bin/env ruby 
b=File.read("file2").split
open("file1").each do |x|
  x.chomp!
  puts x if !b.include?(x)
end

具有O(N ^ 2)复杂度。如果您想关心性能,这是另一个版本

b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}

使用散列来实现减法,复杂度O(n)(a的大小)+ O(n)(b的大小)

这是一个小基准,由user576875提供,但有10万行,以上:

$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1
$ for i in $(seq 1 2 100000); do echo "$i"; done|sort --random-sort > file2
$ time ruby test.rb > ruby.test

real    0m0.639s
user    0m0.554s
sys     0m0.021s

$time sort file1 file2|uniq -u  > sort.test

real    0m2.311s
user    0m1.959s
sys     0m0.040s

$ diff <(sort -n ruby.test) <(sort -n sort.test)
$

diff 用来显示生成的两个文件之间没有差异。


1
这具有O(n²)的复杂性,一旦文件包含多于K行,将需要花费数小时才能完成。
Arnaud Le Blanc

在这个关头我真的不在乎,因为他没有提到任何大文件。
kurumi 2011年

3
无需如此防御,这并不是@ user576875否决了您的答案或其他任何东西。:-)
John Parker

非常好的第二版,红宝石赢了:)
Arnaud Le Blanc

4

其他各种答案之间的一些时间比较:

$ for n in {1..10000}; do echo $RANDOM; done > f1
$ for n in {1..10000}; do echo $RANDOM; done > f2
$ time comm -23 <(sort f1) <(sort f2) > /dev/null

real    0m0.019s
user    0m0.023s
sys     0m0.012s
$ time ruby -e 'puts File.readlines("f1") - File.readlines("f2")' > /dev/null

real    0m0.026s
user    0m0.018s
sys     0m0.007s
$ time grep -xvf f2 f1 > /dev/null

real    0m43.197s
user    0m43.155s
sys     0m0.040s

sort f1 f2 | uniq -u 甚至不是对称差异,因为它删除了两个文件中多次出现的行。

comm也可以与stdin和以下字符串一起使用:

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a

2

似乎是适合SQLite shell的工作:

create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);
-- comment: if you have | in your files then specify  .separator ××any_improbable_string×× 
.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in (select line from file1);
.q

1

您是否使用sed 尝试了此操作

sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh

sed -i 's#$#%%g'"'"' f1#g' f2.sh

sed -i '1i#!/bin/bash' f2.sh

sh f2.sh

0

这不是一个“编程”的答案,但是这是一个快速而肮脏的解决方案:只需转到http://www.listdiff.com/compare-2-lists-difference-tool

显然不适用于大文件,但对我有用。一些注意事项:

  • 我不以任何方式隶属于该网站(如果您仍然不相信我,那么您可以在线搜索其他工具;我使用搜索词“在线设置差异列表”)
  • 链接的网站似乎会在每个列表比较中进行网络通话,因此请勿向其提供任何敏感数据
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.