如何使用 unix 命令在文件中查找特定列具有相同值的行?
How to find rows in a file with same values for specific columns using unix commands?
我有一个包含大量行的文件。每行包含 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
这个table的结果应该是
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 个字段,在这种情况下会 return 所有行。
为此我倾向于使用 awk
。
script.awk
{ x = count[,,,]++; line[,,,,x] = [=10=] }
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[,,,]++;
line[,,,,x] = [=12=]
if (x == 0)
seq[n++] = SUBSEP SUBSEP SUBSEP
}
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[,,,]
中用作索引的值。 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 = SUBSEP SUBSEP SUBSEP
if (new_key == old_key)
{
if (old_line != "") { print old_line; old_line = "" }
print [=14=]
}
else
{
old_line = [=14=]
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 = SUBSEP sUBSEP SUBSEP
if (old_key != new_key) { grp_seq = }
print grp_seq, [=16=]
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
然后您可以应用忽略 </code> 和 <code>
的 script3.awk
的修改版本来生成所需的输出。或者您可以 运行 通过一个程序显示的输出,该程序去掉了两个前导列。
我有一个包含大量行的文件。每行包含 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
这个table的结果应该是
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 个字段,在这种情况下会 return 所有行。
为此我倾向于使用 awk
。
script.awk
{ x = count[,,,]++; line[,,,,x] = [=10=] }
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[,,,]++;
line[,,,,x] = [=12=]
if (x == 0)
seq[n++] = SUBSEP SUBSEP SUBSEP
}
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[,,,]
中用作索引的值。 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 = SUBSEP SUBSEP SUBSEP
if (new_key == old_key)
{
if (old_line != "") { print old_line; old_line = "" }
print [=14=]
}
else
{
old_line = [=14=]
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 = SUBSEP sUBSEP SUBSEP
if (old_key != new_key) { grp_seq = }
print grp_seq, [=16=]
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
然后您可以应用忽略 </code> 和 <code>
的 script3.awk
的修改版本来生成所需的输出。或者您可以 运行 通过一个程序显示的输出,该程序去掉了两个前导列。