linux 有办法按列'uniq'吗?

1mrurvl1  于 2023-08-03  发布在  Linux
关注(0)|答案(9)|浏览(103)

我有一个这样的.csv文件:

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

字符串
我必须从文件中删除重复的电子邮件(整行)(即上面例子中包含overflow@domain2.example的行之一)。如何仅在字段1上使用uniq(用逗号分隔)?根据manuniq没有列选项。
我尝试了sort | uniq,但它不工作。

k5hmc34c

k5hmc34c1#

sort -u -t, -k1,1 file

字符串

  • -u表示唯一
  • -t,所以逗号是分隔符
  • -k1,1用于关键字段1

试验结果:

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

cwxwcias

cwxwcias2#

awk -F"," '!_[$1]++' file

字符串

  • -F设置字段分隔符。
  • $1是第一个字段。
  • _[val]在hash _(一个常规变量)中查找val
  • ++递增,并返回旧值。
  • !返回逻辑非。
  • 在末尾有一个隐式打印。
b4lqfgs4

b4lqfgs43#

要考虑多列。
根据第1列和第3列排序并给予唯一列表:

sort -u -t : -k 1,1 -k 3,3 test.txt

字符串

  • -t :冒号是分隔符
  • -k 1,1 -k 3,3基于列1和列3
ahy6op9u

ahy6op9u4#

如果要使用uniq:。
<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2
给出:

kulphzqa

kulphzqa5#

如果你想保留你可以使用的最后一个副本

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

字符串
这是我的要求
在这里
tac将逐行反转文件

3df52oht

3df52oht6#

这是一个非常漂亮的方法。
首先格式化内容,以便比较唯一性的列具有固定宽度。一种方法是使用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 }'

2ekbmq32

2ekbmq327#

awk CLI,其行为类似于不带sortuniq,但仅捕获连续的重复

到目前为止,大多数其他答案都给出了删除重复项的方法,即使它们不是连续的。
这样做的问题是,它需要首先排序或在内存中存储潜在的巨大Map,这对于大型输入文件可能很慢/不可行。
因此,对于这些情况,这里有一个awk解决方案,像uniq一样,只捕获出现在连续行上的重复项。例如,要删除第一列上的所有连续重复项,我们可以使用$1,如下所示:

awk '$1 != last { print $0; last = $1; }' infile.txt

字符串
例如,考虑输入文件:

a 0
a 1
b 0
a 0
a 1


输出将是:

a 0
b 0
a 0


这里:

  • 第一个a 1列被删除,因为前一个a 0行具有重复的第一列a
  • 但是我们得到了第二个a 0列,因为b 0行破坏了连续性

awk脚本的工作原理很简单,它将前一行的列值存储在last值中,并将当前值与之进行比较,如果不同则跳过。
如果你知道你的输入数据有很多无用的连续重复,并且想在做任何更昂贵的排序处理之前清理一下,这种只连续的方法可能很有用。
如果你真的需要删除非连续的重复项,更健壮的解决方案通常是使用像SQLite这样的关系数据库,例如:how can I delete duplicates in SQLite?

快速Python脚本,用于删除最后N行中出现的重复内容

如果你需要更多的灵活性,但仍然不想支付完整的排序:
统一

#!/usr/bin/env python

import argparse
from argparse import RawTextHelpFormatter
import fileinput
import sys

parser = argparse.ArgumentParser(
    description='uniq but with a memory of the n previous distinct lines rather than just one',
    epilog="""Useful if you know that duplicate lines in an input file are nearby to one another, but not necessarily immediately one afte the other.

This command was about 3x slower than uniq, and becomes highly CPU (?) bound even on rotating disks. We need to make a C++ version one day, or try PyPy/Cython""",
    formatter_class=RawTextHelpFormatter,
)
parser.add_argument("-k", default=None, type=int)
parser.add_argument("-n", default=10, type=int)
parser.add_argument("file", nargs='?', default=[])
args = parser.parse_args()
k = args.k

lastlines = {}
for line in fileinput.input(args.file):
    line = line.rstrip('\r\n')
    if k is not None:
        orig = line
        line = line.split()[k]
    else:
        orig = line
    if not line in lastlines:
        print(orig)
    lastlines.pop(line, None)
    lastlines[line] = True
    if len(lastlines) == args.n + 1:
        del lastlines[next(iter(lastlines))]


此脚本查找前面-n行上的重复项,并且可以用于清除具有某种周期性模式的数据,这些模式阻止uniq对其执行太多操作。-k选择列。例如,考虑输入文件:
单一测试
然后:

./uniqn -k0 -n3 uniqn-test


给出:
例如,第二个1 a看到第一个1 a三行后,并跳过它作为-n3的结果。

需要考虑的一些内置uniq选项

虽然uniq没有一个很好的“只考虑第N”列,但它确实有一些标志,可以解决某些更受限制的情况,从man uniq
-f,-跳过字段=N:避免比较前N个字段
-s,--skip-chars=N:避免比较前N个字符
-w,--check-chars=N:每行比较不超过N个字符
字段是一串空格(通常是空格和/或TAB),然后是非空字符。在字符之前跳过字段。
如果有人将类似于--check-chars--check-fields修补到它,那么我们就完成了--skip-fields N-1 --check-fields 1。然而,它已经适用于第一字段的特定情况。
在Ubuntu 23.04上测试。

ckx4rj1h

ckx4rj1h8#

首先使用sort对文件进行排序,然后可以应用uniq
它似乎可以很好地对文件进行排序:

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

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

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

1yjd4xko

1yjd4xko9#

好吧,比用awk隔离列更简单,如果你需要删除给定文件中具有特定值的所有内容,为什么不直接使用grep -v:
例如,删除第二行中具有值“col 2”的所有内容: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'


(note- sed命令是草率的,因为它不包括转义值。另外,sed模式实际上应该类似于“[^|]+”(即任何不是分隔符的内容)。但希望这是足够清楚的。

相关问题