如何根据第3列中的唯一标识符打印第1列的第一行和第2列的最后一行

时间:2014-03-06 03:01:47

标签: bash sorting awk

我有一个制表符分隔文件,如下所示:

Het 157709  157731  Cluster.90  2   +
Het 157739  157760  Cluster.90  2   +
Het 164238  164259  Cluster.97  10  +
Het 164380  164401  Cluster.97  10  +
Het 164396  164417  Cluster.97  10  +
Het 164397  164421  Cluster.97  10  +
Het 164397  164420  Cluster.97  10  +
Het 164399  164420  Cluster.97  10  +
Het 164536  164561  Cluster.97  10  +
Het 164576  164598  Cluster.97  10  +
Het 164599  164615  Cluster.97  10  +
Het 164635  164656  Cluster.97  10  +
Het 198007  198031  Cluster.125 3   +
Het 198007  198028  Cluster.125 3   +
Het 198011  198035  Cluster.125 3   +

我正在寻找一种有效的方法来生成如下文件:

Het 157709  157760  Cluster.90  2   +
Het 164238  164656  Cluster.97  10  +
Het 198007  198035  Cluster.125 3   +

对于第4列中的每个唯一条目,我写了一行,其中包括第1列和第2列的第一行,后面是第3,4,5和6列中的最后一行。到目前为止,我已经尝试了以下解决方案但效率似乎很低:

for i in `awk '{print $4}' filename | sort | uniq`
    do
    fgrep -F $i -w filename | awk 'NR==1 {printf $1"\t"$2"\t"} END {print $3"\t"$4"\t"$5"\t"$6}' >>filename2
done

问题是,当我有一个巨大的文件(487559行)时,这需要永远。是否有更好的解决方案隐藏在某人的脑袋里?

4 个答案:

答案 0 :(得分:3)

只有第4列中的唯一条目在第5列到末尾始终具有相同的数据时,此单行才会起作用。你的例子是这样的,但我似乎并没有真正回答这个问题。不过,FWIW:

paste <(uniq -f3 file | cut -f1,2) <(tac file | uniq -f3 | tac | cut -f3-)

uniq有一个选项,可以控制要比较唯一性的字符数,以及要跳过的前导字段数和要跳过的前导字符数,但不包括要比较的字段数。

答案 1 :(得分:1)

这可以在单个awk中完成,这将比你的脚本更有效:

awk '!($4 in a){a[$4]=$1 FS $2; r[++i]=$4; b[$4]=$3 FS $4 FS $5 FS $6; next;} {b[$4]=$3 FS $4 FS $5 FS $6; next} END{for (k=1; k<=i; k++) print a[r[k]], b[r[k]]}' OFS='\t' file
Het 157709      157760 Cluster.90 2 +
Het 164238      164656 Cluster.97 10 +
Het 198007      198035 Cluster.125 3 +

使其可读:

awk '!($4 in a){
    a[$4]=$1 FS $2;
    r[++i]=$4;
    b[$4]=$3 FS $4 FS $5 FS $6;
    next;
}
{
    b[$4]=$3 FS $4 FS $5 FS $6;
    next;
}
END {
   for (k=1; k<=i; k++)
       print a[r[k]], b[r[k]]
}' OFS='\t' file

答案 2 :(得分:1)

以下是awk的另一种方式:

awk '
!seen[$4]++ { 
  col[$4] = $1 FS $2; 
  fld[++i] = col[$4] 
}
{ 
  sub(/([^ ]+ +){2}/,x); 
  line[i] = fld[i] FS $0 
} 
END { 
  for(x = 1; x <= i; x++) 
    print line[x] 
}' OFS='\t' file

输出:

Het 157709 157760  Cluster.90  2   +
Het 164238 164656  Cluster.97  10  +
Het 198007 198035  Cluster.125 3   +

答案 3 :(得分:0)

您的代码很慢,因为您为每个组启动了fgrepawk个进程。

您可以在第4列排序后,一次性处理整个文件,当然,您已经知道该怎么做。

所以只需用bash,python,ruby,perl,awk或你选择的任何语言来编写,这些语言从stdin逐行读取,并在第4列中记录最后看到的值。每当这个值发生变化时,做你需要的do:写出包含前两列中第一个看到的值以及后面列中最后看到的值的行。然后记录第1列和第2列的新值。它非常简单,但在第一行和最后一行可能会很棘手。