Bash - 根据列在行中添加值
Bash - adding values in row based on column
我的 csv 文件中的第 2 列有重复项。我想根据这些重复项添加第 1 列中的关联值。
csv 示例:
56, cc=DK
49, cc=US
34, cc=GB
32, cc=DE
32, cc=NZ
31, cc=DK
31, cc=GB
31, cc=GB
示例结果:
96, cc=GB # where 96 = 34+31+31
87, cc=DK # where 87 = 56+31
32, cc=DE
32, cc=NZ
您可以在 awk
中使用关联数组:
awk '{s[]+=}END{for(k in s)print s[k]", ",k}' inFile
为了可读性对其进行扩展,并使用 sum/key
而不是 s/k
:
{ # Do for each line.
sum[] += # Add first field to accumulator,
# indexed by second field.
# initial value is zero.
}
END { # Do this bit when whole file processed.
for (key in sum) # For each key like cc=US:
print sum[key] ", " key # Output the sum and key.
}
这是我盒子上的样品 运行:
pax$ echo;echo '56, cc=DK
49, cc=US
34, cc=GB
32, cc=DE
32, cc=NZ
31, cc=DK
31, cc=GB
31, cc=GB' | awk '{s[]+=}END{for(k in s)print s[k]", "k}'
32, cc=DE
96, cc=GB
32, cc=NZ
49, cc=US
87, cc=DK
尽管第一列的形式为 999,
(注意末尾的逗号),但它仍然有效,只是因为 awk
在数字上下文中评估字符串时仅使用在该上下文中有效的前缀。因此 45xyzzy
将变为 45
,更重要的是,49,
变为 49
.
Perl 解决方案:
perl -ane '$h{ $F[1] } += $F[0] }{ print "$h{$_}\t$_\n" for keys %h' input.csv
解释:
-n
逐行处理输入
-a
将空格上的输入行拆分为@F 数组中的字段
- 哈希 table %h 记录每个键的总和(2nd 列)。它只是将第一列的值添加到它。
}{
(称为"Eskimo greeting")将每行(-n
)执行的内容与处理整个输入后的代码运行分开[=25] =]
使用 awk
完成如此简单的任务没问题,但如果您有一堆类似的任务并且您将来可能需要更改它,那么很容易搞砸。
由于这是典型的数据库问题,请考虑使用 sqlite
。
您可以:
添加行名并删除多余的空格:
$ cat <(echo "num, name") originalInput.txt | tr -d ' ' > input.csv
将数据导入临时 sqlite 数据库:
$ sqlite3 --batch temp.db <<EOF!
.mode csv
.import input.csv input
EOF!
select 来自数据库:
$sqlite3 temp.db 'SELECT sum(num), name FROM input GROUP BY name'
32|cc=DE
87|cc=DK
96|cc=GB
32|cc=NZ
49|cc=US
它的代码稍微多一点,并使用外部 sqlite3
命令,但它显着 不易出错 并且更 灵活 .您可以轻松加入多个 csv 文件、使用花式排序等。
此外,想象一下您六个月后查看代码并试图快速理解它的作用。
我的 csv 文件中的第 2 列有重复项。我想根据这些重复项添加第 1 列中的关联值。
csv 示例:
56, cc=DK
49, cc=US
34, cc=GB
32, cc=DE
32, cc=NZ
31, cc=DK
31, cc=GB
31, cc=GB
示例结果:
96, cc=GB # where 96 = 34+31+31
87, cc=DK # where 87 = 56+31
32, cc=DE
32, cc=NZ
您可以在 awk
中使用关联数组:
awk '{s[]+=}END{for(k in s)print s[k]", ",k}' inFile
为了可读性对其进行扩展,并使用 sum/key
而不是 s/k
:
{ # Do for each line.
sum[] += # Add first field to accumulator,
# indexed by second field.
# initial value is zero.
}
END { # Do this bit when whole file processed.
for (key in sum) # For each key like cc=US:
print sum[key] ", " key # Output the sum and key.
}
这是我盒子上的样品 运行:
pax$ echo;echo '56, cc=DK
49, cc=US
34, cc=GB
32, cc=DE
32, cc=NZ
31, cc=DK
31, cc=GB
31, cc=GB' | awk '{s[]+=}END{for(k in s)print s[k]", "k}'
32, cc=DE
96, cc=GB
32, cc=NZ
49, cc=US
87, cc=DK
尽管第一列的形式为 999,
(注意末尾的逗号),但它仍然有效,只是因为 awk
在数字上下文中评估字符串时仅使用在该上下文中有效的前缀。因此 45xyzzy
将变为 45
,更重要的是,49,
变为 49
.
Perl 解决方案:
perl -ane '$h{ $F[1] } += $F[0] }{ print "$h{$_}\t$_\n" for keys %h' input.csv
解释:
-n
逐行处理输入-a
将空格上的输入行拆分为@F 数组中的字段- 哈希 table %h 记录每个键的总和(2nd 列)。它只是将第一列的值添加到它。
}{
(称为"Eskimo greeting")将每行(-n
)执行的内容与处理整个输入后的代码运行分开[=25] =]
使用 awk
完成如此简单的任务没问题,但如果您有一堆类似的任务并且您将来可能需要更改它,那么很容易搞砸。
由于这是典型的数据库问题,请考虑使用 sqlite
。
您可以:
添加行名并删除多余的空格:
$ cat <(echo "num, name") originalInput.txt | tr -d ' ' > input.csv
将数据导入临时 sqlite 数据库:
$ sqlite3 --batch temp.db <<EOF! .mode csv .import input.csv input EOF!
select 来自数据库:
$sqlite3 temp.db 'SELECT sum(num), name FROM input GROUP BY name' 32|cc=DE 87|cc=DK 96|cc=GB 32|cc=NZ 49|cc=US
它的代码稍微多一点,并使用外部 sqlite3
命令,但它显着 不易出错 并且更 灵活 .您可以轻松加入多个 csv 文件、使用花式排序等。
此外,想象一下您六个月后查看代码并试图快速理解它的作用。