ヘッダーに特定の部分文字列を含むcsvファイルから列を抽出するawkコマンド

ヘッダーに特定の部分文字列を含むcsvファイルから列を抽出するawkコマンド

次の形式の巨大なCSVファイルがあります。

aaa1, "aaa2, aa214", aa21, "aa, a14", aa211, aa44, aaa445 
data, data, data, data, data, data, data,
........................................
........................................

ヘッダーに特定の文字列(たとえば)を含む列を抽出したいと思いますa2。上記の例では、列aaa2などが含まれていますaa21

私が試したawkコマンドは次のとおりです。

awk --csv 'NR==1 {for (i=1; i<=NF; i++) if ($i ~ /a2/) print $i}' file.csv 

ただし、これは一致するヘッダーのみを返し、その下の列は返しません。正しい方向を教えてください。私はLinuxシステムを使用しています。

答え1

mlrこの偽のCSV形式は実際にサポートされており、正規表現に基づいてフィールドを切り取ることができます。

$ mlr --csv --csv-trim-leading-space --allow-ragged-csv-input cut -rf a2 your-file.csv
"aaa2, aa214",aa21,aa211
data,data,data

ただし、これはメモリに収まらないCSVには拡張されません。--allow-ragged-csv-input例と行ごとのフィールド数が異なるCSVを処理するには、いずれの場合でもファイル全体を読み取って列数を把握する必要があります(ヘッダーのない列には数値ヘッダーが自動的に割り当てられます)。

答え2

GNU awkを使用しFPAT、フィールドに改行文字が含まれていないとします。

awk -v FPAT='[^,]*|\\s*("([^"]|"")*")\\s*' -v OFS=',' '
    NR==1 {
        for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
            if ( $inFldNr ~ /a2/ ) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
            inFldNr = out2in[outFldNr]
            printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file.csv
 "aaa2, aa214", aa21, aa211
 data, data, data

--csv入力ファイルが有効なCSVではないため(GNU awkも必要です)、使用しませんでした(,sと最初の"sの間にスペースがあり、,2行目の末尾に続くヘッダーよりも多くのデータ列があります)。 CSVパーサーはこれを処理すると予想してはいけません。また、この問題を解決しても、--csv各列ヘッダーの周りの引用符が削除されるため、これを維持したいようです。すべてのフィールドが二重引用符で囲まれていない場合は、少し問題があります。フィールドに改行文字を含めることができ、とにかくフィールドの周りの引用符を削除する場合は、--csv正しい設定を使用するよりも使用する方がはるかに優れています。FPAT

本当に試してみたい場合は、--csv次の(テストされていません)が役に立ちます。

awk --csv -v OFS=',' '
    NR==1 {
        for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
            if ( $inFldNr ~ /a2/ ) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
            inFldNr = out2in[outFldNr]
            outVal = $inFldNr
            if ( outVal ~ ("[" OFS ORS "\"]") ) {
                gsub(/"/,"\"\"",outVal)
                outVal = "\"" outVal "\""
            }
            printf "%s%s", outVal, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file.csv

"ただし、ループに s を追加する場合、先頭/末尾の空白 (存在する場合) が最初に引用符の内側または外側にあったかどうかを知る簡単な方法がないため、フィールド全体を引用しました。

バラよりawkを使用してcsvを効率的に解析する最も強力な方法は何ですかCSVを解析するためにawkを使用する方法に関する追加情報。

答え3

使用幸せ(以前のPerl_6)

...RakuのText::CSVモジュールを使用して:

~$ raku -MText::CSV -e 'csv(in => csv(in => $*IN, sep => ", "), out => $*OUT);'  <  file

上記のコマンドはCSVファイル(すべての列)をメモリに読み込みます。ファイルは std-in で受信され、すべての列は$*INstd-out で出力されます。$*OUTカスタム", "フィールド区切り記号を参照してください。

特定の列をフィルタリングするには(他のすべての列を削除する)、見つかった列の数値インデックスを返すRakuのgrepキーパラメータを使用します。:k

~$ raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                        my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                        for @aoa.map( *.[@col-nbrs]) {
                            .map(q["] ~ * ~ q["]).join("\t").put
                        };'  <  file

入力例:

aaa1_a, "aaa2, aa214_b", aa21_c, "aa, a14_d", aa211_e, aa44_f, aaa445_g
data1_a, data1_b, data1_c, data1_d, data1_e, data1_f, data1_g,
data2_a, data2_b, data2_c, data2_d, data2_e, data2_f, data2_g,

出力例(上記TSV):

"aaa2, aa214_b" "aa21_c"    "aa211_e"
"data1_b"   "data1_c"   "data1_e"
"data2_b"   "data2_c"   "data2_e"

上記には、「a2」に一致するすべての列名が含まれています。出力を読みやすくするために、各フィールドのテキストを引用符で囲み、タブjoinで編集します\t。引用符がないと、コードははるかに簡単になります。次の列が接続されて\t\t出力されます。

~$ raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                        my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                        for @aoa.map( *.[@col-nbrs]) {
                            $_.join("\t\t").put
                        };'  <  file
aaa2, aa214_b       aa21_c      aa211_e
data1_b     data1_c     data1_e
data2_b     data2_c     data2_e

最後に、デフォルトでは、1.列のカンマ、2.スペースを含む二重引用符フィールドなどのText::CSV出力機能を利用できます。join,

% raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                       my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                       my  @filtered; for @aoa.map( *.[@col-nbrs] ) {
                           @filtered.push($_); 
                       };  csv(in => @filtered, out => $*OUT);'  <  file
"aaa2, aa214_b",aa21_c,aa211_e
data1_b,data1_c,data1_e
data2_b,data2_c,data2_e

https://raku.land/zef:Tux/Text::CSV
https://github.com/Tux/CSV/blob/master/doc/Text-CSV.md
https://docs.raku.org
https://raku.org

答え4

もう一つの非常に便利なツールはアヒルデータベース。走る

duckdb --csv -c "SELECT COLUMNS('.*a2.*') from read_csv_auto('input.csv',HEADER = true)" >output.csv

あなたは得る

AAA2、AAA214 AA21 AA211
データ データ データ
データ データ データ

関連情報