我有很多列的巨大二进制矩阵,我试图在每个字段中为每个文件计算零和1,同时跟踪文件和标题。每个文件都有相同的标题和列数(但行数可变),它们是这样的:
File 1:
Header1 Header2 Header3 Header4
0 1 0 1
0 1 0 1
1 0 0 1
0 1 0 1
File 2:
Header1 Header2 Header3 Header4
0 1 0 0
0 0 0 0
0 0 0 1
期望输出,计数为0/1
Header1 Header2 Header3 Header4 Total
File1 1 3 0 4 4
File2 0 1 0 1 3
现在我有一个等于仅为file1的值的计数,但是每个行作为标题出现,而我希望原始标题保留为标题,而且这不会打印0如果没有...而且它不包含原始文件名,所以总体来说不对!你能指导我正确的方法吗?
awk 'NF>0{
for (i=1; i<=NF; i++)
if(NR==1)h[i]=$i;else if($i==1) a[i]++;
} END{for(i=1; i<=length(a); i++) print h[i], a[i], NR}' file1
答案 0 :(得分:4)
假设示例文件实际上应该有与列一样多的标题(示例有四列但只有三个标题),以下代码适用于我:
#!/bin/sh
awk '
function pr(filename) {
if (filename) printf ("%s",filename)
for (i=1; i<=NF; i++) {
if (filename)
printf ("%s%s",OFS,a[i])
else
printf ("%s%s",OFS,$i)
a[i] = 0
}
if (filename)
printf ("%s%s",OFS,prevFNR-1)
else
printf ("%sTotal",OFS)
printf ("\n")
}
FNR==1 {
pr(prevFileName)
prevFileName = FILENAME
next
}
NF>0 {
for (i=1; i<=NF; i++)
if ($i==1) a[i]++
prevFNR = FNR
}
END {
pr(FILENAME)
}' file1 file2
没有办法知道程序已到达任何给定文件的最后一行,但FNR==1
在下一个文件的开头是真的,所以我用它来触发打印每一行。因此,程序使用prevFNR
和prevFileName
来记住上一个文件中的记录数以及要显示的文件名。打印代码是从两个不同的地方调用的,因此我将其放在一个函数pr()
中,该函数使用prevFileName
第一次没有值FNR==1
的事实表明它应该打印标题行而不是计算的摘要信息。
输出结果为:
Header1 Header2 Header3 Header4 Total
file1 1 3 0 4 4
file2 0 1 0 1 3
答案 1 :(得分:4)
以下似乎对我有用:
awk '
# Gather headers, only from the first line of the first file.
NR==1{
for(i=1;i<=NF;i++){
h[i]=$i;
}
}
# Do not process header as if they were data.
FNR==1{ next; }
NF>limit{ limit=NF; }
# Step through data
{
f[FILENAME]++;
for(i=1;i<=NF;i++){
a[FILENAME,i]+=$i;
}
}
# Display what we found.
END{
# Headers...
printf("File\t");
for(i=1;i<=length(h);i++){
printf("%s\t",h[i])
}
print "Total";
# And data.
for(file in f){
printf("%s",file);
for(i=1;i<=limit;i++){
printf("\t%d",a[file,i])
}
printf("\t%d\n",f[file]);
}
}' file1 file2
请注意,我们为文件名保留了一个数组f[]
,因为awk 实际不支持多维数组。上面的脚本应该适用于任何旧的awk。 (我在FreeBSD中对它进行了测试。)虽然如果处理数百万个文件可能会遇到问题,因为数组使用非零内存量。另一方面,文件数也受shell命令行长度的限制。 : - )
我不确定的一件事是您的标头数与数据中的字段数不匹配的原因。但也许这足以让你完全接受它。
答案 2 :(得分:3)
它比你想象的要简单得多。使用GNU awk(您在代码中使用gawk扩展名length(array)
后已经使用过),用于ENDFILE:
$ cat tst.awk
BEGIN { OFS="\t" }
NR==1 { print "", $0, "Total" }
FNR>1 {
for (i=1; i<=NF; i++) {
cnt[i,$i]++
}
}
ENDFILE {
printf "%s%s", FILENAME, OFS
for (i=1; i<=NF; i++) {
printf "%d%s", cnt[i,1], OFS
}
print FNR-1
delete cnt
}
$ awk -f tst.awk file1 file2
Header1 Header2 Header3 Header4 Total
file1 1 3 0 4 4
file2 0 1 0 1 3
以上只在数组中存储了少量数据(一次在1个文件中每个字段的值计数),因此它使用的内存最少,操作非常少,因此运行速度非常快。
正如@ghoti所指出的那样,你可能根本不会使用gawk所以这里只是依赖于length(array)
的非gawk版本:
$ cat tst.awk
BEGIN { OFS="\t" }
NR==1 { print "", $0, "Total" }
FNR==1 { prt(); next }
{
for (i=1; i<=NF; i++) {
cnt[i,$i]++
}
}
END { prt() }
function prt() {
if (prevFilename) {
printf "%s%s", prevFilename, OFS
for (i=1; i<=NF; i++) {
printf "%d%s", cnt[i,1], OFS
}
print length(cnt) - NF
delete cnt
}
prevFilename = FILENAME
}
$ awk -f tst.awk file1 file2
Header1 Header2 Header3 Header4 Total
file1 1 3 0 4 3
file2 0 1 0 1 4