![保留每列的唯一值(以逗号分隔)](https://linux22.com/image/225725/%E4%BF%9D%E7%95%99%E6%AF%8F%E5%88%97%E7%9A%84%E5%94%AF%E4%B8%80%E5%80%BC%EF%BC%88%E4%BB%A5%E9%80%97%E5%8F%B7%E5%88%86%E9%9A%94%EF%BC%89.png)
我有一个.tsv
在 Linux 系统上有一个(制表符分隔的列)文件,其中包含以下列,其中包含以逗号分隔的不同类型的值(字符串、数字):
col1 col2
. NS,NS,NS,true,true
. 12,12,12,13
1,1,1,2 door,door,1,1
我想保留独特的价值观(不幸的是我尝试过但不能)。这将是输出:
col1 col2
. NS,true
. 12,13
1,2 door,1
答案1
这是一个 Miller 变体,它将值字符串拆分为整数索引映射,然后通过交换映射的键和值(加入键返回到以逗号分隔的字符串):
mlr --tsv put 'for (k,v in $*) {
$[k] = joink(apply(splitnvx(v,","), func(k,v) {return{v:k}}),",")
}' file.tsv
或者,在 perl5 的帮助下列表::实用程序模块:
perl -MList::Util=uniq -F'\t' -lpe '
$_ = join "\t", map { join ",", uniq split /,/, $_ } @F
' file.tsv
答案2
使用磨坊主( mlr
) 循环遍历每个输入记录中的制表符分隔字段,用逗号分割字段的值,将每个生成的字符串添加为名为 的映射中的键,最后通过使用逗号作为分隔符seen
连接映射中的键来重写字段seen
:
$ mlr --tsv put 'for (k,v in $*) { seen={}; for (i in splitax(v,",")) { seen[i]=1 } $[k] = joink(seen,",") }' file
col1 col2
. NS,true
. 12,13
1,2 door,1
米勒的put
表达式印得很漂亮:
for (k, v in $*) {
seen = {};
for (i in splitax(v, ",")) {
seen[i] = 1
}
$[k] = joink(seen, ",")
}
将分割值添加为映射中的键可以对它们进行重复删除。该splitax()
函数将分隔符上的字符串拆分为数组,而不推断每个生成项的类型(它们将是字符串)。该joink()
函数将映射的键连接在一起形成由给定分隔符分隔的字符串。外循环中的k
和的值v
分别是字段名称及其值。
答案3
这是一个笨重的 Perl 方法:
$ perl -F'\t' -le '
%k=%l={};
print join("\t",
join(",",grep{++$k{$_}==1}split(/,/,$F[0])),
join(",",grep{++$l{$_}==1}split(/,/,$F[1]))
)' file.tsv
col1 col2
. NS,true
. 12,13
1,2 door,1
答案4
下面的程序就可以了(应该适用于所有支持数组(1)awk
操作的最近的 Awk 实现):delete
BEGIN{FS=OFS="\t"}
{
for (i=1;i<=NF;i++)
{
n=split($i,sf,",")
delete seen
fld=""
for (j=1;j<=n;j++)
{
if (!seen[sf[j]]++)
{
fld=fld (fld?",":"") sf[j]
}
}
$i=fld
}
print
}
将其存储为例如dedup.awk
并通过调用它
awk -f dedup.awk input.tsv
对您的输入文件进行操作。
这将设置\t
为输入和输出字段分隔符。然后它会
- 迭代 (
for (i=1;i<=NF;i++)
)行的所有字段 - 将每个字段拆分为子字段 (
sf
),
- 迭代所有子字段并将该字段重新组装到临时变量中
fld
,但仅添加该字段中尚未看到的那些子字段值。在数组中保存一条记录seen
。 - 最后将重新组合的字段赋值
fld
给当前字段$i
- 打印整行,包括迄今为止所做的所有修改
(1)感谢@EdMorton 暗示如今支持已经相当广泛。