web-dev-qa-db-ja.com

列ヘッダーに基づいてファイル内の列をマージする

同じ列ヘッダーを共有するファイル内の異なる列をマージしたい。ファイルは次のようになり、タブ区切りまたはその他のファイルにすることができます。

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

順序付けされていない列がたくさんあるため、タイトルヘッダーを1つずつ手動で指定するのではなく、自動的に読み取る必要があります。空のフィールドもたくさんあります。私は仕事をするために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

個別の列が表示されますが、ここで最初の問題が発生しました。ヘッダーとデータの間に空白のある列はすべて正しく処理されませんでした。代わりに、これらのファイルには列ヘッダーのみが含まれていました。

2番目の問題は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
2
And

入力がタブ区切りの場合:

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
RudiC

ファイル全体の「バッファリング」を必要としないわずかに異なるアプローチ:

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全体に対するlinemapステートメントがすべてのawk実装でサポートされているわけではないことにも注意してください(ただし、gawkmawk、およびnawkでは機能するはずです)。

1
AdminBee

タブ区切り入力用。

ヘッダーと対応する列番号を配列に読み込み、入力ファイルに表示されます。次に、各列の入力ファイルを、同じ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
1
αғsнιη