awk/sed删除重复项并合并置换列
我有以下文件:awk/sed删除重复项并合并置换列,awk,Awk,我有以下文件: 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行的重复项)。此外,我还希望合并第1列和第2列被排列的条目(例如,第1行和第4行)。这意味着该列表应导致: ABC
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行的重复项)。此外,我还希望合并第1列和第2列被排列的条目(例如,第1行和第4行)。这意味着该列表应导致:
ABC MNH 1 4
UHR LOI 2 8
QWE LOI 3
PUQ LOI 5
然而,这个文件是巨大的。大约2-3 TB。这可以用awk/sed来完成吗?这对救援总是有帮助的
$ 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
如果有足够的重复项和足够的RAM可以将结果保存在内存中,则可以在输入文件的一次传递中删除重复项,然后迭代所有剩余的值对:
#/usr/bin/perl
使用警告;
严格使用;
使用功能qw/say/;
我的%keys;
而(){
咀嚼;
my($col1,$col2,$col3)=拆分“”;
$keys{$col1}{$col2}=$col3,除非存在$keys{$col1}{$col2};
}
$, = " ";
while(my($col1,$sub)=每个%keys){
而(my($col2,$col3)=每个%$sub){
其次,除非定义为$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}){
下一个
}
比如$col1、$col2、$col3;
}
}
为了提高效率,这会以任意未排序的顺序生成输出
以及使用sqlite的方法(还需要大量额外的可用磁盘空间,并且列之间用制表符分隔,而不是任意空格):
#/垃圾箱/垃圾箱
输入=“$1”
sqlite3-batch-noheader-list temp.db 2>/dev/null如果前两列最多只有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
如果已经打印了排列,则可以通过第二个数组跟踪来清除。称之为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] } }
我不明白为什么您发布的内容是您的预期输出,因此您可能需要对其进行处理,但我认为这是解决问题的正确方法,因此只有“排序”处理内部存储多TB输入的问题(而排序设计用于分页等)虽然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讨论后的替代实现(需要GNU排序用于-s
稳定排序):
到目前为止你都试了些什么?那真是一个很大的文件。通常情况下,当人们说他们有一个巨大的文件,结果是20MB,我们会说“没问题!”。无论您尝试什么,或者有什么建议,我肯定会先在文件的20MB块上尝试,然后将所需时间乘以150000,看看它是否实用。另外,在一次处理未排序的文件时解决此问题的典型方法是在第1列和第2列中加载具有所有唯一顺序独立对的内存。这可能是实际的,也可能不是,这取决于重复的百分比。字符串在实际文件中的长度是多少。它们总是3个字符吗?这只是为了知道可能的组合数量。如果它们是3,那么您只有26^6个可能的唯一组合,因此可以使用awk进行管理。请稍候。行末的数字是否真的存在于您的数据中,或者您只是想向我们显示输入/输出中的输入行号?@EdMorton我假设总是3个字符。我想保存subsep@EdMorton数组a
中的每个条目表示找到的原始key1
。我测试key1
是否在数组中以检查是否存在重复项,但如果key2
在数组中,我们会遇到排列。最后,您应该在数组中同时包含key1
和key2
,以便进一步复制。有一种方法可以清理数组。我不确定,但我认为第一个管道和排序无法处理2TB文件。此外,在您的示例中,第一行中的输出6
,不应显示为键组合MNH ABC
,前面已经看到了值4
。这也意味着sort命令可能会更改重复键的原始顺序,这将影响输出。关于管道和排序,这里有一些有趣的信息:我相信类似于sort-s-T/path/to/extra/harddisk-S4G
的东西可能会做到这一点@riasc如果上述方法无效,请告知我们,我们将尝试提出另一种解决方案。
$ 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
$ 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