【问题标题】:Bash - Find variable in many .txt files and calculate statisticsBash - 在许多 .txt 文件中查找变量并计算统计数据
【发布时间】:2015-03-25 03:26:20
【问题描述】:

我在一个文件夹中有许多 .txt 文件。它们充满了统计数据,并且具有代表这些统计数据所涉及的实验的名称。

exp_1_try_1.txt
exp_1_try_2.txt
exp_1_try_3.txt

exp_2_try_1.txt
exp_2_try_2.txt

exp_other.txt

在这些文件中,我需要找到具有特定名称的变量的值,并使用它们来计算一些统计数据:min、max、avg、std dev 和 median。

变量是一个十进制值和点“。”用作小数分隔符。没有科学记数法,尽管处理它也很好。

#in file exp_1_try_1.txt
var1=30.523
var2=0.6

#in file exp_1_try_2.txt
var1=78.98
var2=0.4

#in file exp_1_try_3.txt
var1=78.100
var2=1.1

为了做到这一点,我正在使用 bash。这是我在 bash 技能生锈之前制作的旧脚本。它计算整数值的平均值。

#!/bin/bash

folder=$1
varName="nHops"

cd "$folder"
grep -r -n -i --include="*_out.txt" "$varName" . | sed -E 's/(.+'"$varName"'=([0-9]+))|.*/\2/' | awk '{count1+=$1; count2+=$1+1}END{print "avg hops:",count1/NR; print "avg path length:",count2/NR}' RS="\n"

我想将此脚本修改为:

  • 支持查找可变长度的十进制值
  • 计算更多统计数据

尤其是标准差和中位数可能需要特别注意。

更新:这是我尝试仅使用 UNIX 工具解决问题的方法,部分灵感来自 by this answer。它工作正常,除了它不计算标准偏差。选择的答案使用 Perl,可能要快得多。

#!/bin/bash

folder=$1
varName="var1"

cd "$folder"
grep -r -n -i --include="exp_1_run_*" "$varName" . | sed -E 's/(.+'"$varName"'=([0-9]+(\.[0-9]*)?))/\2/' | sort -n | awk '
  BEGIN {
    count = 0;
    sum = 0;
  }
  {
    a[count++] = $1;
    sum += $1;
  }
  END {
    avg = sum / count;
    if( (count % 2) == 1 ) {
      median = a[ int(count/2) ];
    } else {
      median = ( a[count/2] + a[count/2-1] ) / 2;
    }
    OFS="\t";
    OFMT="%.6f";
    print avg, median, a[0], a[count-1];
  }
'

【问题讨论】:

标签: regex bash perl awk statistics


【解决方案1】:

要仅提取值,请使用 -o-P grep 选项:

grep -rioPh --include="*_out.txt" "(?<=${varName}=)[\d.]+" .

这会寻找像nHops=1.234 这样的模式,然后打印出1.234

鉴于您的样本数据:

$ var="var1"
$ grep -oPh "(?<=$var=)[\d.]+" exp_1_try_{1,2,3}.txt 
30.523
78.98
78.100

要输出一些统计数据,您应该能够将这些数字通过管道传输到您最喜欢的统计程序中。这是一个例子:

grep -oPh "(?<=$var=)[\d.]+" f? | 
perl -MStatistics::Basic=:all -le '
    @data = <>; 
    print "mean: ", mean(@data);
    print "median: ", median(@data);
    print "stddev: ", stddev(@data)
'
mean: 62.53
median: 78.1
stddev: 22.64

当然,因为这是 perl,我们根本不需要 grep 或 sed:

perl -MStatistics::Basic=:all -MList::Util=min,max -lne '
        /'"$var"'\s*=\s*(\d+\.?\d*)/ and push @data, $1
    } END {
        print "mean: ", mean(@data);
        print "median: ", median(@data);
        print "stddev: ", stddev(@data);
        print "min: ", min(@data);
        print "max: ", max(@data);
' exp_1_try_*
mean: 62.53
median: 78.1
stddev: 22.64
min: 30.523
max: 78.98

【讨论】:

  • 我试图为等号周围的任意数量的空格创建一个模式,但我收到错误“lookbehind assertion is not fixed length. Here's my code grep -rioPh --include="exp_1_run_*" "(?&lt;=${varName}\s*=\s*\K)[\d]+\.?[\d]*
  • 如果您使用\K,则不需要后视。 grep ... "${varName}\s*=\s*\K[\d]+\.?[\d]*" -- 可能需要将双引号中的反斜杠加倍
  • 你可以完全取消 sed 和 grep
  • 我在打印之前使用 sprintf 将数字四舍五入到特定精度。这是诀窍print "mean: ", sprintf("mean: %.6f", mean(@data))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多