根据第一列组合几个制表符分隔文件的某些列

Posted

技术标签:

【中文标题】根据第一列组合几个制表符分隔文件的某些列【英文标题】:Combining certain columns of several tab-delimited files based on first column 【发布时间】:2014-09-22 16:01:30 【问题描述】:

inFile 中的第一列包含一个不一定存在于所有 inFiles 中的字符串

每个 inFile 中的第 2 和第 7 列包含 Title# 字符串

使用 AWK,我无法正确地将其拼凑在一起。我对描述性变量的使用有望帮助澄清我正在尝试做的事情。这些是我认为我需要的组件:

    制表符分隔的输入文件:-F'\t' 增加第一列中的字符串,但每个“名称”只添加一次到“1stColumnNames”:!1stColumnNames[$1]++ name[++i] = $1 为每个 .tsv 文件创建一个新索引来存储每个文件的值以避免覆盖每列的值:!r[FILENAME]++ ++argind 在每个文件的第 2 列和第 7 列中存储对应的列值: 2ndColumnVals[$1, argind] = $2 7thColumnVals[$1, argind] = $7 打印所有 1stColumnNames 以及相关的 2ndColumnVals 和 7thColumnVals,包括它们的标题 'Title1' 'Title2' 'Title3' 等:????? 对于特定 2ndColumnVals 或 7thColumnVals 为空的索引值,打印为 Mtee:????? 对当前工作目录中的所有 .tsv 文件执行此操作并输出一个新的 tsv 文件:*.tsv > outFile.tsv

示例文件

inFile1.tsv

Names   Title1  Title2
AAAA    1111    123456
BBBBB   1111    123456
CCC     1111    123456

inFile2.tsv

Names   Title3  Title4
BBBBB   2222    789456
DDDDD   2222    789456
EEEE    2222    789456

inFile3.tsv

Names   Title5  Title6
AAAA    3333    987654
CCC     3333    987654
EEEE    3333    987654

outFile123.tsv

Names   Title1  Title2  Title3  Title4  Title5  Title6
AAAA    1111    123456  Mtee    Mtee    3333    987654  
BBBBB   1111    123456  2222    789456  Mtee    Mtee
CCC     1111    123456  Mtee    Mtee    3333    987654
DDDDD   Mtee    Mtee    2222    789456  Mtee    Mtee
EEEE    Mtee    Mtee    2222    789456  3333    987654

测试脚本

GNU Awk 4.0.1 位于 /usr/bin/awk ,所以我制作了这个文件并在 3 个输入文件所在的同一工作目录中执行它:

命名为 script1.sh
#### Example Usage:  script1.sh inFile1.tsv inFile2.tsv inFile3.tsv > outFile123.tsv

awk -F'\t' '
FNR==1  ++numFiles
!seen[$1]++  keys[++numKeys] = $1 
 a[$1,numFiles] = $2 FS $3 
END 
    for (keyNr=1; keyNr<=numKeys; keyNr++) 
        key = keys[keyNr]
        printf "%s", key
        for (fileNr=1;fileNr<=numFiles;fileNr++) 
            printf "\t%s", ((key,fileNr) in a ? a[key,fileNr] : "Mtee\tMtee")
        
        print ""
    

' "$@"

运行 awk -F script1.awk inFile1.tsv inFile2.tsv inFile3.tsv &gt; outFile123.tsv 会打印以下错误消息:

awk: cmd. line:1: inFile1.tsv

awk: cmd. line:1: ^ syntax error

来自 konsolebox 的测试脚本 2

完美运行,但我试图通过评论来理解每一行:

#!/usr/bin/awk -f
#### named as script2.awk
#### Example Usage:  awk -f script2.awk inFile1.tsv inFile2.tsv inFile3.tsv > outFile123.tsv

BEGIN  FS = "\t"  #input File Style is tab-delimited
 sub(/\r/, "")    #remove all carriage return characters
!f[FILENAME]++  ++indx    #for all files inputted make a single index called indx
!a[$1]++  keys[i++] = $1  #the new indx comprises only unique strings in column 1
 b[$1, indx] = $2 FS $3   #the 2nd and 3rd column are tab delimited and each pair that corresponds to a string saved in keys gets stored after the 1st column string in matrix b
END 
    for (i = 0; i in keys; ++i)    #????
        key = keys[i]   #????
        printf "%s", keys   #prints out all strings in the index column 1 stored as keys
        for (j = 1; j <= indx; ++j)    #????
            v = b[key, j]   #????
            printf "\t%s", length(v) ? v : "Mtee" FS "Mtee" #print out strings as tab delimited and replace any lengths of 1 char to two Mtee separated by a tab
        
        print ""    #????
    

【问题讨论】:

+1 以获得清晰的描述、可测试的输入/输出,并努力自己解决! script1.awk 不是 awk 脚本,它是调用 awk 的 shell 脚本。为清楚起见,将其重命名为script1.sh,使其像任何其他shell 脚本一样可执行,将"$@" 添加到awk 脚本的末尾,以便shell 将awk 传递给它的参数列表,然后以script1.sh inFile1.tsv inFile2.tsv inFile3.tsv &gt; outFile123.tsv 执行它。我编辑了你的问题以表明这一点。 【参考方案1】:

你需要这样的东西:

Gawk 版本(用于 Gawk 4.0+ 中的 ARGIND 加上真正的 2D 数组):

$ gawk -F'\t' '
!seen[$1]++  keys[++numKeys] = $1 
 a[$1][ARGIND] = $2 FS $3 
END 
    for (keyNr = 1; keyNr <= numKeys; keyNr++) 
        key = keys[keyNr]
        printf "%s", key
        for (fileNr = 1; fileNr <= ARGIND; fileNr++) 
            printf "\t%s", (fileNr in a[key] ? a[key][fileNr] : "Mtee\tMtee")
        
        print ""
    

' file1 file2 file3

非 gawk 版本:

awk -F'\t' '
FNR==1  ++numFiles
!seen[$1]++  keys[++numKeys] = $1 
 a[$1,numFiles] = $2 FS $3 
END 
    for (keyNr=1; keyNr<=numKeys; keyNr++) 
        key = keys[keyNr]
        printf "%s", key
        for (fileNr=1;fileNr<=numFiles;fileNr++) 
            printf "\t%s", ((key,fileNr) in a ? a[key,fileNr] : "Mtee\tMtee")
        
        print ""
    

' file1 file2 file3
Names   Title1  Title2  Title3  Title4  Title5  Title6
AAAA    1111    123456  Mtee    Mtee    3333    987654
BBBBB   1111    123456  2222    789456  Mtee    Mtee
CCC     1111    123456  Mtee    Mtee    3333    987654
DDDDD   Mtee    Mtee    2222    789456  Mtee    Mtee
EEEE    Mtee    Mtee    2222    789456  3333    987654

【讨论】:

感谢 Ed 的快速评论!我无法让它正常运行,这是我保存的文件:#!/usr/bin/gawk #####save 并运行为 ./test.sh gawk -F'\t' ' !看到[$1]++ 键[++numKeys] = $1 a[$1][ARGIND] = $2 FS $3 END for (keyNr = 1; keyNr 在什么情况下“运行不正常”?错误的输出,没有输出,核心转储,错误信息 - 什么?此外,您不能在 cmets 中包含格式,因此如果您有代码或其他格式的文本要向我们展示,请更新您的问题。最后 - 运行 gawk --version 并发布结果,您需要 gawk 4.0 或更高版本才能获得真正的 2D 数组和许多其他非常有用的功能。 我的 test.sh 的第 1 行错误(gawk 所在的位置)似乎是一个问题。 $ which gawk 确实返回位置 /usr/bin/gawk。该死,我刚刚编辑/更新,然后才看到您将其修改为 awk,我会再试一次。 我看到你发布了。只需删除第一行 #!/usr/bin/gawk,因为它告诉 shell 在脚本上运行 gawk,但第一行 (gawk -F...) 是 shell 命令,而不是 awk 命令。您要么需要一个 shebang (#!..),后跟一个 awk 脚本的主体,要么没有 shebang,然后是一个调用 awk 但你不能同时拥有的 shell 脚本。我个人通常发现 no shebang 方法更容易使用。【参考方案2】:

这是另一个awk

#!/usr/bin/awk -f
# Set field separator to tab (\t)
BEGIN  FS = "\t" 
# Remove carriage return characters if file is in DOS format (CRLF)
 sub(/\r/, "") 
# Increment indx by 1 (starts at 0) everytime a new file is processed
!f[FILENAME]++  ++indx 
# Add a key ($1) to keys array every time it is first encountered
!a[$1]++  keys[i++] = $1 
# Store the 2nd and 3rd field to b matrix
 b[$1, indx] = $2 FS $3 
# This block runs after all files are processed
END 
    # Traverse the keys in order
    for (i = 0; i in keys; ++i) 
        key = keys[i]
        # Print key
        printf "%s", key
        # Print columns from every file in order
        for (j = 1; j <= indx; ++j) 
            v = b[key, j]
            printf "\t%s", length(v) ? v : "Mtee" FS "Mtee"
        
        # End the line with a newline
        print ""
    

用法:

awk -f script.awk file1 file2 file3

输出:

Names   Title1  Title2  Title3  Title4  Title5  Title6
AAAA    1111    123456  Mtee    Mtee    3333    987654
BBBBB   1111    123456  2222    789456  Mtee    Mtee
CCC     1111    123456  Mtee    Mtee    3333    987654
DDDDD   Mtee    Mtee    2222    789456  Mtee    Mtee
EEEE    Mtee    Mtee    2222    789456  3333    987654

【讨论】:

哇,谢谢!用法对于解决我在运行脚本时的错误特别有帮助。 @BlacquenedRed 这是一个不同的答案。 这很好用,但我正在努力理解它。 'b' 是您制作的矩阵,里面的索引是制表符分隔的 col 2 和 col 3? indx 是一个计数器,它在每个文件上递增,因此它从文件中的 1 开始,然后在文件 2 上变为 2,依此类推.. @BlacquenedRed 这是我最好的参考。我还没有为awk 尝试过任何其他材料。快速参考也是man gawk。我一开始就做/something,以便快速搜索。

以上是关于根据第一列组合几个制表符分隔文件的某些列的主要内容,如果未能解决你的问题,请参考以下文章

读取以第一列为键,其余为值的制表符分隔文件

索引巨大的文本文件

如何优化打开和读取多次相同文件的python脚本?

如何从两个制表符分隔的文件中获取枢轴线?

linux NSS

Excel将一行的内容进行复制时,列与列之间是用制表符“ ”进行分隔的