11

2つのファイルがen.csvありsp.csv、それぞれにコンマで区切られた2つのレコードが含まれているとします。

en.csv

1,dog,red,car
3,cat,white,boat

sp.csv

2,conejo,gris,tren
3,gato,blanco,bote

実行すると

join -t, -a 1 -a 2 -e MISSING en.csv sp.csv

私が得る出力は次のとおりです。

1,dog,red,car
2,conejo,gris,tren
3,cat,white,boat,gato,blanco,bote

欠落しているすべてのフィールドが折りたたまれていることに注意してください。「適切な」完全な外部結合を取得するには、形式を指定する必要があります。したがって

join -t, -a 1 -a 2 -e MISSING -o 0,1.2,1.3,1.4,2.2,2.3,2.4 en.csv sp.csv

収量

1,dog,red,car,MISSING,MISSING,MISSING
2,MISSING,MISSING,MISSING,conejo,gris,tren
3,cat,white,boat,gato,blanco,bote

完全な外部結合を生成するこの方法の1つの欠点は、ファイナルテーブルの形式を明示的に指定する必要があることです。これは、プログラムアプリケーション(結合されたテーブルのIDが実行時にのみ認識される)では簡単ではない場合があります。

最近のバージョンのGNUjoinは、特別な形式をサポートすることでこの欠点を解消していますauto。したがって、join上記の最後のコマンドのそのようなバージョンでは、はるかに一般的なものに置き換えることができます

join -t, -a 1 -a 2 -e MISSING -o auto en.csv sp.csv

joinオプションをサポートしていないバージョンでこれと同じ効果をどのように達成でき-o autoますか?


背景と詳細

いくつかのCSVフラットファイルを処理するように設計されたUnixシェル(zsh)スクリプトがあり、GNUの「-oauto」オプションを多用することで処理します。joinこのスクリプトを変更して、使用可能なjoinコマンドがオプションをサポートしていない環境で機能するようにする必要があり-o autoます(BSDjoinおよび古いバージョンのGNUの場合と同様join)。

スクリプトでのこのオプションの一般的な使用法は次のとおりです。

_reccut () {
    cols="1,$1"
    shift
    in=$1
    shift
    if (( $# > 0 )); then
        join -t, -a 1 -a 2 -e 'MISSING' -o auto \
          <( cut -d, -f $cols $in | sort -t, -k1 ) \
          <( _reccut "$@" )
    else
        cut -d, -f $cols $in | sort -t, -k1
    fi
}

この例を示して-o auto、明示的な形式に置き換えるのが難しいことを示します。これは、この形式に含めるフィールドが実行時までわからないためです。

上記の関数_reccutは基本的にファイルから列を抽出し、結果のテーブルを最初の列に沿って結合します。実際の動作を確認するため_reccutに、上記のファイルに加えて、ファイルもあると想像してください。

de.csv

2,Kaninchen,Grau,Zug
1,Hund,Rot,Auto

次に、たとえば、の列3 en.csv、の列2と4 sp.csv、およびde.csvの列3を並べて表示するには、次のように実行します。

% _reccut 3 en.csv 2,4 sp.csv 3 de.csv | cut -d, 2-
red,MISSING,MISSING,Rot
MISSING,conejo,tren,Grau
white,gato,bote,MISSING
4

1 に答える 1

2

これは、データに対して機能する場合と機能しない場合があるソリューションです。csv ファイル内のレコードを2行番号で整列することにより、問題にアプローチします。欠落しているレコード/行はフィールドで埋められるため、入力ファイルは次のようにマングルされます。231233123MISSING

en.csv:

1,dog,red,car
2,MISSING,MISSING,MISSING
3,cat,white,boat

de.csv:

1,Hund,Rot,Auto
2,Kaninchen,Grau,Zug
3,MISSING,MISSING,MISSING

sp.csv:

1,MISSING,MISSING,MISSING
2,conejo,gris,tren
3,gato,blanco,bote

そこから、目的の列を切り取り、 を使用して並べて印刷するのは簡単pasteです。

これを実現するために、まず入力ファイルをソートしてから、愚かawk魔法を適用します。

  • 予想される行番号にレコードが表示された場合は、それを印刷します
  • それ以外の場合は、配置が再び正しくなるまで、期待される数 (これは、ファイルの最初の行のフィールド数に基づいています) を含む行をできるだけ多く印刷しますjoin -o autoMISSING
  • すべての入力ファイルが同じ数のレコードに送られるわけではないため、すべての前に最大数が検索されます。次に、MISSING最大数に達するまで、フィールドを含む行がさらに出力されます。

コード

reccut.sh:

#!/bin/bash

get_max_recnum()
{
    awk -F, '{ if ($1 > max) { max = $1 } } END { print max }' "$@"
}

align_by_recnum()
{
    sort -t, -k1 "$1" \
        | awk -F, -v MAXREC="$2" '
            NR==1 { for(x = 1; x < NF; x++) missing = missing ",MISSING" }
            {
                i = NR
                if (NR < $1)
                {
                    while (i < $1)
                    {
                        print i++ missing
                    }
                    NR+=i
                }
            }1
            END { for(i++; i <= MAXREC; i++) { print i missing } }
            '
}

_reccut()
{
    local infiles=()
    local args=( $@ )
    for arg; do
        infiles+=( "$2" )
        shift 2
    done
    MAXREC="$(get_max_recnum "${infiles[@]}")" __reccut "${args[@]}"
}

__reccut()
{
    local cols="$1"
    local infile="$2"
    shift 2

    if (( $# > 0 )); then
        paste -d, \
            <(align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}) \
            <(__reccut "$@")
    else
        align_by_recnum "${infile}" "${MAXREC}" | cut -d, -f ${cols}
    fi
}

_reccut "$@"

走る

$ ./reccut.sh 3 en.csv 2,4 sp.csv 3 de.csv
red,MISSING,MISSING,Rot
MISSING,conejo,tren,Grau
white,gato,bote,MISSING
于 2013-04-28T12:02:15.070 に答える