我正在处理位于不同目录中的大量 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
来计算一系列文件,只需在命令行上将这些文件作为脚本的输入提供即可max
min
awk
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 '...'