在提供小计和总计的 AWK 中重新排列数据文件

Rearanging data file in AWK providing subtotals and totals

我有以下数据:

cat st_in.txt 
2015-01-01  2   A   FI
2015-02-03  4   B   VI
2015-03-01  6   A   FI
2015-01-08  -4  C   VE
2016-01-05  -3  B   VE
2016-02-03  -1  D   FE
2016-04-01  -2  B   FE
2016-06-13  -5  D   VE
2017-01-01  2   A   VI
2017-02-03  3   A   VI
2017-02-04  8   C   FI
2017-01-05  -1  B   FE

我想这样输出数据(当然没有注释):

        2015    2016    2017    # ...

A       0       0       5       # >0 && ~/VI/       Ordered alphabetically asc
B       4       0       0       #       .                       .
sumVI   4       0       5

A       8       0       0       # >0 && ~/FI/               .
C       0       0       8       #       .                       .
sumFI   8       0       8

sumI    12      0       13      # sumI=sumFI+sumVI

B       0       -3      0       # <0 && ~/VE/               .
C       -4      0       0       #       .                       .
D       0       -5      0       #       .                       .
sumVE   -4      -8      0

B       0       -2      -1      # <0 && ~/FE/               .
sumFE   0       -2      -1      #       .

sumE    -4      -10     -1      # sumE=sumFE+sumVE

NET     8       -10     12      # NET=sumI+sumE

我是 awk 的新手,不知道如何处理这个问题。我已经阅读了 gnu.org 关于多维数组和数组数组的 awk 手册,我想我会在这里需要它们,但不完全理解它们是如何工作的。我可以这样做一年而不是多年。请注意,st_in.txt 非常大,跨度比本示例中的年数更长。另外,您是否可以推荐一个很好的资源来学习如何在 awk 中对数据表进行透视。

这是我目前尝试过的。然而,这不起作用:

cat trans1
#!/usr/bin/env bash

awk '
    BEGIN{OFS="\t"
    cat[]
    height[][] +=
    width[substr(,1,4)][][] +=
    }

    END{
    PROCINFO["sorted_in"]="@ind_str_asc";
    for (width in height){
        for (cat in height[width]){
            if(>0 && ~/VI/)
                {print cat, height[width]}
            else if(>0 && ~/FI/)
                {print cat, height[width]}
            else if(<0 && ~/VE/)
                {print cat, height[width]}
            else {print cat, height[width]}}}

    }
' "${@:--}"

我收到以下错误:

awk: cmd. line:11: (FILENAME=st_in.txt FNR=12) fatal: attempt to use array `width' in a scalar context

不是完整的解决方案,而是一种更结构化的方法,需要最终格式化...

$ awk 'BEGIN {SUBSEP=FS} 
             {split(,f1,"-"); 
              s=substr(,2); y=f1[1]; 
              a[s,,y,]=+; 
              a[s,,y,"sum"]+=; 
              a[s,"+",y,"sum"s]+=;
              a["+","+",y,"NET"]+=} 
       END   {for(k in a) print k,a[k]}' file | 
 sort -k1,2r -k4,4 -k3,3

I VI 2017 A 3
I VI 2015 B 4
I VI 2015 sumVI 4
I VI 2017 sumVI 5
I FI 2015 A 6
I FI 2017 C 8
I FI 2015 sumFI 8
I FI 2017 sumFI 8
I + 2015 sumI 12
I + 2017 sumI 13
E VE 2016 B -3
E VE 2015 C -4
E VE 2016 D -5
E VE 2015 sumVE -4
E VE 2016 sumVE -8
E FE 2016 B -2
E FE 2017 B -1
E FE 2016 D -1
E FE 2016 sumFE -3
E FE 2017 sumFE -1
E + 2015 sumE -4
E + 2016 sumE -11
E + 2017 sumE -1
+ + 2015 NET 8
+ + 2016 NET -11
+ + 2017 NET 12

这会根据数组的键创建各种小计,最后打印整个数组(和小计)。通过仔细选择密钥,您可以计算出您需要什么。

s是顶级类别,y是年份。

              a[s,,y,]=+; 

汇总所有重复条目,因为使用了所有字段

              a[s,,y,"sum"]+=; 

基于字段 4 值(VI、FI 等)的分组

              a[s,"+",y,"sum"s]+=;

基于顶级类别 (I,E) 的分组

              a["+","+",y,"NET"]+=} 

这总结了基于年份的所有内容。终于

  END   {for(k in a) print k,a[k]}

在文件末尾,提取数组中的所有条目并打印。

  sort -k1,2r -k4,4 -k3,3

根据 I/E VI,FI A/B/.. 和年份进行排序。

例如,作为练习,您可以通过将 y 删除或替换为常量(我使用 +)来轻松添加总和。

在这一行:

width[substr(,1,4)][][] +=

您将 width 声明为一个数组,因此您不能在这一行使用相同的名称:

for (width in height){

作为标量(另一个数组的索引,height)。只需将第二个更改为 wid 或其他名称即可消除错误消息。显然,将 width 更改为 wid,在循环中也用作 height[] 的索引。


以此为起点,我选择了更能代表它们所含内容的变量名称(尽管我不知道您的第 4 列代表什么,所以我只是将其命名为 box - 更改为有意义的名称)作为调试和增强代码的第一步,尝试帮助您理解每个代码的含义:

$ cat trans1
#!/usr/bin/env bash

awk '
    BEGIN { OFS="\t" }
    {
        year   = substr(,1,4)
        height = 
        cat    = 
        box    = 

        cats[cat]
        boxCat_2_Heights[box][cat] += height
        yearBoxCat_2_Widths[year][box][cat] += height
    }

    END {
        PROCINFO["sorted_in"]="@ind_str_asc"
        for (box in boxCat_2_Heights) {
            for (cat in boxCat_2_Heights[box]) {
                height = boxCat_2_Heights[box][cat]

                if      (height>0 && box~/VI/) { type = "type1" }
                else if (height>0 && box~/FI/) { type = "type2" }
                else if (height<0 && box~/VE/) { type = "type3" }
                else                           { type = "type4" }

                print box, cat, height, type
            }
        }
    }
' "${@:--}"

$ ./trans1 st_in.txt
FE      B       -3      type4
FE      D       -1      type4
FI      A       8       type2
FI      C       8       type2
VE      B       -3      type3
VE      C       -4      type3
VE      D       -5      type3
VI      A       5       type1
VI      B       4       type1

我并不是说以上是您真正想要的,只是它做了您现有代码试图做的事情,但使用了有意义的名称和有效的语法。这是你的起点。