将某些列中带有多个分隔条目的文件拆分为单独的行

时间:2017-06-20 04:52:57

标签: linux bash

我有一个非常大的文件,其中包含以下基本格式,还有许多其他字段:

 posA,id1,id2,posB,id3,name,(n additional fields)
 1,ENST7,ENSP93,1,ENSG92,Gene1
 2,ENST25;ENST76;ENST35,ENSP91;ENSP77;ENSP78,515;544;544,ENSG765,Gene2
 3,ENST25;ENST76;ENST35,ENSP91;ENSP77;ENSP78,515;544;544,ENSG765,Gene2
 4,ENST54;ENST93,ENSP83;ENSP36,1864;722,ENSG48,Gene3
 5,ENST54;ENST93,ENSP83;ENSP36,1864;722,ENSG48,Gene3
 6,ENST54;ENST93,ENSP83;ENSP36,1864;722,ENSG48,Gene3

第一行(posA = 1)每列有一个条目,不需要修改。对于某些列具有可变数量的多个条目的行,对于第三行(posA = 2),“id1”的第一个条目(ENST25)与“id2”(ENSP91)的第一个条目和第一个条目配对对于“posB”(515),依此类推,但具有单个条目的列(例如,“posA”,“id3”,“name”)适用于列2-4中的所有配对条目。除第2-4列之外的某些字段也很少包含多个条目。

我想将具有多个条目的列拆分为单独的行,同时保留其他列中的数据,如下所示:

 posA,id1,id2,posB,id3,name,(n additional fields)
 1,ENST7,ENSP93,1,ENSG92,Gene1
 2,ENST25,ENSP91,515,ENSG765,Gene2
 2,ENST76,ENSP77,544,ENSG765,Gene2
 2,ENST35,ENSP78,544,ENSG765,Gene2
 3,ENST25,ENSP91,515,ENSG765,Gene2
 3,ENST76,ENSP77,544,ENSG765,Gene2
 3,ENST35,ENSP78,544,ENSG765,Gene2
 4,ENST54,ENSP83,1864,ENSG48,Gene3
 4,ENST93,ENSP36,722,ENSG48,Gene3
 ...

解决此问题的最佳方法是什么?

谢谢!

3 个答案:

答案 0 :(得分:0)

以你的例子为例,最多会有两个复合属性,然后使用简单的参数扩展和子串移除,你可以很容易地完成你想要的事情,例如。

#!/bin/bash

while IFS=, read -r p a1 a2 a3; do 
    [[ $a1 =~ ';' ]] && { 
        printf "%s,%s,%s,%s\n" "$p" "${a1%;*}" "${a2%;*}" "$a3"
        printf "%s,%s,%s,%s\n" "$p" "${a1#*;}" "${a2#*;}" "$a3"
    } || printf "%s,%s,%s,%s\n" "$p" "$a1" "$a2" "$a3"
done < "$1"

[[ $a1 =~ ';' ]]检查';'$a1的位置,如果找到,则选择$a1中的第一个属性和$a2 ${a1%;*}${a2%;*}。然后,对于每个属性中的第二个属性,使用${a1#*;}${a2#*;}

如果';'中不包含$a1,则打印属性不变。 IFS=,确保参数在','上进行分词。

注意:您应该将文件名有效的验证等添加到最终脚本中。如果您愿意,还可以使用echo

示例使用/输出

$ splitattrib.sh file
Pos,Attribute1,Attribute2,Attribute3
1,a,b,-
2,c,e,+
2,d,f,+

答案 1 :(得分:0)

假设您的多个条目以分号;分隔,这是要执行的awk版本。

BEGIN {
    FS="[,]"
}
{
    if ($0 ~ /^[0-9].*/) {
        end_split_field = 0
        for (f=2;f<=NF;f++) {
            if ($f ~ /.*;.*/) {
                end_split_field=f
            }
        }

        if (end_split_field == 0) {
            print $0
        } else {
            for (f=2;f<=end_split_field;f++) {
                n = split($f, a, ";") #split and return the number
                for (i=1;i<=n;i++) {
                    b[f, i] = a[i]
                }
            }

            for (i=1;i<=n;i++) {
                printf $1","
                for (j=2;j<=end_split_field;j++) {
                    printf b[j, i]","
                }
                for (k=end_split_field;k<NF;k++) {
                    printf $k","
                }
                printf $NF"\n"
            }
        }
    } else {
        print $0
    }
}

将上面的内容保存为input.awk,输入和输出示例

$ cat input
Pos,Attribute1,Attribute2,Attribute3
1,a,b,-
2,c;d,e;f,+
3,g;h;i,j;k;l,-

我们可以获得拆分输出

$ awk -f input.awk input
Pos,Attribute1,Attribute2,Attribute3
1,a,b,-
2,c,e,+
2,d,f,+
3,g,j,-
3,h,k,-
3,i,l,-

答案 2 :(得分:0)

最好是把它分成三部分。

你有3种线条图案。一个有6列。另一个有12个,最后一个是9.

6列=&gt; 1行
12列=&gt; 3行
9列=&gt; 2行

不应修改您的6列。所以提醒12和9.你可以在ifelse ifelse中将它们分开。喜欢:

if( column == 6 ){...}  
else if( column == 12 ){...}  
else {...}   

这是 Perl one-liner 解决方案:

perl -a -F",|;" -lne '$s=scalar @F;if($s==6){print join ",",@F}elsif($s==12){print join",",@F[0,1,4,7,-2,-1];print join",",@F[0,1,5,8,-2,-1];print join",",@F[0,1,6,9,-2,-1];}else{print join",",@F[0,1,3,5,-2,-1];print join",",@F[0,1,4,6,-2,-1]} ' file

并输入,输出为:

 1,ENST7,ENSP93,1,ENSG92,Gene1
 2,ENST25,ENSP91,515,ENSG765,Gene2
 2,ENST25,ENSP77,544,ENSG765,Gene2
 2,ENST25,ENSP78,544,ENSG765,Gene2
 3,ENST25,ENSP91,515,ENSG765,Gene2
 3,ENST25,ENSP77,544,ENSG765,Gene2
 3,ENST25,ENSP78,544,ENSG765,Gene2
 4,ENST54,ENSP83,1864,ENSG48,Gene3
 4,ENST54,ENSP36,722,ENSG48,Gene3
 5,ENST54,ENSP83,1864,ENSG48,Gene3
 5,ENST54,ENSP36,722,ENSG48,Gene3
 6,ENST54,ENSP83,1864,ENSG48,Gene3
 6,ENST54,ENSP36,722,ENSG48,Gene3