ファイル内で同じ列ヘッダーを共有する異なる列を結合したいです。ファイルは次のようになります。タブ区切りやその他の形式にすることができます。
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 つ 1 つ指定するのではなく、自動的に読み取る必要があります。また、空のフィールドも多数あります。この作業を実行するために、paste
およびjoin
コマンドを調べています。特に、join
必要な処理は実行されているようですが、列は同じファイル内にあるのに、別のファイルで機能する点が異なります。
そこで、列を別々のファイルに分割し、 と結合してみましたjoin
。awk
ここから派生したコマンドを使用しました。
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
答え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.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
配列全体に対するステートメントは、linemap
すべての実装でサポートされているわけではないことに注意してくださいawk
(ただし、、およびでは動作するはずgawk
ですmawk
) nawk
。