awk:使用大量输入文件计算多列数据的最小值/最大值

awk:使用大量输入文件计算多列数据的最小值/最大值

我正在处理位于不同目录中的大量 csv 数据文件的后处理。每个 csv 文件都有以下 3 列格式:

ID, POP, dG
1, 24, -6.8100
2, 22, -6.7900
3, 11, -6.6800
4, 18, -6.1100
5, 5, -6.0700
6, 1, -6.0600
7, 11, -6.0300
8, 36, -6.0100

以下 bash 函数结合了 awk 代码,用于计算 dG 的最小值(第 3 列,始终为负浮点数)以及 POP 的最大值(第 2 列 2,为正数)一次性处理所有 CSV并将其存储在第二个 awk 脚本使用的新 bash 变量 highestPOP lowestDG 中(这里不考虑):

home="$PWD"
# folder with the outputs
rescore="${home}"/rescore 
# folder with the folders to analyse
storage="${home}"/results_bench
cd "${storage}"
# pattern of the csv file located inside each of sub-directory of "${storage}"
str='*str1.csv'
rescore_data4 () {
str_name=$(basename "${str}" .csv)
mkdir -p "${rescore}"/"${str_name}"
# 1- calculate max POP and dGmin for ALL rescored CSVs at once
read highestPOP lowestDG < <(
    awk -F ', ' '
        FNR == 1 {
            next
            }
        NR == 2 || $2 > popMAX {popMAX = $2}
        NR == 2 || $3 < dGmin  {dGmin  = $3}
        END {printf "%d %.2f\n", popMAX, dGmin}
    ' "${storage}"/*_*_*/${str}
)
#
# 2- run rescoring routine using the min/max values
awk -F', *' -v OFS=', ' -v highest_POP="${highestPOP}" -v lowest_dG="${lowestDG}" '
   ... some awk code
'
}

在第一个 awk 脚本中,$str 是位于不同目录中的目标 csv 文件的 glob 掩码(匹配 glob 模式“__*") 虽然这通常有效,但第一个 AWK 代码(用于计算所有已处理的 CSV 的最小值/最大值)中存在一个错误:有时在输入大量 CSV/包含多行的情况下无法计算最低 DG 的值。问题始终与 dg 变量的计算有关(始终为负数),脚本报告 dg=0.000,这是不正确的。

为了解决这个问题,我尝试修改 AWK 代码,在开始时定义两个新变量(具有最小值和最大值),然后将列中的每个值与它们进行比较:

   read highestPOP lowestDG < <(
    awk -F ', ' '
        FNR == 1 {
            dGmin = ""                              # initialize the min value
            POPmax = ""   
            next
            }
        NR == 2 || POPmax == "" || $2 > POPmax {POPmax = $2 }
        NR == 2 || dGmin == "" || $3 < dGmin  {dGmin  = $3 }
        END {printf "%d %.2f\n", POPmax, dGmin}
    ' "${storage}"/*_*_*/${str}
)

现在,从技术上讲它可以工作,但似乎第二个解决方案没有正确报告最小值和最大值。如何正确修复 awk 脚本?

答案1

如果要使用/awk来计算一系列文件,只需在命令行上将这些文件作为脚本的输入提供即可maxminawk

awk -F, '
    ($2+0 > POP+0 || POP == "") && $1+0 > 0 { POP = $2 }
    ($3+0 < dG+0 || dG == "") && $1+0 > 0 { dG = $3 }
    END { print POP, dG }
' file1 file2 file3...

(这也可以通过简单地将所有行连接起来写成一行,但可读性较差。)

让我们分解一条线。模式样式是表达 { 行动 }并且任何一部分都是可选的。此处的表达式是寻找POP任何行中较大的值,其中ID是非零数字

($2+0 > POP+0 || POP == "") && $1+0 > 0 { POP = $2 }

$2+0 > POP+0    # Is the numeric values of $2 more than the numeric value of POP
||              # OR
POP == ""       # Is POP the empty string (possibly unset)

如果至少有一个是,true那么我们还需要下一个条件

$1+0 > 0        # Is the numeric value of $1 greater than zero ("skip the header")

然后...

{ POP = $2 }    # Assign the numeric value of $2 to POP

然后对每个文件的每一行重复该循环。在最后一个文件结束时,END执行该构造,打印出结果的两个值。

请注意,只有在比较时,循环中的值才会awk转换为数字。在其他所有时候,它们都只是字符串,因此不会损失精度。

bash可以很容易地将变量分配给这些输出,从而允许将不需要的空格作为副作用丢弃

read pop dg < <(awk ...)

对于大量的文件,例如 glob 扩展失败,标准find方法应该足够了,将文件的内容输入awk标准输入而不是在命令行上列出它们

find "${storage}" -type f -name 'target_file.csv' -exec cat {} + | awk '...'

相关内容