如何使用unix命令在特定列的相同值的文件中查找行?

时间:2015-10-15 04:42:51

标签: linux shell unix duplicate-data uniq

我有一个包含大量行的文件。每行包含由制表符分隔的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

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

1 个答案:

答案 0 :(得分:0)

我倾向于使用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]
                }
            }
        }
    }

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

样品运行

$ 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

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