使用AWK

时间:2018-05-16 04:25:08

标签: awk tab-delimited

我有多个文件没有标题,前四列相同,第五列不同。我必须使用awk将所有第五列的所有第五列以及如下所示的相应标题附加到单个最终制表符分隔的文本文件中。

File_1.txt

chr1    101845021   101845132   A   0
chr2    128205033   128205154   B   0
chr3    128205112   128205223   C   0
chr4    36259133    36259244    D   0
chr5    36259333    36259444    E   0
chr6    25497759    25497870    F   1
chr7    25497819    25497930    G   1
chr8    25497869    25497980    H   1

File_2.txt

chr1    101845021   101845132   A   6
chr2    128205033   128205154   B   7
chr3    128205112   128205223   C   7
chr4    36259133    36259244    D   7
chr5    36259333    36259444    E   10
chr6    25497759    25497870    F   11
chr7    25497819    25497930    G   11
chr8    25497869    25497980    H   12

File_3.txt

chr1    101845021   101845132   A   41
chr2    128205033   128205154   B   41
chr3    128205112   128205223   C   42
chr4    36259133    36259244    D   43
chr5    36259333    36259444    E   47
chr6    25497759    25497870    F   48
chr7    25497819    25497930    G   48
chr8    25497869    25497980    H   49

预期输出文件Final.txt

Part    Start   End Name    File1   File2   File3
chr1    101845021   101845132   A   0   6   41
chr2    128205033   128205154   B   0   7   41
chr3    128205112   128205223   C   0   7   42
chr4    36259133    36259244    D   0   7   43
chr5    36259333    36259444    E   0   10  47
chr6    25497759    25497870    F   1   11  48
chr7    25497819    25497930    G   1   11  48
chr8    25497869    25497980    H   1   12  49

2 个答案:

答案 0 :(得分:2)

文件顺序相同

如果可以安全地假设每个文件中的行的顺序相同,那么您可以非常简洁地完成这项工作:

awk '
FILENAME != oname { FN++; oname = FILENAME }
    { p[FNR] = $1; s[FNR] = $2; e[FNR] = $3; n[FNR] = $4; f[FN,FNR] = $5; N = FNR }
END {
    printf("%-8s %-12s %-12s %-4s %-5s %-5s %-5s\n",
           "Part", "Start", "End", "Name", "File1", "File2", "File3");
    for (i = 1; i <= N; i++)
    {
        printf("%-8s %-12d %-12d %-4s %-5d %-5d %-5d\n",
               p[i], s[i], e[i], n[i], f[1,i], f[2,i], f[3,i]);
    }
}' file_1.txt file_2.txt file_3.txt

当您开始使用新文件时,第一行会出现,并递增FN变量(因此文件1中的行可以用FN == 1标记,等等)。它将文件名记录在oname中,以便它可以发现更改。

第二行对每条数据行进行操作,将前四个字段存储在数组psen中(按记录编号索引)当前文件),并记录f中的第五列(由FN索引并记录编号)。它将当前记录编号记录在N

中的当前文件中

END块打印出标题,然后对于数组中的每一行(从1到N索引),打印出各个字段。

输出(不出所料):

Part     Start        End          Name File1 File2 File3
chr1     101845021    101845132    A    0     6     41   
chr2     128205033    128205154    B    0     7     41   
chr3     128205112    128205223    C    0     7     42   
chr4     36259133     36259244     D    0     7     43   
chr5     36259333     36259444     E    0     10    47   
chr6     25497759     25497870     F    1     11    48   
chr7     25497819     25497930     G    1     11    48   
chr8     25497869     25497980     H    1     12    49   

不同订单的文件

如果你不能依赖每个文件中相同顺序的记录,你必须更加努力。假设第一个文件中的记录按所需顺序排列,则以下脚本安排按顺序打印记录:

awk '
FILENAME != oname { FN++; oname = FILENAME }
    { key = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4
      if (FN == 1)
      {   p[key] = $1; s[key] = $2; e[key] = $3; n[key] = $4; f[FN,key] = $5; k[FNR] = key; N = FNR }
      else
      {   if (key in p)
            f[FN,key] = $5
          else
              printf "Unmatched key (%s) in %s\n", key, FILENAME
      }
    }
END {
    printf("%-8s %-12s %-12s %-4s %-5s %-5s %-5s\n",
           "Part", "Start", "End", "Name", "File1", "File2", "File3")
    for (i = 1; i <= N; i++)
    {
        key = k[i]
        printf("%-8s %-12d %-12d %-4s %-5d %-5d %-5d\n",
               p[key], s[key], e[key], n[key], f[1,key], f[2,key], f[3,key])
    }
}' "$@"

这是基于以前的脚本; FN处理是相同的。 SUBSEP变量用于分隔多索引数组中的下标。变量key包含与之相同的值 通过索引数组z[$1,$2,$3,$4]来生成。

如果处理第一个文件(FN == 1),则会创建数组psen中的值,并按key。第五列类似地记录在f中。密钥在文件中出现的顺序记录在数组k中,由(文件)记录编号索引。

如果处理第二个或第三个文件,请检查密钥是否已知,如果不是,则进行报告。假设已知,请再次在f中添加第五列。

打印类似,只是它从k按顺序收集键,然后打印相关值。

鉴于这些文件:

  • file_4.txt

    chr8    25497869    25497980    H   1
    chr7    25497819    25497930    G   1
    chr6    25497759    25497870    F   1
    chr5    36259333    36259444    E   0
    chr4    36259133    36259244    D   0
    chr3    128205112   128205223   C   0
    chr2    128205033   128205154   B   0
    chr1    101845021   101845132   A   0
    
  • file_5.txt

    chr2    128205033   128205154   B   7
    chr8    25497869    25497980    H   12
    chr3    128205112   128205223   C   7
    chr1    101845021   101845132   A   6
    chr6    25497759    25497870    F   11
    chr4    36259133    36259244    D   7
    chr7    25497819    25497930    G   11
    chr5    36259333    36259444    E   10
    
  • file_6.txt

    chr5    36259333    36259444    E   47
    chr4    36259133    36259244    D   43
    chr6    25497759    25497870    F   48
    chr8    25497869    25497980    H   49
    chr2    128205033   128205154   B   41
    chr3    128205112   128205223   C   42
    chr7    25497819    25497930    G   48
    chr1    101845021   101845132   A   41
    

该脚本产生输出:

Part     Start        End          Name File1 File2 File3
chr8     25497869     25497980     H    1     12    49   
chr7     25497819     25497930     G    1     11    48   
chr6     25497759     25497870     F    1     11    48   
chr5     36259333     36259444     E    0     10    47   
chr4     36259133     36259244     D    0     7     43   
chr3     128205112    128205223    C    0     7     42   
chr2     128205033    128205154    B    0     7     41   
chr1     101845021    101845132    A    0     6     41   

在许多情况下,这些脚本无法完全适应。例如,如果文件长度不同;如果有重复的键;如果在一个或两个文件中找不到其他文件中找到的密钥;如果第五列数据不是数字;如果第二列和第三列不是数字;如果只有两个文件,或列出的文件超过三个。 “非数字”问题实际上很容易解决;只需使用%s代替%d即可。但脚本很脆弱。他们在所示的生态系统中工作,但不是很普遍。必要的修复并不难以置信;但是,对于必须编码来说,它们是令人讨厌的。

可能有多于或少于3个文件

扩展前一个脚本以处理任意数量的文件,并输出制表符分隔数据而不是格式化(可读)数据并不是很困难。

awk '
FILENAME != oname { FN++; file[FN] = oname = FILENAME }
    { key = $1 SUBSEP $2 SUBSEP $3 SUBSEP $4
      if (FN == 1)
      {   p[key] = $1; s[key] = $2; e[key] = $3; n[key] = $4; f[FN,key] = $5; k[FNR] = key; N = FNR }
      else
      {   if (key in p)
              f[FN,key] = $5
          else
          {
              printf "Unmatched key (%s) in %s\n", key, FILENAME
              exit 1
          }

      }
    }
END {
    printf("%s\t%s\t%s\t%s", "Part", "Start", "End", "Name")
    for (i = 1; i <= FN; i++) printf("\t%s", file[i]);
    print ""
    for (i = 1; i <= N; i++)
    {
        key = k[i]
        printf("%s\t%s\t%s\t%s", p[key], s[key], e[key], n[key])
        for (j = 1; j <= FN; j++)
            printf("\t%s", f[j,key])
        print ""
    }
}' "$@"

关键是printf除非你告诉它,否则不会输出换行符,但print会输出换行符。该代码记录了用于打印列的实际文件名。它循环遍历文件数据数组,假设每个文件中的行数相同。

给出6个文件作为输入 - 三个原始文件,第一个文件的相反顺序的副本,以及第二个和第三个文件的置换副本,输出有6列额外数据,标识列:

Part    Start   End     Name    file_1.txt      file_2.txt      file_3.txt      file_4.txt      file_5.txt      file_6.txt
chr1    101845021       101845132       A       0       6       41      0       6       41
chr2    128205033       128205154       B       0       7       41      0       7       41
chr3    128205112       128205223       C       0       7       42      0       7       42
chr4    36259133        36259244        D       0       7       43      0       7       43
chr5    36259333        36259444        E       0       10      47      0       10      47
chr6    25497759        25497870        F       1       11      48      1       11      48
chr7    25497819        25497930        G       1       11      48      1       11      48
chr8    25497869        25497980        H       1       12      49      1       12      49

答案 1 :(得分:1)

假设两个文件都已排序,您可以使用join命令:

join -o "1.1,1.2,1.3,1.4,2.5,2.6,1.5" file3 <(join -o "1.1,1.2,1.3,1.4,1.5,2.5" file1 file2)

-o选项允许通过从两个文件中选择某些字段来格式化输出结果。 1.x2.x指的是给定的文件。例如,1.1指的是第一个文件的第一个字段。

由于join只接受2个文件,因此bash运算符<(...)用于创建临时文件。

使用pasteawk的另一种解决方案(仍假设文件已排序):

paste file* | awk '{print $1,$2,$3,$4,$5,$10,$15}'