【问题标题】:awk: find minimum and maximum in columnawk:在列中查找最小值和最大值
【发布时间】:2015-06-29 07:25:57
【问题描述】:

我正在使用 awk 处理一个简单的 .dat 文件,该文件包含多行数据,每行有 4 列,由一个空格分隔。 我想找到第一列的最小值和最大值。

数据文件如下所示:

9 30 8.58939 167.759
9 38 1.3709 164.318
10 30 6.69505 169.529
10 31 7.05698 169.425
11 30 6.03872 169.095
11 31 5.5398 167.902
12 30 3.66257 168.689
12 31 9.6747 167.049
4 30 10.7602 169.611
4 31 8.25869 169.637
5 30 7.08504 170.212
5 31 11.5508 168.409
6 31 5.57599 168.903
6 32 6.37579 168.283
7 30 11.8416 168.538
7 31 -2.70843 167.116
8 30 47.1137 126.085
8 31 4.73017 169.496

我使用的命令如下。

min=`awk 'BEGIN{a=1000}{if ($1<a) a=$1 fi} END{print a}' mydata.dat`
max=`awk 'BEGIN{a=   0}{if ($1>a) a=$1 fi} END{print a}' mydata.dat`

但是,输出为 min=10 和 ma​​x=9。

(类似的命令可以返回第二列的正确最小值和最大值。)

谁能告诉我哪里错了?谢谢!

【问题讨论】:

  • 这些值被解释为字符串,因此结果是按字典顺序排列的最小值/最大值。
  • 我明白了,我需要在 awk 中将字符串转换为 int。谢谢!

标签: bash awk


【解决方案1】:

Awk 猜测类型。

字符串“10”小于字符串“4”,因为字符“1”在“4”之前。 强制类型转换,使用加零:

min=`awk 'BEGIN{a=1000}{if ($1<0+a) a=$1} END{print a}' mydata.dat`
max=`awk 'BEGIN{a=   0}{if ($1>0+a) a=$1} END{print a}' mydata.dat`

【讨论】:

  • 还值得考虑awk 'NR==1{max = $1 + 0; next} {if ($1 &gt; max) max = $1;} END {print max}',它适用于不同的值范围,并确保max 在第一次分配中是数字。如果文件大小不限,单次计算最小值和最大值会有所帮助。
  • 同时删除 if 语句末尾的虚假“fi”,例如:awk 'BEGIN{a=1000}{if ($1&lt;0+a) a=$1} END{print a}' mydata.dat。事实上,没有那个 awk 会正确猜测类型(尽管根据@Klaus 的答案明确设置它更好)。
  • 对,你的方法更灵活。如果数据文件有标题,您的方法允许避开标题并从第一行数据开始。
【解决方案2】:

您的问题只是在您的脚本中:

if ($1<a) a=$1 fi

最后的 fi 不是 awk 语法的一部分,所以它被视为一个变量,所以 a=$1 fi 是字符串连接,所以你告诉 awk a 包含一个字符串,而不是数字,因此是字符串$1&lt;a 中的比较而不是数字。

更重要的是,一般来说,永远不要从猜测的最大值/最小值开始,只需使用读取的第一个值作为种子。下面是编写脚本的正确方法:

$ cat tst.awk
BEGIN { min = max = "NaN" }
{
    min = (NR==1 || $1<min ? $1 : min)
    max = (NR==1 || $1>max ? $1 : max)
}
END { print min, max }

$ awk -f tst.awk file
4 12

$ awk -f tst.awk /dev/null
NaN NaN

$ a=( $( awk -f tst.awk file ) )
$ echo "${a[0]}"
4
$ echo "${a[1]}"
12

如果您不喜欢NaN,请在输入文件为空时选择您希望打印的任何内容。

【讨论】:

  • 我明白了。你的代码加深了我的理解。谢谢!
  • 不客气。如果你刚开始学习 awk,我推荐 Arnold Robbins 的《Effective Awk Programming》一书。
  • 我以前不知道那本书。我会查查的。谢谢推荐!
  • 这是唯一一本包含所有当前 awk 信息并且呈现良好的 awk 书籍。它也可以通过gnu.org/software/gawk/manual/gawk.html 在线获得,但恕我直言,作者应该为他在这本书和 GNU awk 上的所有工作获得报酬,这是一个很好的纸质参考。不要得到“The AWK Programming Language”,因为它已经过时了,也不要得到“SED & AWK”,因为它也已经过时了,你不需要一本书来学习如何将 sed 用于你应该使用的东西半本书都浪费了。
【解决方案3】:

一个非 awk 答案:

cut -d" " -f1 file |
sort -n |
tee >(echo "min=$(head -1)") \
  > >(echo "max=$(tail -1)")

那个 tee 命令可能有点太聪明了。 tee 将其 stdin 流复制到文件名作为参数,并将相同的数据流式传输到 stdout。我正在使用进程替换来过滤流。

可以使用相同的效果(不那么繁琐)来提取数据流的第一行和最后一行:

cut -d" " -f1 file | sort -n | sed -n '1s/^/min=/p; $s/^/max=/p'

或

cut -d" " -f1 file | sort -n | { 
    read line
    echo "min=$line"
    while read line; do max=$line; done
    echo "max=$max"
}

【讨论】:

  • 你能补充一些解释吗?
  • 不错的解决方案。但我也注意到sort 只是找到最小值和最大值有点过分。我的文件有超过 100 万行,sort 运行速度比其他答案慢,因为它不必要地将所有值按顺序排列。
【解决方案4】:

较晚但更短的命令,在没有初始假设的情况下具有更高的精度:

  awk '(NR==1){Min=$1;Max=$1};(NR>=2){if(Min>$1) Min=$1;if(Max<$1) Max=$1} END {printf "The Min is %d ,Max is %d",Min,Max}' FileName.dat

【讨论】:

    【解决方案5】:

    #最小

    cat your_data_file.dat | sort -nk3,3 | head -1 
                            #this fill find minumum of column 3
    

    #maximun

    cat your_data_file.dat | sort -nk3,3 | tail -1
                        #this will find maximum of column 3 
    

    #在第2列中查找,使用-nk2,2

    #assing 到一个变量并使用

    min_col=`cat your_data_file.dat | sort -nk3,3 | head -1 | awk '{print $3}'`
    

    【讨论】:

      【解决方案6】:

      一个非常简单的解决方案(如果不是强制使用awk):

      求最小值 --> sort -n -r numbers.txt | tail -n1

      查找 Max --> sort -n -r numbers.txt | head -n1

      您可以使用sort、head、tail 的组合来获得所需的输出,如上所示。

      (PS:如果要提取第一列/任何所需列,可以使用cut 命令,即提取第一列cut -d " " -f 1 sample.dat)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-06
        • 1970-01-01
        • 2012-09-16
        • 2013-11-12
        • 2017-09-19
        • 2022-06-16
        相关资源
        最近更新 更多