为许多文件中的每列打印唯一值的计数

时间:2015-10-02 03:29:16

标签: linux awk

我有很多列的巨大二进制矩阵,我试图在每个字段中为每个文件计算零和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

3 个答案:

答案 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在下一个文件的开头是真的,所以我用它来触发打印每一行。因此,程序使用prevFNRprevFileName来记住上一个文件中的记录数以及要显示的文件名。打印代码是从两个不同的地方调用的,因此我将其放在一个函数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