代码之家  ›  专栏  ›  技术社区  ›  armahalma

删除前缀相似的重复行

  •  2
  • armahalma  · 技术社区  · 7 年前

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

    从这里,

    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 回复  |  直到 7 年前
        1
  •  4
  •   kvantour    7 年前

    如果允许对输出进行重新排序,请回答。

    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 并将新字符串保存在 (与下一行比较)

    第一行 $0 不在中 因为没有指定值 (始终打印第一行)

        2
  •  2
  •   Allan    7 年前

    一种快速而肮脏的方法如下:

    $ 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仅打印仅出现1次的行。

        3
  •  0
  •   kvantour    7 年前

    以下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是行数。

        4
  •  0
  •   Bach Lien    7 年前

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

    #!/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 )字符串中的字符 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 :使用分隔符=“|”进行反向排序,并使用键2
    4. while ... done :类似于步骤1的解决方案
    5. sort -n -t'|' -k1 :按原始顺序排序(编号排序)
    6. sed 's:^.*|::' :删除编号

    测试:

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

    笔记: 在这两种解决方案中,成本最高的操作是调用 sort . 步骤1调用中的解决方案 分类 一次,步骤2中的解决方案调用 分类 两次所有其他操作( cat , sed , while ,字符串比较,…)成本水平不同。

    在步骤2的解决方案中, cat + sed + while + sed “相当于”扫描该文件4次(理论上可以通过管道并行执行)。