【问题标题】:How to find rows in a file with same values for specific columns using unix commands?如何使用 unix 命令在文件中为特定列查找具有相同值的行?
【发布时间】:2015-10-15 04:42:51
【问题描述】:

我有一个包含大量行的文件。每行包含由制表符分隔的 5 列。我想查找前 4 列具有相同值但第 5 列具有不同值的所有行。

name     age    address    phone    city
eric      5      add1      1234     City1
jerry     5      add1      1234     City2
eric      5      add1      1234     City3
eric      5      add1      1234     City4
jax       5      add1      1234     City5
jax       5      add1      1234     City6
niko      5      add1      1234     City7

这个表的结果应该是

 eric      5      add1      1234     City1
 eric      5      add1      1234     City3
 eric      5      add1      1234     City4
 jax       5      add1      1234     City5
 jax       5      add1      1234     City6

我尝试在sort 之后使用uniq -u -f4,但这会忽略前 4 个字段,在这种情况下会返回所有行。

【问题讨论】:

    标签: linux shell unix duplicate-data uniq


    【解决方案1】:

    我倾向于为此使用awk

    script.awk

    { x = count[$1,$2,$3,$4]++; line[$1,$2,$3,$4,x] = $0 }
    END {   for (key in count)
            {
                kc = count[key]
                if (kc > 1)
                {
                    for (i = 0; i < kc; i++)
                    {
                        print line[key,i]
                    }
                }
            }
        }
    

    对于每一行,以前四个字段值作为键的行数递增。以正确的顺序保存当前行。最后,对于计数大于 1 的每个键,打印为该键保存的每一行。

    样品运行

    $ awk -f script.awk data
    jax       5      add1      1234     City5
    jax       5      add1      1234     City6
    eric      5      add1      1234     City1
    eric      5      add1      1234     City3
    eric      5      add1      1234     City4
    $
    

    请注意,这会以与它们在文件中出现的不同顺序生成密钥(第一个 eric, 5, add1, 1234 条目出现在第一个 jax, 5, add1, 1234 条目之前)。

    如果有必要,可以解决这个问题。

    script2.awk

    {   x = count[$1,$2,$3,$4]++;
        line[$1,$2,$3,$4,x] = $0
        if (x == 0)
            seq[n++] = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4
    }
    END {   for (s = 0; s < n; s++)
            {
                key = seq[s]
                kc = count[key]
                if (kc > 1)
                {
                    for (i = 0; i < kc; i++)
                    {
                        print line[key,i]
                    }
                }
            }
        }
    

    SUBSEP 是用于分隔多项目键的组件的字符,因此对seq[n++] 的赋值记录了在count[$1,$2,$3,$4] 中用作索引的值。 seq 数组按它们出现的顺序记录每个键(前四列)。按顺序遍历该数组会按照第一个条目出现的顺序给出键。

    样品运行

    $ awk -f script2.awk data
    eric      5      add1      1234     City1
    eric      5      add1      1234     City3
    eric      5      add1      1234     City4
    jax       5      add1      1234     City5
    jax       5      add1      1234     City6
    $
    

    预处理数据以节省内存并加快处理速度

    上面的代码在内存中保存了大量数据。它具有数据文件中每一行的完整副本;它有一个前四个字段的键;它有另一个带有四个字段和一个整数的键。对于大多数实际目的,这是数据的 3 个副本。如果日期文件很大,那可能是个问题。但是,鉴于样本数据有jerry 的行出现在eric 的行中间,因此不可能做得更好——除非首先对数据进行排序。然后你知道所有相关的行都在文件中,你可以更简单地处理它。

    script3.awk

    {   
        new_key = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4
        if (new_key == old_key)
        {
            if (old_line != "") { print old_line; old_line = "" }
            print $0
        }
        else
        {
            old_line = $0
            old_key = new_key
        }
    }
    

    样品运行

    $ sort data | awk -f script3.awk
    eric      5      add1      1234     City1
    eric      5      add1      1234     City3
    eric      5      add1      1234     City4
    jax       5      add1      1234     City5
    jax       5      add1      1234     City6
    $
    

    当然,eric 按字母顺序排在jax 之前是巧合;通过排序,您会丢失原始数据序列。但是script3.awk 脚本在内存中最多保留两个键和一行,这不会对内存造成任何压力。与原始处理机制相比,添加排序时间可能仍然可以为您带来可观的节省。

    如果原始订单很关键,您必须做更多的工作。我认为它涉及对原始文件中的每一行进行编号,在前四个键将相同的键组合在一起之后使用行号作为第五个键进行排序,然后用相同的行号识别具有相同四个键值的每组行,然后再次对组号和组内的序列号进行排序,并将其提供给script3.awk 脚本中处理的修改版本。但是如果文件在千兆字节范围内,这仍然可能比原始文件更好。但是,唯一可以确定的方法是对实际大小的示例进行测量。

    例如:

    nl data |
    sort -k2,2 -k3,3 -k4,4 -k5,5 -k1,1n |
    awk '{ new_key = $2 SUBSEP $3 sUBSEP $4 SUBSEP $5
           if (old_key != new_key) { grp_seq = $1 }
           print grp_seq, $0
           old_key = new_key
         }' |
    sort -k1,1n -k2,2n
    

    这么多生成:

    1      1    name     age    address    phone    city
    2      2    eric      5      add1      1234     City1
    2      4    eric      5      add1      1234     City3
    2      5    eric      5      add1      1234     City4
    3      3    jerry     5      add1      1234     City2
    6      6    jax       5      add1      1234     City5
    6      7    jax       5      add1      1234     City6
    8      8    niko      5      add1      1234     City7
    

    然后,您可以应用忽略 $1$2script3.awk 的修改版本来生成所需的输出。或者,您可以通过删除两个前导列的程序运行显示的输出。

    【讨论】:

    • 我找到了一种使用 Ruby 脚本的方法,但这很棒。谢谢!这适用于非常大的文件(大约 1000 万行)吗?如果文件真的很大,命令很长一段时间都不会返回。我将输出重定向到一个新文件。
    • 必须在内存中保留整个文件的副本,加上前四列的副本,以及一些管理开销。鉴于数据没有按顺序呈现(jerry 出现在eric 的行中间),很难做得更好。但是,如果您对数据进行了预处理(sort;它不需要比这更复杂),您可以知道相关的行都在输入中,因此您不必保存所有内容。如果您正在处理数兆字节或更大的文件,这可能会非常节省。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-19
    • 1970-01-01
    • 2021-08-02
    • 2018-05-07
    • 2011-09-07
    相关资源
    最近更新 更多