我想编写一段代码来操作 CSV 文件上的文本,其中包含以下内容:
71w - Rus,51200
71w - Phi,307200
71w - Ukr,307200
71w - Ukr,51200
71w - Mic,102400
71w - Mic,51200
71w - Jul,256000
71w - Jul,51200
71w - Pro,256000
71w - Uni,51200
71w - Ind,50176
71w - Ind,40960
71w - Sin,358400
71w - May,20480
71w - Tha,512000
71w - Tha,972800
71w - Bar,1280000
71w - Bar,102400
71w - Bar,2048000
71w - Upg,358400
71w - Leg,20480
71w - Res,153600
我想收集具有相同值的列并将其放在相应的行上,如下所示:
71w - Rus,51200
71w - Phi,307200
71w - Ukr,307200,51200
71w - Mic,102400,51200
71w - Jul,256000,51200
71w - Pro,256000
71w - Uni,51200
71w - Ind,50176,40960
71w - Sin,358400
71w - May,20480
71w - Tha,512000,972800
71w - Bar,1280000,102400,2048000
71w - Upg,358400
71w - Leg,20480
71w - Res,153600
谢谢。
答案1
做到这一点的一个好方法是使用关联数组或散列。每个哈希的键是第一个字段(我将它们称为“ids”,因为需要一个更好的术语),为每个键存储的值将是一个字符串,其中包含该值的逗号分隔列表id,或包含相同内容的数组。
awk:
这个 awk 版本使用逗号分隔的字符串,因为(在 awk 中)它比处理包含数组的关联数组更容易。
#!/usr/bin/awk -f
BEGIN {
FS=" *, *";
OFS="";
}
{
key=$1; $1=""; $0=$0;
if (length(ids[key]) > 0) {
ids[key]=ids[key]","$0;
} else {
ids[key] = $0
};
}
END {
for (k in ids) {
print k "," ids[k]
}
}
在 Perl 中,处理数组哈希(或“HoA”)并不比连接字符串更难(而且更有用/更灵活):
#!/usr/bin/perl -w
use strict;
my %ids = ();
while(<>) {
chomp;
my @F = split /\s*,\s*/;
push @{ $ids{$F[0]} }, $F[1];
};
END {
foreach my $key (keys %ids) {
print $key . ',' . join(",",@{ $ids{$key} }), "\n";
}
}
awk 和 perl 版本的输出是相同的:
71w - Ukr,307200,51200
71w - Bar,1280000,102400,2048000
71w - Res,153600
71w - Upg,358400
71w - Sin,358400
71w - Mic,102400,51200
71w - May,20480
71w - Tha,512000,972800
71w - Jul,256000,51200
71w - Uni,51200
71w - Ind,50176,40960
71w - Pro,256000
71w - Rus,51200
71w - Leg,20480
71w - Phi,307200
注意:awk 和 perl 版本的输出没有任何特定的顺序,并且每次运行时可能会以不同的顺序出现。这是因为 awk“关联数组”和 perl“哈希”(同一事物的两个名称)本质上是无序的。
sort
如果需要,您可以通过管道输出。或者,在 Perl 中您可以使用:
foreach my $key (sort keys %ids) {
代替:
foreach my $key (keys %ids) {
另外 - 因为我们将每个 id 的单独值存储在一个数组中,所以在 Perl 中也很容易对这些值进行排序。例如,将END
perl 版本中的整个块替换为:
END {
foreach my $key (sort keys %ids) {
print $key . ',' . join(",",sort @{ $ids{$key} }), "\n";
}
}
输出将是:
71w - Bar,102400,1280000,2048000
71w - Ind,40960,50176
71w - Jul,256000,51200
71w - Leg,20480
71w - May,20480
71w - Mic,102400,51200
71w - Phi,307200
71w - Pro,256000
71w - Res,153600
71w - Rus,51200
71w - Sin,358400
71w - Tha,512000,972800
71w - Ukr,307200,51200
71w - Uni,51200
71w - Upg,358400
答案2
使用 GNU datamash
:
$ datamash -t, -g 1 collapse 2 < file.csv
71w - Rus,51200
71w - Phi,307200
71w - Ukr,307200,51200
71w - Mic,102400,51200
71w - Jul,256000,51200
71w - Pro,256000
71w - Uni,51200
71w - Ind,50176,40960
71w - Sin,358400
71w - May,20480
71w - Tha,512000,972800
71w - Bar,1280000,102400,2048000
71w - Upg,358400
71w - Leg,20480
71w - Res,153600
使用排序输出-s
:
$ datamash -s -t, -g 1 collapse 2 < file.csv
71w - Bar,1280000,102400,2048000
71w - Ind,50176,40960
71w - Jul,256000,51200
71w - Leg,20480
71w - May,20480
71w - Mic,102400,51200
71w - Phi,307200
71w - Pro,256000
71w - Res,153600
71w - Rus,51200
71w - Sin,358400
71w - Tha,512000,972800
71w - Ukr,307200,51200
71w - Uni,51200
71w - Upg,358400
选项解释:
-s
分组前对输入进行排序-t,
使用逗号作为字段分隔符-g 1
第一个字段上的组collapse 2
在第二个字段上崩溃
看看“崩溃”的例子手动的。
答案3
假设您的输入是或可以通过排序等方式按输入中所示的 $1 值进行分组:
$ cat tst.awk
BEGIN { FS=OFS="," }
$1 != prev { if (NR>1) print rec; rec=prev=$1 }
{ rec = rec OFS $2 }
END { print rec }
。
$ awk -f tst.awk file
71w - Rus,51200
71w - Phi,307200
71w - Ukr,307200,51200
71w - Mic,102400,51200
71w - Jul,256000,51200
71w - Pro,256000
71w - Uni,51200
71w - Ind,50176,40960
71w - Sin,358400
71w - May,20480
71w - Tha,512000,972800
71w - Bar,1280000,102400,2048000
71w - Upg,358400
71w - Leg,20480
71w - Res,153600
或者如果您更喜欢简洁而不是清晰:
$ awk -F, '$1!=p{if(NR>1)print r;r=p=$1}{r=r FS$2}END{print r}' file
71w - Rus,51200
71w - Phi,307200
71w - Ukr,307200,51200
71w - Mic,102400,51200
71w - Jul,256000,51200
71w - Pro,256000
71w - Uni,51200
71w - Ind,50176,40960
71w - Sin,358400
71w - May,20480
71w - Tha,512000,972800
71w - Bar,1280000,102400,2048000
71w - Upg,358400
71w - Leg,20480
71w - Res,153600