我想从给定列(示例中的 $2)中删除重复字段(以逗号分隔)。
输入文件:
A 1,2,3,4
B 4,5,6,3
C 2,15
预期输出:
A 1,2,3,4
B 5,6
C 15
答案1
perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e'
您可以像这样运行上面的代码:
$ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e' afile
A 1,2,3,4
B 5,6
C 15
怎么运行的
首先调用perl
with-lpe
会执行以下 3 件事。
-l[octal]
启用行结束处理,指定行结束符-p
假设像 -n 一样循环,但也打印行,像 sed-e program
一行程序(允许多个-e,省略programfile)
这本质上是接收文件,去掉换行符,对一行进行操作,然后在完成后将换行符添加回其上。所以它只是循环遍历文件并依次对每个文件执行我们的 Perl 代码。
至于实际的 Perl 代码:
\s
表示空格字符(五个字符[ \f\n\r\t]
,\v
在较新版本中perl
,如[[:space:]]
)。\K
保留 \K 左边的内容,不要将其包含在 $& 中\S+
不在集合中的一个或多个字符 [ \f\n\r\t\v]
将join ",",
获取结果并重新连接每个字段,以便用逗号分隔。
Thesplit ",", $&
将获取由 the 找到的匹配项,\S+
并将它们拆分为仅字段,不带逗号。
将grep {!$seen{$_}++}
获取每个字段的编号,将其添加到哈希中,$seen{}
其中每个字段的编号是$_
我们遍历每个字段时的编号。每次“看到”字段编号时,都会通过++
运算符进行计数$seen{$_}++
。
grep{!$seen{$_}++}
如果字段值只出现过一次,则将返回该字段值。
修改一下看看发生了什么
如果您使用这一经过修改的令人厌恶的内容,您可以看到当 Perl 单行代码从文件中跨行移动时发生了什么。
$ perl -lpe 's/\s\K\S+/join ",", grep {!$seen{$_}++} split ",", $&/e; @a=keys %seen; @b=values %seen; print "keys: @a | vals: @b"' afile
keys: 4 1 3 2 | vals: 1 1 1 1
A 1,2,3,4
keys: 6 4 1 3 2 5 | vals: 1 2 1 2 1 1
B 5,6
keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1
C 15
$seen{}
这向您显示了处理文件中的一行结束时的内容。让我们看看文件的第二行。
B 4,5,6,3
这是我的修改版本将该行显示为:
keys: 6 4 1 3 2 15 5 | vals: 1 2 1 2 2 1 1
这就是说我们已经看到了字段#6(1 次)、字段#4(2 次)等以及字段#5(1 次)。因此,当grep{...}
返回结果时,如果该数组出现在这一行 (4,5,6,3) 中并且我们只看到它 1 次 (6,1,15,5),则只会返回该数组的结果。这两个列表的交集是 (5,6),这就是 所返回的内容grep
。
参考
答案2
{
tr -cs '0-9ABC' '[\n*]' |
nl -w1 -s: |
sort -t: -uk2,2 | sort -t: -k1,1n |
sed 's/[^:]*://;/^[ABC]/!{H;$!d
};x;y/\n/,/;s/,/\t/'
} <<\FILE
A 1,2,3,4
B 4,5,6,3
C 2,15
FILE
基本上,这只是采取一些措施来确保数据能够运行sort -u
并且仍能以与发送时相对相同的形状返回。
首先tr
将其输入中非数字的任何字节转换ABC
为换行符并压缩任何重复。所以每个字段都有自己的行。
nl
然后对输入中的每一行进行编号。它的输出看起来像:
1:A
2:1
3:2
...等等。
接下来,数据被sort
编辑两次。第一个sort
删除所有重复字段 - 它仅对每行的第二个数据字段进行操作,并用:
.第二个通过这次对第一个字段进行 ingsort
来恢复原始顺序 -nl
的编号顺序。sort
最后sed
将所有内容重新组合在一起。它收集其输入,删除所有的nl
插入,并且 ex
更改其行ABC
和$
最后一行的保持和模式缓冲区。在每行中,它将y
所有\n
ewlines 转换为,
逗号,并用最后的 a 替换<tab>
每行的第一行。
结果如何?
输出
A 1,2,3,4
B 5,6
C 15
朋友们,这就是 Unix 的魅力所在。
而且,因为我很好奇,这里是sed
单独的:
sed 's/\t\(.*\)/,\1,/;H;$!d;x;:t
s/,\([^,]*,\)\(.*,\)\1/,\1\2/;tt
s/,\(\n.\),/\1\t/g;s/,\(.*\),/\t\1/'
' <<\FILE
A 1,2,3,4
B 4,5,6,3
C 2,15
FILE
第一行和第三行就像准备/清理。第一个将整个文件拉入模式空间并确保每个字段都以逗号分隔,因此第一行,例如:
A,1,2,3,4,
通过后看起来是这样的。
第二行是完成所有工作的地方。它实际上是一个递归替换函数 - 它不断地替换任何可以在其他地方仅与自身匹配的逗号分隔字段,直到找不到更多的字段为止。这就是est命令的作用t
。
正如我所说,第三行只是清理所有内容。它将逗号和制表符等放回到原来的位置,最终结果是:
输出
A 1,2,3,4
B 5,6
C 15
这很有趣,但对任何相当大的数据块执行此操作都是计算机谋杀。这是递归正则表达式——可能是最糟糕的递归。因此,只需几行代码就可以完成这种工作,但以这种方式制作电子表格可能是不明智的。
答案3
有了awk
,这很容易使用array
,split
,以及常规的loop
:
{
split($2, elements, ",")
out = ""
for (i in elements) {
el = elements[i]
if (!(el in used)) {
out = out el ","
}
used[el] = 1
}
sub(/,$/, "", out)
$2 = out
}
1
对于每一行,我们用逗号分隔第二列并将这些位保存到数组中elements
。然后,我们使用循环构建该列的新值,检查我们之前是否见过该值。我们保留在(关联)数组中已经看到的值集used
。如果el in used
,我们以前见过这个,不应该把它放在输出中;否则,它是新的,我们将它连接起来out
并将其添加到我们看到的值集中,这样我们就不会再次使用它。最后,我们将构建的列表放回第二列。这本质上是您在任何其他语言中采用的方法。
将上面的代码放入一个文件中并使用awk -f
或单引号将其全部作为命令行上的参数运行。