次の形式でタブ区切りの複数の2列ファイルをマージしたいと思います。
a
A 5
C 4
D 2
b
A 2
B 5
C 3
c
B 4
C 4
D 2
次の形式で単一のテーブルに配置します。
a b c
A 5 2 0
B 0 5 4
C 4 3 4
D 2 0 2
答え1
join
動作するツールですが、オプションは少し迷惑です。
join -t $'\t' -a1 -a2 -o 0,1.2,2.2 file1 file2 |
join -t $'\t' -a1 -a2 -o 0,1.2,1.3,2.2 - file3 |
sed 's/\t\(\t\|$\)/\t0\1/g'
a b c
A 5 2 0
B 0 5 4
C 4 3 4
D 2 0 2
そのオプションを最初に使用しましたが、-e
これによりヘッダー行に問題が発生しました。
答え2
本質的に値の2D配列を作成したいと思います。各行の最初の列は次のとおりです。鍵、各入力ファイルの各行のタブ区切りの最初のフィールドから取得します。以下の各列は別々の入力ファイルに対応しています。
awk 'BEGIN {
RS = "(\r\n|\n\r|\r|\n)"
FS = " *\t *"
SUBSEP = ":"
}
FNR==1 {
++file
}
NF>=2 {
if ($1 in keynum)
key = keynum[$1]
else {
key = ++keys
keynum[$1] = key
keystr[key] = $1
}
value[key,file] = $2
}
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
printf "\t%s", value[key,file]
printf "\n"
}
}' INPUT1 INPUT2 ... INPUTN
このBEGIN
規則は、各行が別々のレコードになるように、レコード区切り文字をすべての種類の改行文字に設定します。また、周囲のスペースを含むフィールド区切り文字をタブ文字に設定します。
awkでは、すべての配列が関連付けられており、本質的に1次元です。多次元配列は、インデックスを連結することでSUBSEP
サポートされます(中間に1つ含まれます)。ここでは、:
使用されるインデックスが正の整数であるため、区切り文字として使用します。 (必要に応じて、tabなどの他の多くの文字を使用できます\t
。)
このFNR==1
ルールは、各入力ファイルの最初の行で実行されます。最初の入力ファイル、2番目の入力ファイルなどに適用されるfile
変数を追加します。1
2
NF>=2
このルールは、複数のフィールドを持つすべてのレコードに対してトリガーされます。この場合、1行に1つのタブ文字があることを意味します。最初のフィールドは鍵、2番目のフィールド値。
この変数は、一意のkey
キー文字列を参照する正の整数です。 (1は最初のユニークです鍵すべての入力ファイルで2から2番目のファイルまで表示されます。 )
連想配列は、keynum
キー文字列をキー番号(key
、正の整数)にマップします。これはkeystr
、キー番号をキー文字列にマッピングする逆マッピングです。
NF>=2
ルールの最初のフィールドが既知のキーである場合は、その番号を見つけます。それ以外の場合、最初のフィールドは新しい一意のキー文字列として追加されます。その後、2番目のフィールドが配列value
に保存されます。
END
このルールは、すべての入力ファイルが処理された後にトリガーされます。配列value
は、私たちが望むフィールドを含む論理2D配列です。
外部ループは、key
最初に示された順序で表示されたすべての一意のキーを繰り返します。外部ループを繰り返すたびに、出力行が生成されます。
内部ループは、file
各入力ファイルをリストされた順序で繰り返します。各反復は、現在の出力行に追加の列を生成します。各出力行には、指定された入力ファイルの数よりも正確に1つ以上の列が含まれています。 (入力ファイルが指定されていない場合、awkは標準入力から読み取り、それを入力ファイルとして扱います。)
これは確かにこれを達成する最も簡単な方法ではありませんが、強力なのでこれが好きです(Unix、Linux、古いMac、新しいMac、Windowsなど、基本的にASCII準拠の文字セットを使用する任意の場所で生成された入力ファイルを許可します。入力ファイルに知られているすべてのキーのサブセットだけが混乱する可能性があり、同様の状況を理解し、維持し、適応するのは比較的簡単です。
上記をスクリプトとして実行するには、次のように保存しますpaste.awk
。
#!/usr/bin/awk -f
BEGIN {
RS = "(\r\n|\n\r|\r|\n)"
FS = " *\t *"
SUBSEP = ":"
}
FNR==1 {
++file
}
NF>=2 {
if ($1 in keynum)
key = keynum[$1]
else {
key = ++keys
keynum[$1] = key
keystr[key] = $1
}
printf "key = %s, file = %s, value = %s\n", key, file, $2 >/dev/stderr
value[key,file] = $2
}
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
printf "\t%s", value[key,file]
printf "\n"
}
}
input1
含めた場合
a
A 5
C 4
D 2
そしてinput2
包含
b
A 2
B 5
C 3
そしてinput3
包含
c
B 4
C 4
D 2
ただし、各行の2番目の文字はTab;
printf ' \ta\nA\t5\nC\t4\nD\t2\n' > input1
printf ' \tb\nA\t2\nB\t5\nC\t3\n' > input2
printf ' \tc\nB\t4\nC\t4\nD\t2\n' > input3
または上記のテキストをファイルにコピーして貼り付けた場合は、実行して修正してからsed -e 's|^\(.\) *|\1\t|' -i input1 input2 input3
実行してください。
paste.awk input1 input2 input3
出力
a b c
A 5 2
C 4 3 4
D 2 2
B 5 4
上記の連続した空白が実際にはsであるだけですtab。ご覧のとおり、ウェブサイトのソフトウェアはタブを空白に変換します。
追加するように変更されました:欠落している項目に事前定義されたいくつかの値を使用するには、ルールを次のように変更しますEND
。
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
if ((key SUBSEP file) in value)
printf "\t%s", value[key,file]
else
printf "\t%s", blank
printf "\n"
}
}
blank
希望の値を反映するように変数を設定します。 (コマンドラインを使用して設定するか、awkコードを変更して、ルールのどこかまたはルールの先頭に値を設定できます./paste.awk -v blank=0 input1 input2 input3
。)BEGIN
END
答え3
これはGNU awkバージョンです。まず、空の値をゼロで埋めることができるように、すべてのキー値を見つけます。
keys=$(cut -d $'\t' -f1 file{1,2,3} | sort -u | paste -sd,)
gawk -F'\t' -v keys="$keys" '
BEGIN {
n = split(keys,k,/,/)
for (i=1; i<=n; i++) values[k[i]] = k[i]
}
{v[$1] = $2}
ENDFILE {
for (key in values)
values[key] = values[key] FS (v[key] ? v[key] : 0)
delete v
}
END {
for (key in values) print values[key]
}
' file1 file2 file3 | sort -t $'\t' -k 1,1