【问题标题】:append columns to tab delimited file using AWK使用 AWK 将列附加到制表符分隔的文件
【发布时间】:2018-05-16 04:25:07
【问题描述】:

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

文件_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

文件_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

文件_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

【问题讨论】:

  • 嗨,约翰,我不明白该怎么做?因为我只想将每个文件中的第 5 列添加为最终文件中的新列。是的,所有文件都读取了相同的记录。我还想在这些列中添加标题,因为我知道我想要文件的标题。

标签: awk csv


【解决方案1】:

文件顺序相同

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

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

【讨论】:

  • 我可能应该补充一点,我怀疑我忽略了一个简单的解决方案,但同样,这取决于代码在面对数据文件中的不规则性时需要有多大的弹性。
  • 您好 Jon,感谢您的输入,但是此代码不会创建制表符分隔的文件输出。另外,如果我有三个以上的文件怎么办。文件数量每次都会有所不同。是否可以有一个统一的 awk 代码,无论我有多少文件都可以使用?
  • 因此,更改格式以在字段之间使用制表符 (\t);也删除字段大小。这种改变很容易。我给出了格式良好的输出。如果你想要标签,那很好,但它们在 SO 上表现不佳。原则上,在将 OFS 设置为 "\t" 后,您可以将 printf 调用替换为 print 调用。不同数量的文件主要是安排在循环中打印尾随字段而不是像代码中那样硬编码 3 的问题。再次,直接处理。但是,由于print 以换行符结束其输出,因此您必须返回到printf
  • 我无法循环播放它。抱歉,我是新手。
  • @RonicK:请参阅我答案底部的更新部分。
【解决方案2】:

假设3个文件都已排序,可以使用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操作符&lt;(...)创建一个临时文件。


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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 2017-05-09
    • 2020-04-01
    • 1970-01-01
    • 2012-10-05
    • 2018-01-04
    相关资源
    最近更新 更多