Awk:对具有相同列布局的多个文件的列值求和
Awk: Sum up column values across multiple files with identical column layout
我有很多相同的文件header:
COL1、COL2、COL3、COL4
您可以忽略 COL1-COL3。 COL4 包含一个数字。每个文件包含大约 200 行。我正在尝试对各行进行总结。例如:
文件 1
COL1 COL2 COL3 COL4
x y z 3
a b c 4
文件 2
COL1 COL2 COL3 COL4
x y z 5
a b c 10
然后返回一个新文件:
COL1 COL2 COL3 COL4
x y z 8
a b c 14
有没有无需 AWK 的简单方法?如果需要,我会使用 AWK,我只是认为可能有一个简单的 one-liner,我可以马上 运行。我心目中的AWK脚本感觉有点长。
谢谢
你说你有 "a number of files"。即,超过 2 个。
给定这 3 个文件(并且应该可以处理任何数量的文件):
$ cat f1 f2 f3
COL1 COL2 COL3 COL4
x y z 3
a b c 4
COL1 COL2 COL3 COL4
x y z 5
a b c 10
COL1 COL2 COL3 COL4
x y z 10
a b c 15
你可以这样做:
$ awk 'FNR==1{next}
{sum[]+=}
END{print "COL1 COL4";
for (e in sum) print e, sum[e]} ' f1 f2 f3
COL1 COL4
x 18
a 29
不清楚你打算用 COL2 或 COL3 做什么,所以我没有添加。
如果所有文件都有相同的header - awk 解决办法:
awk '!f && FNR==1{ f=1; print [=10=] }FNR>1{ s[FNR]+=$NF; $NF=""; r[FNR]=[=10=] }
END{ for(i=2;i<=FNR;i++) print r[i],s[i] }' File[12]
输出(2 个文件):
COL1 COL2 COL3 COL4
x y z 8
a b c 14
这种方法可以应用于多个文件(在这种情况下,您可以指定 globbing File*
for filename expansion)
多一个选项。
命令:
paste f{1,2}.txt | sed '1d' | awk '{print ,,,+}' | awk 'BEGIN{print "COL1","COL2","COL3","COL4"}1'
结果:
COL1 COL2 COL3 COL4
x y z 8
a b c 14
它的作用:
测试文件:
$ cat f1.txt
COL1 COL2 COL3 COL4
x y z 3
a b c 4
$ cat f2.txt
COL1 COL2 COL3 COL4
x y z 5
a b c 10
命令:paste f{1,2}.txt
加入 2 个文件并给出输出:
COL1 COL2 COL3 COL4 COL1 COL2 COL3 COL4
x y z 3 x y z 5
a b c 4 a b c 10
命令:sed '1d'
旨在暂时删除 header
命令:awk '{print ,,,+}'
Returns COL1-3 并根据粘贴结果求和 $4 和 $8。
命令:awk 'BEGIN{print "COL1","COL2","COL3","COL4"}1'
添加 header 返回
编辑:
在 @mklement0 评论之后,他对 header 处理是正确的,因为我忘记了 NR==1
部分。
所以,我也将在这里代理他的更新版本:
paste f{1,2}.txt | awk '{ print , , , (NR==1 ? : + ) }'
将 paste
与 awk
相结合,如 ,是您的最佳选择:
paste
合并输入文件中的相应行,
- 将单个输入行流发送到
awk
,每个输入行包含要汇总的所有字段。
假设输入文件和列的数量固定,Kristo 的答案可以简化(使处理效率更高):
paste file1 file2 | awk '{ print , , , (NR==1 ? : + ) }'
注意:以上生成 space 分隔的输出列,因为 awk
的默认值为 OFS
,输出字段分隔符,是单个 space.
假设所有文件都具有相同的列结构和行数,下面是解决方案的概括,其中:
- 概括为超过 2 个输入文件(以及超过 2 个数据行)
- 概括为任意数量的字段,只要要汇总的字段是最后一个。
#!/bin/bash
files=( file1 file2 ) # array of input files
paste "${files[@]}" | awk -v numFiles=${#files[@]} -v OFS='\t' '
{
row = sep = ""
for(i=1; i < NF/numFiles; ++i) { row = row sep $i; sep = OFS }
sum = $(NF/numFiles) # last header col. / (1st) data col. to sum
if (NR > 1) { for(i=2; i<=numFiles; ++i) sum += $(NF/numFiles * i) } # add other cols.
printf "%s%s%s\n", row, OFS, sum
}
'
请注意,\t
(制表符)用于分隔输出字段,并且由于依赖 awk
的默认行拆分为字段,因此保留了准确的输入白色space 不保证字段之间。
$ awk '
NR==1 { print }
{ sum[FNR]+=$NF; sub(/[^[:space:]]+[[:space:]]*$/,""); pfx[FNR]=[=10=] }
END { for(i=2;i<=FNR;i++) print pfx[i] sum[i] }
' file1 file2
COL1 COL2 COL3 COL4
x y z 8
a b c 14
以上内容将在任何 UNIX 系统上与任何 awk、任何数量的输入文件以及这些文件的任何内容一起稳健而高效地工作。唯一的潜在问题是它必须在内存中保留相当于 1 个这些文件的内容,因此如果每个文件都非常大,那么您可能会耗尽可用内存。
我有很多相同的文件header:
COL1、COL2、COL3、COL4
您可以忽略 COL1-COL3。 COL4 包含一个数字。每个文件包含大约 200 行。我正在尝试对各行进行总结。例如:
文件 1
COL1 COL2 COL3 COL4
x y z 3
a b c 4
文件 2
COL1 COL2 COL3 COL4
x y z 5
a b c 10
然后返回一个新文件:
COL1 COL2 COL3 COL4
x y z 8
a b c 14
有没有无需 AWK 的简单方法?如果需要,我会使用 AWK,我只是认为可能有一个简单的 one-liner,我可以马上 运行。我心目中的AWK脚本感觉有点长。
谢谢
你说你有 "a number of files"。即,超过 2 个。
给定这 3 个文件(并且应该可以处理任何数量的文件):
$ cat f1 f2 f3
COL1 COL2 COL3 COL4
x y z 3
a b c 4
COL1 COL2 COL3 COL4
x y z 5
a b c 10
COL1 COL2 COL3 COL4
x y z 10
a b c 15
你可以这样做:
$ awk 'FNR==1{next}
{sum[]+=}
END{print "COL1 COL4";
for (e in sum) print e, sum[e]} ' f1 f2 f3
COL1 COL4
x 18
a 29
不清楚你打算用 COL2 或 COL3 做什么,所以我没有添加。
如果所有文件都有相同的header - awk 解决办法:
awk '!f && FNR==1{ f=1; print [=10=] }FNR>1{ s[FNR]+=$NF; $NF=""; r[FNR]=[=10=] }
END{ for(i=2;i<=FNR;i++) print r[i],s[i] }' File[12]
输出(2 个文件):
COL1 COL2 COL3 COL4
x y z 8
a b c 14
这种方法可以应用于多个文件(在这种情况下,您可以指定 globbing File*
for filename expansion)
多一个选项。
命令:
paste f{1,2}.txt | sed '1d' | awk '{print ,,,+}' | awk 'BEGIN{print "COL1","COL2","COL3","COL4"}1'
结果:
COL1 COL2 COL3 COL4
x y z 8
a b c 14
它的作用:
测试文件:
$ cat f1.txt
COL1 COL2 COL3 COL4
x y z 3
a b c 4
$ cat f2.txt
COL1 COL2 COL3 COL4
x y z 5
a b c 10
命令:paste f{1,2}.txt
加入 2 个文件并给出输出:
COL1 COL2 COL3 COL4 COL1 COL2 COL3 COL4
x y z 3 x y z 5
a b c 4 a b c 10
命令:sed '1d'
旨在暂时删除 header
命令:awk '{print ,,,+}'
Returns COL1-3 并根据粘贴结果求和 $4 和 $8。
命令:awk 'BEGIN{print "COL1","COL2","COL3","COL4"}1'
添加 header 返回
编辑:
在 @mklement0 评论之后,他对 header 处理是正确的,因为我忘记了 NR==1
部分。
所以,我也将在这里代理他的更新版本:
paste f{1,2}.txt | awk '{ print , , , (NR==1 ? : + ) }'
将 paste
与 awk
相结合,如
paste
合并输入文件中的相应行,- 将单个输入行流发送到
awk
,每个输入行包含要汇总的所有字段。
假设输入文件和列的数量固定,Kristo 的答案可以简化(使处理效率更高):
paste file1 file2 | awk '{ print , , , (NR==1 ? : + ) }'
注意:以上生成 space 分隔的输出列,因为 awk
的默认值为 OFS
,输出字段分隔符,是单个 space.
假设所有文件都具有相同的列结构和行数,下面是解决方案的概括,其中:
- 概括为超过 2 个输入文件(以及超过 2 个数据行)
- 概括为任意数量的字段,只要要汇总的字段是最后一个。
#!/bin/bash
files=( file1 file2 ) # array of input files
paste "${files[@]}" | awk -v numFiles=${#files[@]} -v OFS='\t' '
{
row = sep = ""
for(i=1; i < NF/numFiles; ++i) { row = row sep $i; sep = OFS }
sum = $(NF/numFiles) # last header col. / (1st) data col. to sum
if (NR > 1) { for(i=2; i<=numFiles; ++i) sum += $(NF/numFiles * i) } # add other cols.
printf "%s%s%s\n", row, OFS, sum
}
'
请注意,\t
(制表符)用于分隔输出字段,并且由于依赖 awk
的默认行拆分为字段,因此保留了准确的输入白色space 不保证字段之间。
$ awk '
NR==1 { print }
{ sum[FNR]+=$NF; sub(/[^[:space:]]+[[:space:]]*$/,""); pfx[FNR]=[=10=] }
END { for(i=2;i<=FNR;i++) print pfx[i] sum[i] }
' file1 file2
COL1 COL2 COL3 COL4
x y z 8
a b c 14
以上内容将在任何 UNIX 系统上与任何 awk、任何数量的输入文件以及这些文件的任何内容一起稳健而高效地工作。唯一的潜在问题是它必须在内存中保留相当于 1 个这些文件的内容,因此如果每个文件都非常大,那么您可能会耗尽可用内存。