【问题标题】:How to loop an awk command on every column of a table and output to a single output file?如何在表的每一列上循环 awk 命令并输出到单个输出文件?
【发布时间】:2013-02-05 22:15:16
【问题描述】:

我有一个由单个单元 1s、2s 和 3s 组成的多列文件。每列中有很多重复的单元,有时它会从一个单元切换到另一个单元。我想计算在每一列上发生了多少次这种切换。例如在第 1 列中,开关从 1 到 2 到 3 到 1,所以有 3 个开关,输出应该是 3。在第二列中,整列有 2s,所以变化为 0,输出为 0 .

我的输入文件有 4000 列,因此无法手动完成。该文件以空格分隔。

例如:

输入:

1 2 3 1 2 
1 2 2 1 3
1 2 3 1 2
2 2 2 1 2
2 2 2 1 2    ......
3 2 2 1 2 
3 2 2 1 1
1 2 2 1 1
1 2 2 1 2
1 2 2 1 1

期望的输出:

3    ## column 1 switch times
0    ## column 2 switch times
3    .....
0    
5    

我正在使用:

awk '{print $1}' <inputfile> | uniq | wc -l
awk '{print $2}' <inputfile> | uniq | wc -l
awk '{print $3}' <inputfile> | uniq | wc -l
....

这一次执行一列。它会给我第一列的输出“4”,稍后我将只计算 4-1 =3 以获得我想要的输出。但是有没有办法可以将这个 awk 命令写入一个循环并在每一列上执行并输出到一个文件?

谢谢!

【问题讨论】:

    标签: linux shell awk


    【解决方案1】:

    awk 告诉您变量 NF 的给定行中有多少个字段,因此您可以创建两个数组来跟踪您需要的信息。一个数组将保留给定列中最后一行的值。另一个将计算给定列中的开关数量。您还将跟踪最大列数(并将新列的计数设置为零,以便在该列的开关数为 0 时在最后的输出中正确打印它们)。您还要确保不计算从空字符串到非空字符串的转换——这发生在第一次遇到该列时。

    如果实际上文件的列数一致,那只会影响第一行数据。如果后续行实际上比第一行包含更多列,则添加它们。如果一列停止出现一段时间,我假设它应该从它停止的地方恢复(好像丢失的列与以前的值相同)。您可以决定不同的算法;这可以算作两次转换(从数字到空白以及从空白到数字。如果是这种情况,您必须修改计数代码。或者,也许更明智的是,您可以决定根本不允许不规则数量的列,在这种情况下,如果当前行中的列数与前一行中的列数不同,您可以提前退出(注意空行,或者它们也被取缔?)。

    而且你不会尝试将整个程序写在一行上,因为这将是不可理解的,而且真的没有必要。

    awk '{   if (NF > maxNF)
             {
                 for (i = maxNF + 1; i <= NF; i++)
                     count[i] = 0;
                 maxNF = NF;
             }
             for (i = 1; i <= NF; i++)
             {
                 if (col[i] != "" && $i != col[i])
                     count[i]++;
                 col[i] = $i;
             }
         }
         END {
             for (i = 1; i <= maxNF; i++)
                 print count[i];
         }' data-file-with-4000-columns
    

    鉴于您的示例数据(删除了点),脚本的输出符合要求:

    3
    0
    3
    0
    5
    

    这个带有锯齿行的替代数据文件:

    1 2 3 1 2
    1 2 2 1 3
    1 2 3 1 2
    2 2 2 1 2
    2 2 2 1 2 1 1 1
    3 2 2 1 2 2 1
    3 2 2 1 1
    1 2 2 1 1 2 2 1
    1 2 2 1
    1 2 2 1 1 3
    

    产生输出:

    3
    0
    3
    0
    3
    2
    1
    0
    

    根据我制定的规则,哪个是正确的 - 但如果您决定想要不同的规则来覆盖数据,您最终可能会得到不同的答案。

    如果您在最后一个循环中使用了printf("%d\n", count[i]);,则无需在循环中将计数值设置为零。您付钱,然后选择。

    【讨论】:

    • 感谢乔纳森的全面回答!非常感谢您的帮助!
    【解决方案2】:

    使用循环并为每个列 current 值保留一个数组,并为相应的 count 保留另一个数组:

    awk '{for(i=0;i<5;i++) if(c[i]!=$(i+1)) {c[i]=$(i+1); t[i]++}} END{for(i=0;i<5;i++)print t[i]-1}' filename
    

    请注意,这假定列的值不为零。如果您碰巧有零值,那么只需将数组 c 初始化为某个不会出现在文件中的唯一值。

    【讨论】:

      【解决方案3】:

      为了便于查看,已编码,SaveColx、CountColx 应该是数组。我至少会在结果中打印列号本身以进行检查:-)

      BEGIN { 
          SaveCol1 = " "
          CountCol1 = 0
          CountCol2 = 0
          CountCol3 = 0
          CountCol4 = 0
          CountCol5 = 0
      }
      {
      if ( SaveCol1 == " " ) {
          SaveCol1 = $1
          SaveCol2 = $2
          SaveCol3 = $3
          SaveCol4 = $4
          SaveCol5 = $5
          next
          }
      if ( $1 != SaveCol1 ) {
          CountCol1++
          SaveCol1 = $1
          }
      if ( $2 != SaveCol2 ) {
          CountCol2++
          SaveCol2 = $2
          }
      if ( $3 != SaveCol3 ) {
          CountCol3++
          SaveCol3 = $3
          }
      if ( $4 != SaveCol4 ) {
          CountCol4++
          SaveCol4 = $4
          }
      if ( $5 != SaveCol5 ) {
          CountCol5++
          SaveCol5 = $5
          }
      }
      END {
          print CountCol1
          print CountCol2 
          print CountCol3
          print CountCol4
          print CountCol5
      }
      

      【讨论】:

      • 问题说明实际数据有4000列。我不认为您的解决方案很容易扩展到 4000 列。
      • :-) 这就是“SaveColx,CountColx 应该是数组”这一点的用武之地。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-21
      • 1970-01-01
      • 1970-01-01
      • 2012-09-17
      • 1970-01-01
      • 2018-04-15
      • 1970-01-01
      相关资源
      最近更新 更多