web-dev-qa-db-ja.com

awkを使用してフィールド内のコンマを無視してcsvを解析する

各行が特定の建物の部屋を定義するcsvファイルがあります。部屋に加えて、各列には床フィールドがあります。抽出したいのは、すべての建物のすべてのフロアです。

私のファイルは次のようになります...

"u_floor","u_room","name"
0,"00BDF","AIRPORT TEST            "
0,0,"BRICKER HALL, JOHN W    "
0,3,"BRICKER HALL, JOHN W    "
0,5,"BRICKER HALL, JOHN W    "
0,6,"BRICKER HALL, JOHN W    "
0,7,"BRICKER HALL, JOHN W    "
0,8,"BRICKER HALL, JOHN W    "
0,9,"BRICKER HALL, JOHN W    "
0,19,"BRICKER HALL, JOHN W    "
0,20,"BRICKER HALL, JOHN W    "
0,21,"BRICKER HALL, JOHN W    "
0,25,"BRICKER HALL, JOHN W    "
0,27,"BRICKER HALL, JOHN W    "
0,29,"BRICKER HALL, JOHN W    "
0,35,"BRICKER HALL, JOHN W    "
0,45,"BRICKER HALL, JOHN W    "
0,59,"BRICKER HALL, JOHN W    "
0,60,"BRICKER HALL, JOHN W    "
0,61,"BRICKER HALL, JOHN W    "
0,63,"BRICKER HALL, JOHN W    "
0,"0006M","BRICKER HALL, JOHN W    "
0,"0008A","BRICKER HALL, JOHN W    "
0,"0008B","BRICKER HALL, JOHN W    "
0,"0008C","BRICKER HALL, JOHN W    "
0,"0008D","BRICKER HALL, JOHN W    "
0,"0008E","BRICKER HALL, JOHN W    "
0,"0008F","BRICKER HALL, JOHN W    "
0,"0008G","BRICKER HALL, JOHN W    "
0,"0008H","BRICKER HALL, JOHN W    "

私が欲しいのは、すべての建物のすべてのフロアです。

Cat、awk、sort、uniqを使用してこのリストを取得していますが、「BRICKER HALL、JOHN W」などの建物名フィールドの「、」に問題があり、csv生成全体が破棄されています。

cat Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq > Floors.csv 

Awkにコンマを使用してフィールドの「」の間にあるコンマを無視させるにはどうすればよいですか?あるいは、誰かがより良い解決策を持っていますか?

Awk csvパーサーを提案する回答に基づいて、私は解決策を得ることができました:

cat Buildings.csv | awk -f csv.awk | awk -F" -> 2|"  '{print $2}' | awk -F"|" '{print $2","$3}' | sort | uniq > floors.csv 

そこで、 csv awk プログラムを使用し、そこから "-> 2 |"を使用したいこれは、csv awkプログラムに基づいたフォーマットです。そこのprint $ 2は、csvで解析された内容のみを出力します。これは、プログラムが元の行の後に "->#"を出力するためです。#はcsvから解析されたカウントです。 (つまり、列。)そこから、このawk csvの結果を「|」で分割できます。 whcihは、コンマを置き換えるものです。次に、ソート、uniq、およびファイルへのパイプアウトを行います!

助けてくれてありがとう。

34
Chris

csv.awkから得られる追加の出力は、デモコードからのものです。スクリプト内の関数を使用して解析を実行し、必要に応じて出力することを意図しています。

csv.awkの最後は、関数の1つを示す{ ... }ループです。 -> 2|を出力しているのはそのコードです。

その代わりに、解析関数を呼び出してprint csv[1], csv[2]を実行するだけです。

コードのその部分は次のようになります。

{
    num_fields = parse_csv($0, csv, ",", "\"", "\"", "\\n", 1);
    if (num_fields < 0) {
        printf "ERROR: %s (%d) -> %s\n", csverr, num_fields, $0;
    } else {
#        printf "%s -> ", $0;
#        printf "%s", num_fields;
#        for (i = 0;i < num_fields;i++) {
#            printf "|%s", csv[i];
#        }
#        printf "|\n";
        print csv[1], csv[2]
    }
}

your_script(たとえば)として保存します。

chmod +x your_scriptを実行します。

catは不要です。また、sort -uの代わりにsort | uniqを実行できます。

コマンドは次のようになります。

./yourscript Buildings.csv | sort -u > floors.csv
10
_gawk -vFPAT='[^,]*|"[^"]*"' '{print $1 "," $3}' | sort | uniq
_

これはすごいGNU Awk 4の拡張機能で、フィールドセパレータパターンの代わりにフィールドパターンを定義します。CSVについては不思議です。( docs

ETA(mitchusに感謝):周囲の引用符を削除するには、gsub("^\"|\"$","",$3);そのように処理するためのフィールドが_$3_だけではない場合、それらをループします。
この単純なアプローチは、不正な形式の入力や引用符の間のいくつかの可能な特殊文字を許容しないことに注意してください。

39
hemflit

私の回避策は、次を使用してcsvからコンマを取り除くことです:

decommaize () {
  cat $1 | sed 's/"[^"]*"/"((&))"/g' | sed 's/\(\"((\"\)\([^",]*\)\(,\)\([^",]*\)\(\"))\"\)/"\2\4"/g' | sed 's/"(("/"/g' | sed 's/"))"/"/g' > $2
}

つまり、最初に開き引用符を「((」で、閉じ引用符を「))」で置き換え、次に「(( "whatever、whatever"))」を「whateverwhatever」で置き換えてから、「((」の残りのすべてのインスタンスを変更します。 "))" 戻る "。

6
Vitalik Buterin

このawkbased csv paserを試すことができます:

http://lorance.freeshell.org/csv/

4
Marcus Whybrow

私が書いたcsvquoteというスクリプトを使用して、引用されたフィールド内のコンマをawkに無視させることができます。コマンドは次のようになります。

csvquote Buildings.csv | awk -F, '{print $1","$2}' | sort | uniq | csvquote -u > Floors.csv

これはawkよりカットが少し簡単かもしれません:

csvquote Buildings.csv | cut -d, -f1,2 | sort | uniq | csvquote -u > Floors.csv

ここでcsvquoteコードを見つけることができます: https://github.com/dbro/csvquote

2
D Bro

問題は実際にはCSVフィールド内のコンマとフィールドを区切るコンマを区別することなので、さらに解析しやすくするために、最初の種類のコンマを別のものに置き換えることができます。

0,"00BDF","AIRPORT TEST            "
0,0,"BRICKER HALL<comma> JOHN W    "

このgawkスクリプト(replace-comma.awk)は次のことを行います。

BEGIN { RS = "(.)" } 
RT == "\x022" { inside++; } 
{ if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }

これは、実際のレコード区切りをRTという変数にキャプチャするgawk機能を使用します。すべての文字をレコードに分割し、レコードを読みながら、引用符内にあるコンマ(\x022<comma>

[〜#〜] fpat [〜#〜] ソリューションは、エスケープされた引用符と引用符内のコンマの両方がある特別なケースで失敗しますが、このソリューションはすべてのケースで機能します。

§ echo '"Adams, John ""Big Foot""",1' | gawk -vFPAT='[^,]*|"[^"]*"' '{ print $1 }'
"Adams, John "
§ echo '"Adams, John ""Big Foot""",1' | gawk -f replace-comma.awk | gawk -F, '{ print $1; }'
"Adams<comma> John ""Big Foot""",1

簡単なコピー&ペーストのためのワンライナーとして:

gawk 'BEGIN { RS = "(.)" } RT == "\x022" { inside++; } { if (inside % 2 && RT == ",") printf("<comma>"); else printf(RT); }'
0
Raghu Dodda

Perlの_Text::CSV_XS_などの本格的なCSVパーサーは、そのような奇妙さを処理するために特別に設計されています。

Perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new()} if($csv->parse($_)){ @f=$csv->fields(); print "$f[0],$f[1]" }' file

入力行は配列_@f_に分割されます
フィールド1は_$f[0]_です。これは、Perlが0でインデックス付けを開始するためです

出力:

_u_floor,u_room
0,00BDF
0,0
0,3
0,5
0,6
0,7
0,8
0,9
0,19
0,20
0,21
0,25
0,27
0,29
0,35
0,45
0,59
0,60
0,61
0,63
0,0006M
0,0008A
0,0008B
0,0008C
0,0008D
0,0008E
0,0008F
0,0008G
0,0008H
_

私はここで私の答えの中で_Text::CSV_XS_の詳細な説明を提供しました: gawkを使用してcsvファイルを解析する

0
Chris Koknat