複数のファイルからテーブルを作成する

複数のファイルからテーブルを作成する

次の形式でタブ区切りの複数の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変数を追加します。12

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。)BEGINEND

答え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

関連情報