【问题标题】:Print lines with minimal values in multiple columns based on duplicated string in a third column根据第三列中的重复字符串打印多列中具有最小值的行
【发布时间】:2017-09-13 17:02:18
【问题描述】:

我试图过滤掉特定列中具有非唯一字符串的行,同时仅保留其他 2 列中具有最小值的那些行,当然还有那些没有重复的行。请看我的示例表:

Col1  Col2  Col3  Col4  Col5  Col6  Col7
blah  blah  1     blah  blah  1     BBBB
blah  blah  0     blah  blah  3     AAAA
blah  blah  1     blah  blah  3     BBBB
blah  blah  2     blah  blah  0     AAAA
blah  blah  0     blah  blah  0     AAAA
blah  blah  8     blah  blah  3     CCCC

Col1、Col2、Col4 和 Col5 并不重要,只需复制即可。如果 Col7 中的一个字符串出现不止一次,那么在所有出现中,我只想打印 Col3 中具有最低值的行,然后,如果有平局 >,Col6 中的最小值。最后,我想添加一个新列,上面写着“唯一”或“多”,指定是否有重复。

我想要的输出是这样的:

Col1  Col2  Col3  Col4  Col5  Col6  Col7  Col8
blah  blah  0     blah  blah  0     AAAA  multi
blah  blah  1     blah  blah  1     BBBB  multi
blah  blah  8     blah  blah  3     CCCC  unique

到目前为止,我已经尽力使用 awk。我可以找到所有带有重复字符串的行,并将它们打印在一行中,但我不知道如何在打印前对其进行过滤。

 awk '{dup[$7]=dup[$7] ? dup[$7] " duplicate of " $1 : $1} END {for (x in dup) print dup[x], x}'

任何帮助将不胜感激,并且 awk 中的解决方案(请提供解释)将是首选,因为我试图更好地理解它。

为更好理解而编辑。

【问题讨论】:

    标签: bash awk


    【解决方案1】:

    使用awk

    单线

    awk 'FNR==1{print $0,"Col8";next}function cp(){a[$7]=$0;b[$7]=$3;c[$7]=$6}{f=$7 in a;  d[$7]++}!f{o[++i]=$7; cp(); next}f && b[$7]>$3{cp();next}f && b[$7]==$3 && $6<c[$7]{cp();next}END{for(i=1; i in o; i++)print a[o[i]],(d[o[i]]>1?"multi":"unique")}' file
    

    说明

    awk '
          # if first line was read then print current record and extra field as header
          # go to next line
          FNR==1{
                 print $0,"Col8";
                 next
          }
          # function which will be used frequently to store data
          function cp(){
                 a[$7]=$0;
                 b[$7]=$3;
                 c[$7]=$6;
           }
           # variable f holds boolean status whether array a has key of field7 value
           # variable f will be used frequently  
           {
                 f=$7 in a;
                 d[$7]++
           }
    
           # if key does not exist in array a then
           !f{
                 # store order
                 # copy data
                 # go to next line
                 o[++i]=$7;
                 cp();
                 next
           }
    
           # if f is true and 3rd field value of previously stored data
           # is greater than current record field3 data then
           # we got smaller value lets save it
           # and go to next line
    
           f && b[$7]>$3{
                  cp();
                  next
           }
    
           # if variable f is true and field3 value of previously stored data
           # is equal to current record field3 its tie, 
           # and check whether 6th field value is lesser than previously stored value
           # then we got smaller value from field6 of current row/record/line
           # copy data
           # go to next line
    
           f && b[$7]==$3 && $6<c[$7]{
                  cp();
                  next
           }
    
           # end block
           # loop through array o,
           # print value from array a, where index being o[i]
           # d[o[i]] holds count of occurrence of field7
           # if its greater than 1 then its multi otherwise unique
    
        END{
                for(i=1; i in o; i++)
                     print a[o[i]],(d[o[i]]>1?"multi":"unique")
           }
          ' file
    

    输入

    $ cat file
    Col1  Col2  Col3  Col4  Col5  Col6  Col7
    blah  blah  0     blah  blah  3     AAAA
    blah  blah  1     blah  blah  3     BBBB
    blah  blah  2     blah  blah  0     AAAA
    blah  blah  0     blah  blah  0     AAAA
    blah  blah  1     blah  blah  1     BBBB
    blah  blah  8     blah  blah  3     CCCC
    

    执行

    $ awk 'FNR==1{print $0,"Col8";next}function cp(){a[$7]=$0;b[$7]=$3;c[$7]=$6;{f=$7 in a;  d[$7]++}!f{o[++i]=$7;cp();next}f && b[$7]>$3{cp();next}f && b[$7]==$3 && $6<c[$7]{cp();next}END{for(i=1; i in o; i++)print a[o[i]],(d[o[i]]>1?"multi":"unique")}' file
    

    输出

    Col1  Col2  Col3  Col4  Col5  Col6  Col7 Col8
    blah  blah  0     blah  blah  0     AAAA multi
    blah  blah  1     blah  blah  1     BBBB multi
    blah  blah  8     blah  blah  3     CCCC unique
    

    【讨论】:

    • 这正是我想要的!非常感谢这些解释。我很乐意接受这个作为我的答案。
    • 当第一次出现重复实际上是出现值最小的行时,我发现了一个错误。在这种情况下,您的解决方案会将其打印为唯一而不是多。我相应地更改了示例表。抱歉,我花了一段时间才发现这个错误。如果您现在运行它,它将为 BBBB 选择正确的行,但会将其标记为唯一。
    • @Skoddo:感谢报告,已修复问题已移至 d[$7]++ 外部函数
    • 太棒了。感谢您的快速帮助!
    【解决方案2】:

    awk 来救援!借助其他工具

    $ head -1 file && sed 1d file | 
           sort -k7 -k3,3n -k6,6n | 
           uniq -c -f6 | 
           awk '!a[$NF]++{c=$1; gsub(" +"$1" +",""); print $0,c==1?"uniq":"multi"}'
    
    Col1  Col2  Col3  Col4  Col5  Col6  Col7
    blah  blah  0     blah  blah  0     AAAA multi
    blah  blah  1     blah  blah  1     BBBB multi
    blah  blah  8     blah  blah  3     CCCC uniq
    

    当然,如果你的标题不在那里,你可以去掉第一部分

    【讨论】:

      【解决方案3】:

      sort + uniq + sed 技巧:

      echo "$(head -1 file)  Col8" && \
            sort -k7 -k3,3 -k6,6 <(tail -n +2 file) | uniq -cf6 \
            | sed -E 's/^ *1 (.*)/\1  unique/; s/^ *([2-9]|[0-9]{2,}) (.*)/\2  multi/'
      

      输出:

      Col1  Col2  Col3  Col4  Col5  Col6  Col7  Col8
      blah  blah  0     blah  blah  0     AAAA  multi
      blah  blah  1     blah  blah  1     BBBB  multi
      blah  blah  8     blah  blah  3     CCCC  unique
      

      ---------

      Bonus 使用 GNU datamash + awk 的解决方案:

      datamash -WHfs -g7 count 7 min 6 min 3 <file \
          | awk 'NR==1{ $8="Col8" }NR>1{ $3=$10; $6=$9; $8=($8>1)?"multi":"unique" }{$9=$10=""}1' \
          | column -t
      

      【讨论】:

      • 你是一个使用 datamash 的魔术师,你用它很棒:)。虽然我花了一些时间来编写我的单个 awk,但使用这个工具看起来更容易。
      猜你喜欢
      • 2019-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多