【问题标题】:Find smallest value in column for each block using awk使用 awk 在每个块的列中查找最小值
【发布时间】:2018-04-06 01:47:05
【问题描述】:

我想为每个具有“@ value1 value2”格式的“标题行”的块找到第 3 列的最小值。文件是这样的

@ 62.65 -50.35
0 1.50 1.676
1.67 1.50 1.677
1.67 2.25 1.423
2.90 2.25 2.902
2.90 4.95 2.903
3.04 4.95 3.049
@ 63.61 -50.45
0 1.50 1.654
3.42 1.50 1.875
3.43 2.19 3.430
5.31 2.19 1.032
5.32 6.23 5.320
5.43 6.23 5.434

在此之后,我想打印标题以及最小的 $3 的整行。

所以,我的输出文件应该是这样的

@ 62.65 -50.35
1.67 2.25 1.423
@ 63.61 -50.45
5.31 2.19 1.032

这是我迄今为止尝试过的:

awk '{if (!/^@/) {if ($3 < min) {$3=min;} print $1, $2, $3, min; min = $3;} else if (/^@/) min = 10; print $1 $2 $3;}' input.txt > output.txt

我很难将块分开并“重置”最小值(我试图将“开始”值设置为高 - 即 10)。

我是编程新手,目前主要使用 awk - 如果您能帮助我,那就太好了!非常感谢!干杯,伊西

【问题讨论】:

  • 您的预期输出是什么,您可以编辑问题并提供它。
  • 另外,请添加您到目前为止的尝试。没用也没关系,
  • 欢迎来到 SO。你已经尝试了什么?不要犹豫,访问help center

标签: bash awk


【解决方案1】:

使用 awk:

awk '/^@/{if(h){print h RS m}min=""; h=$0; next}min=="" || $3 < min{min=$3; m=$0}END{print h RS m}' infile

使用array

awk '/^@/{h=$0;min="";next}min==""||$3<min{min=$3;l[h]=$0}END{for(i in l)print i RS l[i]}' infile

更好的可读性:

awk '/^@/{
          if(h){
               print h RS m
          }
          min=""; h=$0; next
         }
     min=="" || $3 < min{
          min=$3; 
          m=$0
     }
     END{
          print h RS m
     }
    ' infile

使用array

awk '/^@/{
          h=$0;min="";
          next
     }
     min==""||$3<min{
          min=$3;
          l[h]=$0
     }
     END{
          for(i in l)
               print i RS l[i]
     }
     ' infile

测试结果:

$ cat infile
@ 62.65 -50.35
0 1.50 1.676
1.67 1.50 1.677
1.67 2.25 1.423
2.90 2.25 2.902
2.90 4.95 2.903
3.04 4.95 3.049
@ 63.61 -50.45
0 1.50 1.654
3.42 1.50 1.875
3.43 2.19 3.430
5.31 2.19 1.032
5.32 6.23 5.320
5.43 6.23 5.434

输出 1(推荐)

$ awk '/^@/{if(h){print h RS m}min=""; h=$0; next}min=="" || $3 < min{min=$3; m=$0}END{print h RS m}' infile
@ 62.65 -50.35
1.67 2.25 1.423
@ 63.61 -50.45
5.31 2.19 1.032

输出 2

$ awk '/^@/{h=$0;min="";next}min==""||$3<min{min=$3;l[h]=$0}END{for(i in l)print i RS l[i]}' infile
@ 62.65 -50.35
1.67 2.25 1.423
@ 63.61 -50.45
5.31 2.19 1.032

【讨论】:

  • 与另一个答案的评论相同:使用 $0 作为关联数组的键是有争议的:如果有几条相同的行怎么办?
  • @RenaudPacalet:我同意你的看法,但在目前的情况下,我还有一个解决方案,第一个不会覆盖,前一个,因为 OP 显示的数据很少,并且没有提到是否存在是否有任何需要注意的重复项,所以我假设,可能会有以@ 开头的独特行
【解决方案2】:

这是一种完全不同的 awk 方法。

awk 'BEGIN {RS="@"} {s=$3 OFS $4 OFS $5; n=$5; for (i=5;i<=NF;i+=3) {if ($i<n) {s=$(i-2) OFS $(i-1) OFS $i; n=$5} }} n { print "@ " $1 OFS $2 ORS s }' infile

或者为了方便评论而拆分:

BEGIN {
  RS="@"                          # "@" as our record separator
}

{
  s=$3 OFS $4 OFS $5              # store the first line...
  n=$5
  for (i=5;i<=NF;i+=3) {          # for each 3rd field on a line,
    if ($i<n) {                   # test its value and
      s=$(i-2) OFS $(i-1) OFS $i  # store a new value if the
      n=$5                        # condition matches
    }
  }
}

n {
  print "@ " $1 OFS $2 ORS s      # print records once we have them.
}

【讨论】:

    【解决方案3】:

    按照以下脚本,Awk 可以很容易地做到这一点:

    awk '
        $1=="@"    { first=1; key=$0; next }
        first==1   { lowest=$3; line[key]=$0; first=0; next }
                   { if ($3 < lowest) { lowest=$3; line[key]=$0 } }
        END        { for (key in line) { printf "%s\n%s\n", key, line[key] } }
    ' <<EOF
    @ 62.65 -50.35
    0 1.50 1.676
    1.67 1.50 1.677
    1.67 2.25 1.423
    2.90 2.25 2.902
    2.90 4.95 2.903
    3.04 4.95 3.049
    @ 63.61 -50.45
    0 1.50 1.654
    3.42 1.50 1.875
    3.43 2.19 3.430
    5.31 2.19 1.032
    5.32 6.23 5.320
    5.43 6.23 5.434
    EOF
    

    根据要求,输出为:

    @ 62.65 -50.35
    1.67 2.25 1.423
    @ 63.61 -50.45
    5.31 2.19 1.032
    

    分解代码进行澄清:

    $1=="@" {                # For all header lines:
        first=1              #    Flag that you're starting a new block.
        key=$0               #    Save the key.
        next                 #    Go back for next line.
    }
    first==1 {               # For first line in each block:
        lowest=$3            #    It must be lowest.
        line[key]=$0         #    Store line.
        first=0              #    Now processing subsequent lines in block.
        next                 # Go back for next line.
    }
    {                        # For non-first-lines-in-block:
        if ($3 < lowest) {   #    Only if this one is lower.
            lowest=$3        #    Store value and line.
            line[key]=$0
        }
    }
    END {                    # At end, simply output associative array.
        for (key in line) {
            printf "%s\n%s\n", key, line[key]
        }
    }
    

    请记住,这是假设标题行是唯一的。如果可能存在重复项,并且您希望将它们区别对待,则可以从 NR$0 的组合创建密钥。

    【讨论】:

    • 哇,太好了!太感谢了!这真是一个很大的帮助。 :)
    • 使用$0作为关联数组的键是有争议的:如果有几行相同的行怎么办?
    • @RenaudPacalet:那么您可以将NR$0 结合使用,即使行可以重复,这也将唯一标识每个块。我会把它作为一个选项添加。
    【解决方案4】:

    以下是另一种使用 awk 命令的解决方案。

    awk '
    /^@/{if(b)print a;print $0;next}
    !b||$3<b{b=$3;a=$0}
    END{print a}
    ' infile
    
    /^@/{if(b)print a;print $0;next}
    

    对于以@开头的行

    如果定义了 b,则打印 a(在第一行未定义 a 和 b)

    打印该行并转到下一行

    !b||$3<b{b=$3;a=$0}
    

    对于没有以@开头的行的每一行

    如果 b 未定义或 $3 小于 b,则将 $3 保留在 b 中,并将该行保留在 a 中

    END{print a}
    

    最后,我们必须打印存储在a中的行

    【讨论】:

    • 这是如何工作的?一些解释会很棒。
    猜你喜欢
    • 1970-01-01
    • 2015-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-26
    • 2018-12-21
    • 1970-01-01
    相关资源
    最近更新 更多