web-dev-qa-db-ja.com

awkは、引用符で囲まれたフィールド内にコンマを含むCSVファイルを処理できますか?

Awkを使用して、csvファイルの1つの列の合計をカウントしています。データ形式は次のようなものです。

id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

私はこのawkスクリプトを使用して合計をカウントしていました:

awk -F, '{sum+=$3} END {print sum}'

名前フィールドの値の一部にコンマが含まれているため、awkスクリプトが壊れます。私の質問は:awkはこの問題を解決できますか?はいの場合、どうすればそれを行うことができますか?

ありがとうございました。

24
maguschen

GNU awkおよび [〜#〜] fpat [〜#〜] を使用する1つの方法

awk 'BEGIN { FPAT = "([^, ]+)|(\"[^\"]+\")" } { sum+=$3 } END { print sum }' file.txt

結果:

192
22
Steve

私は使っている

`FPAT="([^,]+)|(\"[^\"]+\")" `

gawkでフィールドを定義します。フィールドがnullの場合、これは正しい数のフィールドを認識しないことがわかりました。 「+」はフィールドに少なくとも1文字必要だからです。私はそれを次のように変更しました:

`FPAT="([^,]*)|(\"[^\"]*\")"`

"+""*"に置き換えます。正しく動作します。

また、GNU Awkユーザーガイドにもこの問題があることがわかりました。 https://www.gnu.org/software/gawk/manual/html_node/Splitting-By-Content。 html

4
Frank gu

これは高速で堅牢なソリューションであるため、Text :: CSVを使用してPerlで実行する方がおそらく良いでしょう。

4
Daenyth

私が書いたcsvquoteという小さなスクリプトを使用すると、awkがコンマ(または改行)を含むデータフィールドを操作できるようになります。引用符で囲まれたフィールド内の問題のあるコンマを非印刷文字に置き換えます。必要に応じて、後でこれらのコンマを復元できますが、この場合は、復元する必要はありません。

コマンドは次のとおりです。

csvquote inputfile.csv | awk -F, '{sum+=$3} END {print sum}'

コードについては https://github.com/dbro/csvquote を参照してください

3
D Bro

単純な入力ファイルの場合、引用符の外側にあるすべての実際のFSを他の値に変換する小さな関数を記述して(レコード区切り文字をレコードの一部にすることはできないため、RSを選択しました)、それを次のように使用できます。 FS、例:

$ cat decsv.awk
BEGIN{ fs=FS; FS=RS }

{
   decsv()

   for (i=1;i<=NF;i++) {
       printf "Record %d, Field %d is <%s>\n" ,NR,i,$i
   }
   print ""
}

function decsv(         curr,head,tail)
{
   tail = $0
   while ( match(tail,/"[^"]+"/) ) {
       head = substr(tail, 1, RSTART-1);
       gsub(fs,RS,head)
       curr = curr head substr(tail, RSTART, RLENGTH)
       tail = substr(tail, RSTART + RLENGTH)
   }
   gsub(fs,RS,tail)
   $0 = curr tail
}

$ cat file
id, name, value
1, foo, 17
2, bar, 76
3, "I am the, question", 99

$ awk -F", " -f decsv.awk file
Record 1, Field 1 is <id>
Record 1, Field 2 is <name>
Record 1, Field 3 is <value>

Record 2, Field 1 is <1>
Record 2, Field 2 is <foo>
Record 2, Field 3 is <17>

Record 3, Field 1 is <2>
Record 3, Field 2 is <bar>
Record 3, Field 3 is <76>

Record 4, Field 1 is <3>
Record 4, Field 2 is <"I am the, question">
Record 4, Field 3 is <99>

埋め込まれた改行と引用符内の埋め込まれたエスケープされた引用符を処理する必要がある場合にのみ複雑になりますが、それでもそれほど難しくはなく、すべて以前に行われています...

詳細については、 awkを使用してCSVを効率的に解析するための最も堅牢な方法は何ですか? を参照してください。

3
Ed Morton

'value'列が常に最後の列であることが確実にわかっている場合:

awk -F, '{sum+=$NF} END {print sum}'

NFはフィールドの数を表すため、$ NFが最後の列になります

2
Hai Vu

あなたはいつでもソースから問題に取り組むことができます。 「Iamthe、question」のフィールドと同じように、名前フィールドを引用符で囲みます。これは、そのための回避策のコーディングに時間を費やすよりもはるかに簡単です。

更新(デニスの要求どおり)。簡単な例

$ s='id, "name1,name2", value 1, foo, 17 2, bar, 76 3, "I am the, question", 99'

$ echo $s|awk -F'"' '{ for(i=1;i<=NF;i+=2) print $i}'
id,
, value 1, foo, 17 2, bar, 76 3,
, 99

$ echo $s|awk -F'"' '{ for(i=2;i<=NF;i+=2) print $i}'
name1,name2
I am the, question

ご覧のとおり、区切り文字を二重引用符に設定すると、「引用符」に属するフィールドは常に偶数になります。 OPにはソースデータを変更する余裕がないため、この方法は彼には適していません。

2
ghostdog74

この記事は、これと同じデータフィールドの問題を解決するのに役立ちました。ほとんどのCSVは、スペースまたはコンマを含むフィールドを引用符で囲みます。これは、フィルターで除外しない限り、awkのフィールド数を台無しにします。

ガベージを含むフィールド内のデータが必要な場合、これは適していません。 ghostdog74答えを提供しました。これは、そのフィールドを空にしますが、最終的には合計フィールド数を維持します。これは、データ出力の一貫性を保つための鍵です。このソリューションが新しいラインを導入する方法が気に入らなかった。これは私が使用したこのソリューションのバージョンです。最初の3つのフィールドでは、データにこの問題が発生したことはありません。顧客名を含む4番目のフィールドはよくありましたが、そのデータが必要でした。問題を示している残りのフィールドは、レポート出力では不要だったため、問題なく破棄できました。そこで、最初に4番目のフィールドのガベージを非常に具体的に削除し、引用符の最初の2つのインスタンスを削除しました。次に、何を適用しますghostdog74gaveを使用して、カンマを含む残りのフィールドを空にします。これにより引用符も削除されますが、printfを使用してデータを単一のレコードに保持します。私は85フィールドから始めて、すべての場合で8000行以上の乱雑なデータから85フィールドで終わります。満点!

grep -i $1 $dbfile | sed 's/\, Inc.//;s/, LLC.//;s/, LLC//;s/, Ltd.//;s/\"//;s/\"//' | awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}' > $tmpfile

カンマを含むフィールドを空にするだけでなく、レコードを維持するソリューションは、もちろん次のとおりです。

awk -F'"' '{ for(i=1;i<=NF;i+=2) printf ($i);printf ("\n")}

素晴らしいソリューションを提供してくれたghostdog74に感謝します!

NetsGuy256 /

2
NetsGuy256

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

Perl -MText::CSV_XS -lne 'BEGIN{$csv=Text::CSV_XS->new({allow_whitespace => 1})} if($csv->parse($_)){@f=$csv->fields();$sum+=$f[2]} END{print $sum}' file

入力データにはコンマ区切り文字を囲む空白があるため、_allow_whitespace_が必要です。非常に古いバージョンの_Text::CSV_XS_は、このオプションをサポートしていない可能性があります。

私はここで私の答えの中で_Text::CSV_XS_のより多くの説明を提供しました: gawkを使用してcsvファイルを解析します

1
Chris Koknat

FPATは、引用符内の恐ろしいコンマの問題を処理できるため、洗練されたソリューションですが、先行する区切り文字の数に関係なく、最後の列の数値の列を合計するには、$ NFが適切に機能します。

_awk -F"," '{sum+=$NF} END {print sum}'_

最後から2番目の列にアクセスするには、次を使用します。

awk -F"," '{sum+=$(NF-1)} END {print sum}'

1
galaxywatcher