【问题标题】:How can I find both identical and similar strings in a particular field in a text file in Linux?如何在 Linux 的文本文件的特定字段中找到相同和相似的字符串?
【发布时间】:2018-02-20 18:24:52
【问题描述】:

我提前道歉 - 我不确定是否有一个仅使用 Linux 命令行 fu 的答案。请注意,我不是程序员,但过去几年我一直在玩 bash 和 python。

我有一个大文本文件,其中的行和列类似于以下内容(注意 - 字段用制表符分隔):

1074    Beetle  OOB11061MNH 12/22/16    Confirmed   
3430    Hightop 0817BESTYET 08/07/17    Queued  
3431    Hightop 0817BESTYET 08/07/17    Queued  
3078    Copland 2017GENERAL 07/07/17    Confirmed   
3890    Bartok  FOODS   09/11/17    Confirmed
5440    Alphapha    00B1106IMNH 01/09/18    Queued  

我想要做的是仅查找并输出列表中第三个字段与另一个字段相同或相似的那些行。我真的不在乎其他字段是否相似,但它们都应该包含在输出中。类似,我的意思是在该特定字段中不超过 [n] 个字符不同(例如,不超过 3 个字符不同)。所以我想要的输出是:

1074    Beetle  OOB11061MNH 12/22/16    Confirmed   
3430    Hightop 0817BESTYET 08/07/17    Queued  
3431    Hightop 0817BESTYET 08/07/17    Queued  
5440    Alphapha    00B1106IMNH 01/09/18    Queued  

以 1074 开头的行有一个与 5440 相差 3 个字符的第三个字段,因此它们都包括在内。包括 3430 和 3431 是因为它们完全相同。 3078 和 3890 被淘汰,因为它们不相似。

通过谷歌搜索论坛,我设法拼凑出这个相当长的管道,以便能够找到字段 3 完全相同的所有实例:

cat inputfile.txt | awk 'BEGIN { OFS=FS="\t" } {if (count[$3] > 1) print $0; else if (count[$3] == 1) { print save[$3]; print $0; } else save[$3] = $0; count[$3]++; }' > outputfile.txt

我必须承认我不太了解 awk。我只是从网络上复制和改编。但这似乎非常适合查找精确的重复项(即,它只会在上面输出 3430 和 3431)。但我不知道如何尝试找到不相同但在不超过 3 个地方不同的字符串。

例如,在我上面的示例中,它应该匹配 1074 和 5440,因为它们都符合模式: ??B1106?MNH

但我希望它也能够匹配任何其他随机匹配模式,只要不超过三个差异,如下所示: 20?7G?N?RAL

这些差异可以任意出现在任何位置。

需要这个的原因是我们试图找到一种方法来自动查找类似序列号的字段中的印刷错误。可能有误键,或者字母“O”替换为数字“0”等。

那么...有什么想法吗?感谢您的帮助!

【问题讨论】:

  • 这个文件有多少行?
  • 成千上万。此时,大约有 6500 行。

标签: bash awk sed grep uniq


【解决方案1】:

你可以使用这个脚本

 $ more hamming.awk

  function hamming(x,y,xs,ys,min,max,h) {
    if(x==y) return 0;
    else {
      nx=split(x,xs,"");
      mx=split(y,ys,"");
      min=nx<mx?nx:mx;
      max=nx<mx?mx:nx;
      for(i=1;i<=min;i++) if(xs[i]!=ys[i]) h++;
      return h+(max-min);     
    }
  }  
  BEGIN   {FS=OFS="\t"}
  NR==FNR {
      if($3 in a) nrs[NR];
      for(k in a)
        if(hamming(k,$3)<4) {
           nrs[NR];
           nrs[a[k]];
        }
      a[$3]=NR;
      next
  }

  FNR in nrs

用法

$ awk -f hamming.awk file{,}

这是一种双重扫描算法,可找到键之间的汉明距离(您所描述的距离)。请注意它的 O(n^2) 算法,因此可能不适合非常大的数据集。但是,不确定任何其他算法可以做得更好。

NB 基于我在帖子中错过的评论的附加说明。该算法逐个字符比较键,因此不会识别位移。例如 12323 将给出 3 的距离。

【讨论】:

  • 万一出现误键(OP 提到的可能性)汉明距离将不起作用
  • 感谢您的快速回复!我会试试这个,看看效果如何!老实说,即使它只捕获了一些错误而不是所有错误,这也是朝着正确方向迈出的一步。
  • @karakfa - 我用上面的示例尝试了脚本,它只输出带有 3431 的行。我只是不太了解 awk 以了解它在做什么,但我确实注意到了这一行说if(hamming(k,**$1**)&lt;4 { 使用$1 变量而不是$3。我尝试将 $1 更改为 $3,它似乎产生了我正在寻找的输出。这是正确的举动吗?
  • 是的,没错。我使用$1 进行测试,但忘记在那个地方更改为$3。此外,您可能希望将 FS 和 OFS 设置为选项卡;请参阅上面的更改。
【解决方案2】:

Levenshtein 距离又名“编辑距离”最适合您的任务。下面的 Perl 脚本需要安装一个模块 Text::Levenshtein(对于 debian/ubuntu 执行:sudo apt install libtext-levenshtein-perl)。

use Text::Levenshtein qw(distance);                                                                                                                                                  

$maxdist = shift;                                                                
@ll = (<>);                                                                      
@k = map {                                                                       
    $k = (split /\t/, $_)[2];                                                    
    # $k =~ s/O/0/g;                                                             
} @ll;                                                                           
for ($i = 0; $i < @ll; ++$i) {                                                   
    for ($j = 0; $j < @ll; ++$j) {                                               
        if ($i != $j and distance($k[$i], $k[$j]) < $maxdist) {                  
            print $ll[$i];                                                       
            last;                                                                
        }                                                                        
    }                                                                            
}                                                                                

用法:

perl lev.pl 3 inputfile.txt > outputfile.txt

算法与@karakfa 的帖子中的算法相同,但匹配更灵活。

还要注意注释行# $k =~ s/O/0/g;。如果取消注释,则键中的所有 O 都将变为 0,这将修复因 O->0 转换而损坏的键。在处理损坏的数据时,我总是使用这样的小规则来逐步修复数据,不断完善规则,直到数据几乎完美,不再需要模糊匹配。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-04
    • 1970-01-01
    • 2012-04-12
    • 2015-08-17
    • 1970-01-01
    相关资源
    最近更新 更多