我有以下文件:
ABC MNH 1
UHR LOI 2
QWE LOI 3
MNH ABC 4
PUQ LOI 5
MNH ABC 6
QWE LOI 7
LOI UHR 8
我想删除所有重复项(基于前两列-例如,第6行是第4行的重复项)。我也想合并排列第一和第二列的条目(例如,第一和第四行)。这意味着此列表应导致:
ABC MNH 1 4
UHR LOI 2 8
QWE LOI 3
PUQ LOI 5
但是,此文件很大。大约2-3 TB。可以用awk / sed完成吗?
答案 0 :(得分:2)
我不明白您发布的内容为何是您的预期输出,因此您可能必须对其进行按摩,但是恕我直言,这是解决问题的正确方法,因此只有“ sort”在内部存储多TB输入(和sort旨在通过分页等方式实现。)而awk脚本一次仅处理一行,并且几乎不占用内存:
$ cat tst.sh
#!/bin/env bash
awk '{print ($1>$2 ? $1 OFS $2 : $2 OFS $1), $0}' "$1" |
sort -k1,2 |
awk '
{ curr = $1 OFS $2 }
prev != curr {
if ( NR>1 ) {
print rec
}
rec = $0
sub(/^([^[:space:]]+[[:space:]]+){2}/,"",rec)
prev = curr
next
}
{ rec = rec OFS $NF }
END { print rec }
'
$ ./tst.sh file
ABC MNH 1 4 6
PUQ LOI 5
QWE LOI 3 7
LOI UHR 8 2
在下面的注释中与@kvantour讨论之后的另一种实现(要求对-s
进行稳定排序的GNU排序):
$ cat tst.sh
#!/bin/env bash
awk '{print ($1>$2 ? $1 OFS $2 : $2 OFS $1), $0}' "$1" |
sort -s -k1,2 |
awk '
{ curr = $1 OFS $2 }
prev != curr {
if ( NR>1 ) {
print rec
}
rec = $0
sub(/^([^[:space:]]+[[:space:]]+){2}/,"",rec)
sub(/[[:space:]]+[^[:space:]]+$/,"",rec)
delete seen
prev = curr
}
!seen[$3,$4]++ { rec = rec OFS $NF }
END { print rec }
'
$ ./tst.sh file
ABC MNH 1 4
PUQ LOI 5
QWE LOI 3
UHR LOI 2 8
答案 1 :(得分:0)
GNU datmash一生对救援有帮助!
$ sort -k1,2 -u input.txt |
awk -v OFS="\t" '$2 < $1 { tmp = $1; $1 = $2; $2 = tmp } { print $1, $2, $3 }' |
sort -k1,2 |
datamash groupby 1,2 collapse 3 |
tr ',' ' '
ABC MNH 1 4
LOI PUQ 5
LOI QWE 3
LOI UHR 2 8
简而言之:
MNH ABC 6
变为ABC MNH 6
),并输出制表符分隔的列(datamash
与by一起使用默认)。datamash
为所有重复的前两列生成一行,并以逗号分隔的第三列值列表作为输出的第三列(类似于ABC MNH 1,4
) 大多数内存有效的解决方案都需要对数据进行排序,尽管sort
程序非常擅长这样做,但它仍然会使用一堆临时文件,因此您需要2-3个大约TB的可用磁盘空间。
如果要使用相同的数据处理大量工作,可能值得对它进行一次排序并重新使用该文件,而不是每次都将其作为管道的第一步进行排序:
$ sort -k1,2 -u input.txt > unique_sorted.txt
$ awk ... unique_sorted.txt | ...
如果有足够的重复项和足够的RAM,可以将结果保存在内存中,则可以一遍遍整个输入文件,并删除重复项,然后遍历所有其余的值对:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw/say/;
my %keys;
while (<>) {
chomp;
my ($col1, $col2, $col3) = split ' ';
$keys{$col1}{$col2} = $col3 unless exists $keys{$col1}{$col2};
}
$, = " ";
while (my ($col1, $sub) = each %keys) {
while (my ($col2, $col3) = each %$sub) {
next unless defined $col3;
if ($col1 lt $col2 && exists $keys{$col2}{$col1}) {
$col3 .= " $keys{$col2}{$col1}";
$keys{$col2}{$col1} = undef;
} elsif ($col2 lt $col1 && exists $keys{$col2}{$col1}) {
next;
}
say $col1, $col2, $col3;
}
}
出于效率考虑,这会以任意的未排序顺序产生输出。
以及使用sqlite的方法(还需要大量额外的可用磁盘空间,并且这些列由制表符分隔,而不是任意空格):
#!/bin/sh
input="$1"
sqlite3 -batch -noheader -list temp.db 2>/dev/null <<EOF
.separator \t
PRAGMA page_size = 8096; -- Make sure the database can grow big enough
CREATE TABLE data(col1, col2, col3, PRIMARY KEY(col1, col2)) WITHOUT ROWID;
.import "$input" data
SELECT col1, col2, group_concat(col3, ' ')
FROM (
SELECT col1, col2, col3 FROM data WHERE col1 < col2
UNION ALL
SELECT col2, col1, col3 FROM data WHERE col2 < col1
)
GROUP BY col1, col2
ORDER BY col1, col2;
EOF
rm -f temp.db
答案 2 :(得分:0)
您能否请尝试以下操作(尽管也可以通过一次读取Input_file来完成,也没有对大文件进行测试)。
awk '
FNR==NR{
if(++b[$2,$1]<2 && ++c[$1,$2]<2){
if(($2,$1) in a){
a[$2,$1]=(a[$2,$1]?a[$2,$1] OFS:"")$3
}
else{
a[$1,$2]=(a[$1,$2]?a[$1,$2] OFS:"")$3
}
}
next
}
(($1,$2) in a){
if(a[$1,$2]){
print $1,$2,a[$1,$2]
delete a[$1,$2]
}
}' Input_file Input_file
答案 3 :(得分:0)
如果前两列最多只能包含3个字符,那么前两列将有26 ^ 6种可能的组合。使用awk非常容易处理。
{ key1=$1$2; key2=$2$1 }
(key1 in a) { next } # duplicate :> skip
(key2 in a) { print $2,$1,a[key2],$3 } # permutation :> print
{ a[key1]=$3 } # store value
但是,这只会打印排列,并且根据要求最多打印2个元素。结果,数组a
将在数组中同时包含key1
和置换密钥key2
,以防发现置换,否则它将仅具有key1
。 / p>
可以使用第二个数组进行清理,以跟踪是否已打印排列。称为b
。这样,您可以从a
中删除2个元素,同时跟踪b
中的一个元素:
{ key1=$1$2; key2=$2$1 }
(key1 in b) || (key2 in b) { next } # permutation printed, is duplicate
(key1 in a) { next } # only duplicate, no permutation found
(key2 in a) { # permutation found
print $2,$1,a[key2],$3 # - print
delete a[key1] # - delete keys from a
delete a[key2]
b[key1] # - store key in b
next # - skip the rest
}
{ a[key1]=$3 }
END { for (k in a) { print substr(1,3,k),substr(4,3,k),a[k] } }