对几列进行 Sort -u (或 sort | uniq),但保留第三列中的信息,并将其附加到保留的行?

对几列进行 Sort -u (或 sort | uniq),但保留第三列中的信息,并将其附加到保留的行?

我正在尝试采取这样的表格:

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 行)。

相关内容