【问题标题】:Deleting lines from one file which are in another file从一个文件中删除另一个文件中的行
【发布时间】:2011-06-14 09:24:03
【问题描述】:

我有一个文件f1

line1
line2
line3
line4
..
..

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

line2
line8
..
..

我用catsed 尝试了一些东西,这甚至与我的意图不符。我该怎么做?

【问题讨论】:

标签: bash scripting sh


【解决方案1】:

grep -v -x -f f2 f1 应该可以解决问题。

解释:

  • -v 选择不匹配的行
  • -x 仅匹配整行
  • -f f2f2 获取模式

可以改为使用grep -Ffgrep 来匹配f2 中的固定字符串,而不是模式(如果您想删除“所见即所得”的方式,而不是将f2 中的行视为正则表达式模式)。

【讨论】:

  • 这具有 O(n²) 复杂性,一旦文件包含超过几 K 行,将开始需要数小时才能完成。
  • 找出哪个 SO 建议的算法具有 O(n^2) 复杂度只有 O(n) 复杂度,但仍然需要数小时才能竞争。
  • 我刚刚在 2 个大约 2k 行的文件上尝试了这个,它被操作系统杀死了(当然,这是一个不那么强大的 VM,但仍然如此)。
  • 我喜欢它的优雅;我更喜欢 Jona Christopher Sahnwal 的回答速度。
  • @arnaud576875:你确定吗?这取决于grep 的实现。如果它在开始搜索之前正确预处理f2,搜索将只需要 O(n) 时间。
【解决方案2】:

改用 comm(假设 f1 和 f2 “已经排序”)

comm -2 -3 f1 f2

【讨论】:

  • 我不确定comm 是解决方案有问题并不表示f1 中的行已排序这是使用comm 的先决条件
  • 这对我有用,因为我的文件已经过排序,其中一个有 250,000 多行,另一个只有 28,000 行。谢谢!
  • 当它工作时(输入文件被排序),这是非常快的!
  • 在 arnaud576875 的解决方案中,对于使用 cygwin 的我来说,这消除了第二个文件中可能需要保留的重复行。
  • 可以先使用进程替换对文件进行排序,当然:comm -2 -3 <(sort f1) <(sort f2)
【解决方案3】:

对于不太大的排除文件,您可以使用 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)(exclude-these.txt 大小)+ O(n)(from-this.txt 大小)

【讨论】:

  • 为什么说文件不是太大?这里的担心是(我假设)awk 运行系统超出系统内存来创建哈希,还是有其他限制?
  • 对于追随者,还有其他更激进的选项来“清理”行(因为比较必须准确才能使用关联数组),例如 unix.stackexchange.com/a/145132/8337
  • @rogerdpack:一个大的排除文件将需要一个大的哈希数组(和很长的处理时间)。一个大的“from-this.txt”只需要很长的处理时间。
  • 如果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
  • @GrahamRussell 我找不到 Jona Christopher Sahnwal 的答案...
【解决方案4】:

与 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 快得多,而且我也更喜欢保持原来的顺序。

【讨论】:

  • 这与丹尼斯·威廉姆森的回答有何不同?唯一的区别是它没有对哈希进行分配,所以比这稍微快一点吗?算法复杂度和他一样吗?
  • 区别主要在于句法。我发现变量fNR == FNR 更清晰,但这只是个人喜好问题。分配到散列应该非常快,以至于两个版本之间没有可测量的速度差异。我认为我对复杂性的理解是错误的——如果查找是恒定的,那么更新也应该是恒定的(平均而言)。我不知道为什么我认为更新是对数的。我将编辑我的答案。
  • 我尝试了一堆这样的答案,这个答案很快就令人惊奇。我有数十万行的文件。像魅力一样工作!
  • 这是我的首选解决方案。它适用于多个文件,也适用于空的排除文件,例如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 解决方案因排除文件为空而失败,只能采用一个。
【解决方案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 提供,但有 100K 行,以上:

$ 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 用于显示生成的 2 个文件之间没有差异。

【讨论】:

  • 这具有 O(n²) 复杂性,一旦文件包含超过几 K 行,将开始需要数小时才能完成。
  • 我现在不在乎,因为他没有提到任何大文件。
  • 没有必要如此防御,这并不是说@user576875 否决了您的答案或任何其他内容。 :-)
  • 非常不错的第二个版本,红宝石胜出 :)
【解决方案6】:

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

$ 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 也可以与标准输入和此处的字符串一起使用:

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

【讨论】:

    【解决方案7】:

    似乎是适合 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
    

    【讨论】:

    • 太棒了!我的 70k 和 10k 文件花了 1 sek 左右!谢谢!!!
    【解决方案8】:

    你用 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
    

    【讨论】:

      【解决方案9】:

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

      显然不适用于大文件,但它对我有用。几点说明:

      • 我与该网站没有任何关联(如果您仍然不相信我,那么您可以在线搜索其他工具;我使用搜索词“在线设置差异列表”)
      • 链接的网站似乎在每次列表比较时都会进行网络调用,因此不要向其提供任何敏感数据

      【讨论】:

        【解决方案10】:

        一种使用另一个列表过滤一个列表的 Python 方法。

        加载文件:

        >>> f1 = open('f1').readlines()
        >>> f2 = open('f2.txt').readlines()
        

        删除每行末尾的'\n'字符串:

        >>> f1 = [i.replace('\n', '') for i in f1]
        >>> f2 = [i.replace('\n', '') for i in f2]
        

        只打印 f2 文件中的 f1 行:

        >>> [a for a in f1 if all(b not in a for b in f2)]
        

        【讨论】:

          【解决方案11】:
          $ cat values.txt
          apple
          banana
          car
          taxi
          
          $ cat source.txt
          fruits
          mango
          king
          queen
          number
          23
          43
          sentence is long
          so what
          ...
          ...
          

          我制作了一个小的 shell 脚本来“清除”源文件中的值,这些值存在于 values.txt 文件中。

          $cat weed_out.sh
          from=$1
          cp -p $from $from.final
          for x in `cat values.txt`;
          do
           grep -v $x $from.final > $from.final.tmp
           mv $from.final.tmp $from.final
          done
          

          正在执行...

          $ ./weed_out source.txt
          

          你会得到一个很好的清理文件....

          【讨论】:

            猜你喜欢
            • 2013-01-12
            • 2012-07-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-15
            • 1970-01-01
            • 2016-07-08
            相关资源
            最近更新 更多