在提供小计和总计的 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
我并不是说以上是您真正想要的,只是它做了您现有代码试图做的事情,但使用了有意义的名称和有效的语法。这是你的起点。
我有以下数据:
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
我并不是说以上是您真正想要的,只是它做了您现有代码试图做的事情,但使用了有意义的名称和有效的语法。这是你的起点。