我正在尝试采取这样的表格:
a b hello
a b goodbye
g g test
a c I say
将表折叠为第 1 列和第 2 列中每个唯一条目一行,但不会丢失第 3 列中的任何信息。第 3 列中的值可以作为逗号分隔列表附加。结果如下:
a b hello, goodbye
a c I say
g g test
我不知道从哪里开始。使用sort -k1,1 -k2,2 -u
,我得到以下结果:
a b hello
a c I say
g g test
我丢失了“再见”的条目,但我想保留它。有谁知道如何避免丢弃第 3 列中的数据,如上面的示例所示?
答案1
我不知道单独使用的方法sort
,但是您可以使用 awk 来“折叠”值,然后进行排序:
$ awk -F'\t' '
BEGIN{OFS=FS}
{k = $1 FS $2}
{a[k] = a[k] == "" ? $3 : a[k] "," $3}
END{for (k in a) print k,a[k]}
' file | sort
a b hello,goodbye
a c I say
g g test
使用最新版本的 GNU awk,您可以通过以下方式设置数组遍历顺序来避免外部排序PROCINFO
:
awk -F'\t' '
BEGIN{OFS=FS}
{k = $1 FS $2}
{a[k] = a[k] == "" ? $3 : a[k] "," $3}
END{PROCINFO["sorted_in"]="@ind_str_asc"; for (k in a) print k,a[k]}
' file
或者,使用 GNU datamash
datamash groupby 1,2 collapse 3 <file
或更详细(但更灵活)的米勒
mlr --nidx --fs tab nest --implode --values --across-records --nested-fs , -f 3 file
答案2
bash 中使用临时文件的两阶段解决方案:
$ cat table.csv
a b hello
a b goodbye
g g test
a c I say
$ WD=$(mktemp -d) ; while read K1 K2 V ; do echo -n ",$V" >>$WD/$K1:$K2 ; done <table.csv
$ sort -k1,1 -k2,2 -u table.csv | while read K1 K2 V ; do echo $K1 $K2 $(sed 's/^.//' <$WD/$K1:$K2) ; done
a b hello,goodbye
a c I say
g g test
该解决方案的要点是使用 shell 追加重定向 ( >>
) 来收集排序前相等的键的值。
答案3
它也可以用函数来完成sed
(抱歉,它不是真正人类可读的):
echo "a b hello
a c goodbye
g g foo
a c bar
a b test
a c I say" | sort -k1,1 -k2,2 | sed ":a N; s/\([^\t]*\)\t\([^\t]*\)\t\([^\t]*\)\n\1\t\2\t\([^\t]*\)/\1\t\2\t\3,\4/; ta; P; D; ba;"
输出正如预期的那样:
a b hello,test
a c bar,goodbye,I say
g g foo
- 首先排序,然后使用
sed
代替uniq
。 N;
会要求sed
处理 2 行(或“多一行”)而不是 1 行。s/.../.../
将合并两条线。\([^\t]*\)
将占用一列(尽可能多的字符,不带\t
)。\t
是一个制表符(分隔符)。\n\t\1\t\2\t
检查第二行是否有两个相似的第一列。/\1\t\2\t\3,\4/
将 2 行转换为一行,具有相同的第一列和第二列,并合并第三列。
:a ... ta;
就像一个while
循环(在替换工作时再次执行s
)。P; D;
如果我们在这里,则s
失败,因此我们在模式空间中有两条“不同”(在第一列或第二列)线。P
打印第一行(可能包含合并的行),并D
删除我们打印的行。ba;
循环,同时它可以读取行(模式空间中当前有 1 行)。