【问题标题】:Remove duplicate lines in braces删除大括号中的重复行
【发布时间】:2020-06-12 23:26:25
【问题描述】:

我有一个文件,其中包含:

l1_lololo {
    abcdef
    vgjhklfgkchbnvu
    gfuhjfythkjbgftyhkjgyftuihgt6
    deefgik
    abcdef
}

l2_blabla {
    123456
    vgghyfthjfgtrdygfhhbnvu
    gfuhjgvftdyfgvjgyftuihgt6
    deiulouk
    123456
}

我需要使用 sed/awk/bash/etc 检查大括号中的文本。并删除重复的行,在每个大括号中只留下重复行的第一行,我需要得到这个:

l1_lololo {
    abcdef
    vgjhklfgkchbnvu
    gfuhjfythkjbgftyhkjgyftuihgt6
    deefgik
}

l2_blabla {
    123456
    vgghyfthjfgtrdygfhhbnvu
    gfuhjgvftdyfgvjgyftuihgt6
    deiulouk
}

我该怎么做?

【问题讨论】:

    标签: bash perl awk sed


    【解决方案1】:

    如果您可以保证块以仅包含} 的行结束,则可以这样简单地完成:

    awk '/^}$/ {delete a} !a[$0]++' input
    

    如果您需要更强大的解决方案,也许只需在模式中添加一些空格以匹配块的结尾。但是如果你想要一个完整的解析器并且想要仔细匹配大括号,awk 可能不适合这个任务。

    【讨论】:

    • 但是如果块在大括号之前以一些空格结尾并且可能包含,例如,';'大括号后,我需要在命令中更改什么?
    • 您只需要更改模式以匹配它。例如/^ *}/ 可能适合您。 (匹配任何以空格开头后跟} 的行)
    • @ComptrollerChanel 如果在您的真实数据中,块“在大括号前以一些空格结尾并且可能包含,例如,';'在大括号之后”,那么您显然应该在问题的示例输入/输出中包含这些案例。在尝试回答您的问题时,我们所要做的就是您在问题中提供的信息,因此您的示例真正代表您的真实数据非常重要。 /}/ 是您所需要的,如果您的数据中的其他任何地方都不能有 },如您的示例所示。
    【解决方案2】:

    如果您对其他语言持开放态度,由于输入采用 tcl 列表格式,这在 tcl 中非常容易做到,允许您使用它来进行所有解析,而无需任何潜在的脆弱正则表达式:

    #!/usr/bin/env tclsh
    package require Tcl 8.5
    foreach {key lst} [read stdin] {
        foreach item $lst { dict set seen $item 1 }
        puts "$key {\n\t[join [dict keys $seen] \n\t]\n}\n"
        unset seen
    }
    

    例子:

    $ ./dedup < input.txt
    l1_lololo {
            abcdef
            vgjhklfgkchbnvu
            gfuhjfythkjbgftyhkjgyftuihgt6
            deefgik
    }
    
    l2_blabla {
            123456
            vgghyfthjfgtrdygfhhbnvu
            gfuhjgvftdyfgvjgyftuihgt6
            deiulouk
    }
    

    【讨论】:

      【解决方案3】:

      可以使用以下代码实现所需的结果(数据存储在哈希中)

      use strict;
      use warnings;
      use feature 'say';
      
      my $data = do{ local $/; <DATA> };      # read whole data
      
      my %seen;
      my %records = $data =~ /(\w+)\s+\{\s*(.*?)\s*\}/sg;     # split into records
      
      while( my($k,$v) = each %records ) {    # for each record split into array
          my @array = map { if( not $seen{$_} ) { $seen{$_} = 1; $_ } } split '\s+', $records{$k};    # store uniq elements
          pop @array;                         # pop out last empty element
          $records{$k} = \@array;             # store array in hash
      }
      
      while( my($k,$v) = each %records ) {    # each record
          say "$k = {";                       # output hash key
          say "\t$_" for @{$v};               # output each element of array
          say "}\n";                          # done
      }
      
      __DATA__
      l1_lololo {
          abcdef
          vgjhklfgkchbnvu
          gfuhjfythkjbgftyhkjgyftuihgt6
          deefgik
          abcdef
      }
      
      l2_blabla {
          123456
          vgghyfthjfgtrdygfhhbnvu
          gfuhjgvftdyfgvjgyftuihgt6
          deiulouk
          123456
      }
      

      输出

      l1_lololo = {
              abcdef
              vgjhklfgkchbnvu
              gfuhjfythkjbgftyhkjgyftuihgt6
              deefgik
      }
      
      l2_blabla = {
              123456
              vgghyfthjfgtrdygfhhbnvu
              gfuhjgvftdyfgvjgyftuihgt6
              deiulouk
      }
      

      【讨论】:

        【解决方案4】:

        这可能对你有用(GNU sed):

        sed -E '/^\S+ \{/{:a;N;s/((\n[^\n]*)(\n.*)*)\2$/\1/;/\n\}$/!ba}' file
        

        如果一行以一些文本开头,后跟{,则追加下一行,如果与前一行匹配,则删除最后一行。重复后者,直到一行只包含 } 并打印结果。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-12-30
          • 1970-01-01
          • 2022-01-21
          • 2018-09-09
          • 2018-05-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多