【问题标题】:Is there a way to 'uniq' by column?有没有办法按列“uniq”?
【发布时间】:2010-12-27 07:15:51
【问题描述】:

我有一个这样的 .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 的行之一)。如何仅在字段 1(以逗号分隔)上使用 uniq?根据manuniq 没有列选项。

我用sort | uniq 尝试了一些东西,但它不起作用。

【问题讨论】:

    标签: linux shell sorting uniq


    【解决方案1】:
    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 
    

    【讨论】:

    • 为什么需要 -k1,1 中的 ,​​1?为什么不只是-k1?
    • @hello_there_andy:这在手册中有解释 (man sort)。它代表开始和停止位置。
    • @CarlSmotricz:我对其进行了测试,它证实了sort 的手册页所说的内容:“-u--unique-c,检查严格排序;没有-c,仅输出相等运行的第一个。”所以,确实是“排序前第一次出现重复”。
    • 这也改变了行的顺序,不是吗?
    • 它确实回答了具体问题,但标题并没有反映出这一点——即“uniq”还有其他选项不适用“sort -u”——例如,简单地报告哪些行是重复的(并且不会为唯一的行生成输出)。我想知道为什么 'uniq' 有一个 '--skip-fields=N' 选项,但没有选择要比较哪个字段的选项……这似乎是显而易见的事情。
    【解决方案2】:
    awk -F"," '!_[$1]++' file
    
    • -F 设置字段分隔符。
    • $1 是第一个字段。
    • _[val] 在哈希 _(一个常规变量)中查找 val
    • ++ 递增,并返回旧值。
    • ! 返回逻辑非。
    • 末尾有一个隐式打印。

    【讨论】:

    • 这种方法比排序快两倍
    • 这还有一个额外的好处,就是保持行的原始顺序!
    • 如果您需要 last uniq 而不是第一个,那么这个 awk 脚本会有所帮助:awk -F',' '{ x[$1]=$0 } END { for (i in x) print x[i] }' file
    • @eshwar 只需将更多字段添加到字典索引!例如,!_[$1][$2]++ 可用于按前两个字段排序。不过,我的awk-fu 不够强大,无法在一系列领域独树一帜。 :(
    • 太棒了!此选项比答案更好,因为它保持行顺序
    【解决方案3】:

    考虑多列。

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

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

    【讨论】:

      【解决方案4】:

      或者如果你想使用 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
      

      【讨论】:

      • 我想指出一个可能的简化:您可以转储cat!与其通过管道输入 tr,不如让 tr 使用< 读取文件。通过cat 进行管道传输是新手常用的不必要的复杂操作。对于大量数据,会产生性能影响。
      • 可以使用rev简化字段的反转。
      • @HielkeWalinga 我认为rev 会反转每一行中的字符,而不是字段?!
      • @Fixee 是的,但是字段的顺序也是如此,并且字符反转的字段的唯一性无关紧要。所以像这样:<mycsv.cvs tr -s , ' ' | rev | uniq -f 4 | rev
      【解决方案5】:

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

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

      这是我的要求

      这里

      tac 将逐行反转文件

      【讨论】:

        【解决方案6】:

        这是一个非常漂亮的方法。

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

        现在 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 }'
        

        【讨论】:

          【解决方案7】:

          嗯,比使用 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 列,后跟一些自定义分隔符,然后是整行。然后,您可以通过删除 begin 带有违规值的行来过滤:

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

          然后去掉分隔符之前的东西:

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

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

          【讨论】:

          • 他不想清除行,他想保留具有特定字符串的行的单个副本。 Uniq 是正确的用例。
          【解决方案8】:

          先用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。两者是互斥的。
          • 是的,你是对的。尽管接受的答案更清晰,但最后一个示例完成了问题的要求。关于sort,然后uniqsort需要在做uniq之前完成,否则它不起作用(但你可以跳过第二个命令,直接使用sort -u)。来自uniq(1):“从 INPUT(或标准输入)过滤相邻匹配行,写入 OUTPUT(或标准输出)。”
          猜你喜欢
          • 1970-01-01
          • 2020-02-01
          • 1970-01-01
          • 2023-03-16
          • 2020-08-06
          • 2011-03-04
          • 2019-11-09
          • 2018-02-24
          • 1970-01-01
          相关资源
          最近更新 更多