awk / sed删除重复项并合并排列的列

时间:2019-05-03 10:17:22

标签: 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行的重复项)。我也想合并排列第一和第二列的条目(例如,第一和第四行)。这意味着此列表应导致:

ABC     MNH     1 4
UHR     LOI     2 8
QWE     LOI     3
PUQ     LOI     5

但是,此文件很大。大约2-3 TB。可以用awk / sed完成吗?

4 个答案:

答案 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] } }