根据列标题合并文件中的列

根据列标题合并文件中的列

我想合并文件中共享相同列标题的不同列。该文件如下所示,可以用制表符分隔或其他形式:

AaBbN    CcDdEeN    FfN     AaBbN    FfN
1        5          4   
3        1          2
2        NA         1
                            1        3
                            3        2
                            NA       4

因此字段中有数字或字符串“NA”。结果如下:

AaBbN    CcDdEeN    FfN
1        5          4
3        1          2
2        NA         1
1                   3
3                   2
NA                  4

有很多列是无序的,因此需要自动读取标题标题,而不是手动指定每一个。还有很多空字段。我一直在研究pastejoin命令来完成这项工作。特别是join似乎可以满足我的需要,除了它适用于单独的文件,而我的列在同一个文件中。

所以我尝试将这些列分成单独的文件,然后将它们与join.我使用了awk从这里派生的命令:

https://www.unix.com/unix-for-dummies-questions-and-answers/204303-splitting-up-text-file-into-multiple-files-columns.html

awk ' { for( i = 1; i <= NF; i++ ) printf( "%s\n", $(i) ) >i ".txt"; } ' file.txt

这给了我单独的列,但在这里我遇到了第一个问题。标题和数据之间有空白的所有列均未正确处理。相反,这些文件中仅存在列标题。

我的第二个问题是join:当我尝试再次合并文件时,我收到错误,因为输入未排序,这当然是不可能做到的。任何排序都会破坏我正在维护的关系。

所以我现在陷入了死胡同。有没有更方便的方法直接在文件中合并列?


编辑:

AdminBees 解决方案最接近解决问题,但结果不太正确。以下是将 awk 脚本应用于上面示例的结果。我确保所有条目均以制表符分隔sed -i "s/[[:space:]]/ /g"(使用 CTRL+V 和 TAB 插入制表符)。

AaBbN   CcDdEeN FfN     FfN
1   5   4   

3   1   2

2   NA  1

            1
            3
            NA

答案1

如果您的输入是制表符分隔的:

awk -F"\t" '
NR == 1 {for (i=1; i<=NF; i++)  COL[i] = $i
        }
        {for (i=1; i<=NF; i++) OUT[NR, COL[i]] = $i
        }
END     {for (n=1; n<=NR; n++)  {split ("", DUP)
                                 for (i=1; i<=NF; i++)  if (!DUP[COL[i]]++) printf "%s" FS, OUT[n,COL[i]]
                                 printf RS
                                }
        }
' file
A   B   C   
1   5   4   
3   1   2   
2   2   1   
1       3   
3       2   
1       4   

它保存列标题以供稍后用作部分索引,然后将每行的值收集到按行号和标题部分索引索引的数组中。在该END部分中,它按原始序列打印该数组,并处理重复的列标题。

对于更复杂的文件结构,重复处理可能会成为一项主要工作。

答案2

用于制表符分隔的输入。

将标题和相应的列号读取到它们出现在输入文件中的数组中;然后将每列上的输入文件拆分为具有相同 headerName 的相同文件名 headerName.txt 。毕竟将它们粘贴在一起并且column用于美化输出的命令。

awk -F'\t' '
    ## find all the column number(s) when same header found and store in `h` array
    ## key is the column number and value is header name. for an example:
    ## for the header value 'A', keys will be columns 1 &4
    NR==1{ while (++i<=NF) h[i]=$i; next; }

         { for (i=1; i<=NF; i++) {

    ## save the field content to a file which its key column matches with the column 
    ## number of the current field. for an example:
    ## for the first field in column 1; the column number is 1, and so 1 is the key  
    ## column for header value A, so this will be written to "A.txt" filename
    ## only if it was not empty.
               if ($i!=""){ print $i> h[i]".txt" };
         }; }

    ## at the end paste those all files and beautify output with `column` command.
    ## number of .txt files above is limit to the number of uniq headers in your input. 
END{ system("paste *.txt |column \011 -tn") }' infile

无注释命令:

awk -F'\t' '
    NR==1{ while (++i<=NF) h[i]=$i; next; }
         { for (i=1; i<=NF; i++) {
               if ($i!=""){ print $i> h[i]".txt" };
         }; }
END{ system("paste *.txt |column \011 -tn") }' infile

答案3

一种稍微不同的方法,不需要“缓冲”整个文件:

AWK脚本colmerge.awk

FNR==1{
    for (i=1; i<=NF; i++)
    {
    hdr[i]=$i;
    if (map[$i]==0) {map[$i]=i; uniq_hdr[++u]=$i; printf("%s",$i);}
    if (i==NF) printf("%s",ORS); else printf("%s",OFS);
    }
}

FNR>1{
    delete linemap;
    for (i=1; i<=NF; i++) if ($i!="") linemap[hdr[i]]=$i;
    for (i=1; i<=u; i++)
    {
    printf("%s",linemap[uniq_hdr[i]]);
    if (i==u) printf("%s",ORS); else printf("%s",OFS);
    }
}

用于

awk -F'\t' -v OFS='\t' -f colmerge.awk file

这将收集所有标头并识别“唯一”标头及其在第 1 行上的第一次出现,并为每个连续行在标头和非空值之间创建一个映射,然后按“唯一”标头的顺序打印出来如处理第一行时所识别的那样。

然而,只有当您的输入文件是制表符分隔时,这才有效,因为这是可靠地检测“空”字段的唯一方法。

另请注意,并非所有实现都支持delete整个数组的语句(但是应该适用于、和)。linemapawkgawkmawknawk

相关内容