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