有没有一种方法可以按列进行“ uniq”?


195

我有一个.csv文件,如下所示:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

我必须从文件中删除重复的电子邮件(整行)(即overflow@example.com,上面示例中包含的行之一)。如何uniq仅在字段1(用逗号分隔)上使用?根据manuniq没有列选项。

我尝试了一些东西,sort | uniq但是没有用。

Answers:


325
sort -u -t, -k1,1 file
  • -u 为了独特
  • -t, 所以逗号是定界符
  • -k1,1 对于关键字段1

测试结果:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 

3
如果列本身包含逗号(带引号),则此方法不起作用
2011年

13
为什么需要-k1,1中的,1?为什么不只是-k1?
hello_there_andy 2014年

18
@hello_there_andy:在手册(man sort)中对此进行了说明。它代表开始和停止位置。
塞拉诺2015年

3
@CarlSmotricz:我测试,它证实了sort的手册页说:‘ 用,检查严格排序;没有,只输出第一相等的运行。’ 因此,确实是“排序之前重复项的首次出现”。-u--unique-c-c
Geremia

2
这也改变了行的顺序,不是吗?
rkachach

102
awk -F"," '!_[$1]++' file
  • -F 设置字段分隔符。
  • $1 是第一个领域。
  • _[val]val在哈希_(常规变量)中查找。
  • ++ 递增,并返回旧值。
  • ! 返回逻辑非。
  • 在结尾处有一个隐式打印。

4
这种方法比排序速度快两倍
bitek

9
这还具有使线条保持原始顺序的额外好处!
AffluentOwl 2015年

8
如果您需要最后一个 uniq而不是第一个 uniq,则此awk脚本将有所帮助:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
Sukima 2015年

3
@eshwar只是将更多字段添加到字典索引中!例如,!_[$1][$2]++可用于按前两个字段进行排序。但是,我的awk-fu不够强大,无法在一系列字段中独树一帜。:(
Soham Chowdhury

1
辉煌!此选项比答案更好,因为它可以保持行的顺序
rkachach

16

要考虑多列。

根据第1列和第3列排序并给出唯一列表:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : 冒号是分隔符
  • -k 1,1 -k 3,3 基于第1列和第3列

8

或者,如果您想使用uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

给出:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1

5
我想指出一个可能的简化:您可以转储cat!与其传递到tr中,不如让tr使用读取文件<。管道连接cat是新手经常使用的不必要的并发症。对于大量数据,将产生性能影响。
卡尔·斯莫特里奇

4
很高兴知道。谢谢!(当然,考虑“猫”和“懒惰”是有道理的;))
Carsten C.

可以用简化磁场的反转rev
Hielke Walinga

5

如果要保留重复项的最后一个,可以使用

 tac a.csv | sort -u -t, -r -k1,1 |tac

这是我的要求

这里

tac 将逐行反转文件


1

这是一个非常好的方法。

首先格式化内容,以使要比较的唯一性列为固定宽度。一种方法是将awk printf与字段/列宽度说明符(“%15s”)一起使用。

现在,uniq的-f和-w选项可用于跳过前面的字段/列,并指定比较宽度(列的宽度)。

这是三个例子。

在第一个示例中...

1)暂时使目标列的固定宽度大于或等于字段的最大宽度。

2)使用-f uniq选项跳过前面的列,并使用-w uniq选项将宽度限制为tmp_fixed_width。

3)从列中删除尾随空格以“恢复”其宽度(假设事先没有尾随空格)。

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

在第二个示例中...

创建一个新的uniq列1.在应用uniq过滤器后,将其删除。

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

第三个示例与第二个示例相同,但适用于多列。

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

-3

好了,比用awk隔离该列更简单,如果您需要删除给定文件中具有特定值的所有内容,那么为什么不做grep -v:

例如,删除第二行中值为“ col2”的所有内容:col1,col2,col3,col4

grep -v ',col2,' file > file_minus_offending_lines

如果这还不够好,因为某些行可能会因匹配值显示在不同的列中而导致不正确的剥离,因此您可以执行以下操作:

awk隔离违规列:例如

awk -F, '{print $2 "|" $line}'

-F将字段定界为“,”,$ 2表示第2列,后跟一些自定义定界符,然后是整行。然后,您可以通过删除以有问题的值开头的行来进行过滤:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

然后在定界符之前删除内容:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(请注意-sed命令很草率,因为它不包含转义值。而且sed模式应该确实类似于“ [^ |] +”(即,不是定界符)。但是希望这已经足够清楚了。


3
他不想清除行,他想保留带有特定字符串的行的单个副本。Uniq是正确的用例。
ingyhere

-3

通过sort首先对文件进行排序,然后可以应用uniq

似乎可以对文件进行排序:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

您还可以做一些AWK魔术:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 

这不是问题所要求的按栏唯一。这对于整个生产线来说都是唯一的。另外,您不必进行某种排序即可进行uniq。两者是互斥的。
Javid Jamae 2014年

1
是的,你是对的。尽管接受的答案更加简洁,但最后一个示例确实执行了问题所要求的内容。至于sort,那么uniqsort需要做之前完成uniq它不起作用否则(但你可以跳过第二个命令,并且只使用sort -u)。来自uniq(1):“从INPUT(或标准输入)中过滤相邻的匹配线,写入OUTPUT(或标准输出)。”
Mikael S

啊,关于uniq排序是正确的。我从未意识到uniq仅适用于相邻的行。我想我总是只使用sort -u。
Javid Jamae 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.