我有一个格式为两列的文件;-
A,val1
A,val2
A,val3
B,val1
B,val2
B,val3
我需要的是能够转换它的输出,以便第二列对于第一列中的每个唯一值都是水平的;-
A,val1,val2,val3
B,val1,val2,val3
我不确定最好的方法是使用 BASH 还是 AWK - 也许两者结合?如果有人可以请指出我正确的方向。
答案1
单独使用awk:
$ awk -F, 'BEGIN{OFS=FS} {a[$1] = a[$1] == "" ? $2 : a[$1] FS $2} END {for(i in a) print i,a[i]}' file
A,val1,val2,val3
B,val1,val2,val3
请注意,输出顺序无法保证 - 使用 GNU awk 很容易修复,但使用其他实现则更困难。输入数据不需要排序。
否则,使用 GNU datamash
datamash -t, groupby 1 collapse 2 < file
(如果输入未排序,请添加-s
)或与 Miller
mlr --nidx --fs ',' nest --implode --values --across-records --nested-fs ',' -f 2 file
或更紧凑的更新版本
mlr --nidx --fs ',' nest --ivar ',' -f 2 file
答案2
有多种方法可以使用 shell 脚本解决这个问题,但我更喜欢使用一个还不太标准的工具:磨坊主。您可以apt install miller
在 Ubuntu/Debian 上安装它。我发现 Miller 的动词是比 bash 或 awk 更自然的思考此类问题的工具。
如果问题中指定的数据存储在INPUT_FILE
:
A,val1
A,val2
A,val3
B,val1
B,val2
B,val3
然后是米勒的nest
动词可用于将多个记录(行)打包为字段 2 中有多个值的单个记录,并将字段 2 扩展为多个字段:
mlr --ocsv --headerless-csv-output \
nest --implode --values --across-records -f 2 then \
nest --explode --values --across-fields -f 2 INPUT_FILE
这会产生您想要的输出:
A,val1,val2,val3
B,val1,val2,val3
Miller 可能有一种更简单的方法来做到这一点,但这是我找到的第一个解决方案。
答案3
为了保证输出的顺序,请使用以下 awk 代码。在这里,我们维护一个散列,也称为关联数组,seen[...],每当遇到新键 ($1) 时,它就会以递增的 kounter 为键。
$ awk -F "," '
prev != $1 { prev = $1 }
!($1 in a) { seen[++n] = $1 }
{ a[$1] = a[$1] FS $2 }
END {
for (i=1; i<=n; i++) {
print seen[i] a[seen[i]]
}
}
' file
A,val1,val2,val3
B,val1,val2,val3
答案4
在每个 Unix 机器上的任何 shell 中使用任何 awk 并保留输出行顺序,同时在内存中一次仅存储 1 个 $1 键控块:
$ awk '
BEGIN { FS=OFS="," }
$1!=p { printf "%s%s", rec, sep; rec=p=$1; sep=ORS }
{ rec = rec OFS $2 }
END { print rec }
' file
A,val1,val2,val3
B,val1,val2,val3