删除具有相似前缀的重复行

时间:2018-02-07 05:37:21

标签: bash awk sed duplicates

我需要删除具有重复前缀的文件中的类似行,并保留唯一的行。

从此,

abc/def/ghi/
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/
123/456/789/
xyz/

到这个

abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/789/
xyz/

感谢任何建议,

4 个答案:

答案 0 :(得分:3)

在允许重新排序输出的情况下回答。

sort -r file | awk 'a!~"^"$0{a=$0;print}'
  1. sort -r file:以相反的方式对线条进行排序将具有相同模式的较长线条放置在相同模式的较短线条之前

  2. awk 'a!~"^"$0{a=$0;print}':解析排序的输出,a保留上一行,$0保留当前行

      如果当前行上一行开头的子字符串,则
    • a!~"^"$0检查每一行。
    • 如果$0不是子字符串(即不相似的前缀),我们print并在a中保存新字符串(与下一行进行比较)
  3. 第一行$0不在a,因为没有为a分配值(始终打印第一行)

答案 1 :(得分:1)

快速而肮脏的做法如下:

$ while read elem; do echo -n "$elem " ; grep $elem file| wc -l; done <file | awk '$2==1{print $1}'
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/789/
xyz/

在这里您读取输入文件并打印每个元素及其在文件中出现的时间,然后使用awk只打印出一次只出现的行。

答案 2 :(得分:0)

以下awk执行请求的操作,它会两次读取文件。

  • 在第一遍中,它为每行构建所有可能的前缀
  • 第二遍,它检查该行是否是可能的前缀,如果没有打印。

代码是:

awk -F'/' '(NR==FNR){s="";for(i=1;i<=NF-2;i++){s=s$i"/";a[s]};next}
           {if (! ($0 in a) ) {print $0}}' <file> <file>

您也可以一次读取文件,然后将其存储到内存中:

awk -F'/' '{s="";for(i=1;i<=NF-2;i++){s=s$i"/";a[s]}; b[NR]=$0; next}
           END {for(i=1;i<=NR;i++){if (! (b[i] in a) ) {print $0}}}' <file>

Allan的解决方案类似,但使用grep -c

while read line; do (( $(grep -c $line <file>) == 1 )) && echo $line;  done < <file>

考虑到这个构造读取文件(N + 1)次,其中N是行数。

答案 3 :(得分:0)

第1步:此解决方案基于允许重新排序输出的假设。如果是这样,那么在处理之前反向排序输入文件应该更快。通过反向排序,我们只需要比较每个循环中的2个连续行,不需要搜索所有文件或所有“已知前缀”。我知道一行被定义为前缀,如果它是任何其他行的前缀,则应将其删除。以下是文件中删除前缀的示例,允许重新排序

#!/bin/bash

f=sample.txt                                 # sample data

p=''                                         # previous line = empty

sort -r "$f" | \
  while IFS= read -r s || [[ -n "$s" ]]; do  # reverse sort, then read string (line)
    [[ "$s" = "${p:0:${#s}}" ]] || \
      printf "%s\n" "$s"                     # if s is not prefix of p, then print it
    p="$s"
  done

解释:${p:0:${#s}}获取字符串${#s}中的第一个s(len p)个字符。

测试:

$ cat sample.txt 
abc/def/ghi/
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
abc/def/ghi/jkl/one/one
abc/def/ghi/jkl/two/two
123/456/
123/456/789/
xyz/

$ ./remove-prefix.sh 
xyz/
abc/def/ghi/jkl/two/two
abc/def/ghi/jkl/one/one
123/456/789/

第2步:如果您确实需要保留订单,则此脚本是删除所有前缀的示例,不允许重新排序

#!/bin/bash

f=sample.txt
p=''

cat -n "$f" | \
  sed 's:\t:|:' | \
  sort -r -t'|' -k2 | \
  while IFS='|' read -r i s || [[ -n "$s" ]]; do
    [[ "$s" = "${p:0:${#s}}" ]] || printf "%s|%s\n" "$i" "$s"
    p="$s"
  done | \
  sort -n -t'|' -k1 | \
  sed 's:^.*|::'

说明:

  1. cat -n:为所有行编号
  2. sed 's:\t:|:':使用'|'作为分隔符 - 如果需要,您需要将其更改为另一个
  3. sort -r -t'|' -k2:使用delimiter ='|'进行反向排序并使用密钥2
  4. while ... done:类似于第1步的解决方案
  5. sort -n -t'|' -k1:排序回原始订单(编号排序)
  6. sed 's:^.*|::':删除编号
  7. 测试:

    $ ./remove-prefix.sh 
    abc/def/ghi/jkl/one/one
    abc/def/ghi/jkl/two/two
    123/456/789/
    xyz/
    

    注意:在这两种解决方案中,成本最高的操作都是对sort的调用。步骤1中的解决方案调用sort一次,步骤2中的解决方案调用sort两次。所有其他操作(catsedwhile,字符串比较,...)的费用水平不同。

    在步骤2的解决方案中,cat + sed + while + sed是“等效的”以扫描该文件4次(理论上可以因管道而并行执行)。