14

gawk を使用して CSV ファイルを解析するにはどうすればよいですか? FS=","カンマを含む引用符で囲まれたフィールドは複数のフィールドとして扱われるため、単純に設定するだけでは不十分です。

FS=","動作しない使用例:

ファイルの内容:

one,two,"three, four",five
"six, seven",eight,"nine"

gawk スクリプト:

BEGIN { FS="," }
{
  for (i=1; i<=NF; i++) printf "field #%d: %s\n", i, $(i)
  printf "---------------------------\n"
}

悪い出力:

field #1: one
field #2: two
field #3: "three
field #4:  four"
field #5: five
---------------------------
field #1: "six
field #2:  seven"
field #3: eight
field #4: "nine"
---------------------------

望ましい出力:

field #1: one
field #2: two
field #3: "three, four"
field #4: five
---------------------------
field #1: "six, seven"
field #2: eight
field #3: "nine"
---------------------------
4

9 に答える 9

15

gawk バージョン 4 マニュアルには、使用するように書かれていますFPAT = "([^,]*)|(\"[^\"]+\")"

が定義されている場合、区切り記号ではなくコンテンツでフィールドをFPAT無効にして指定します。FS

于 2012-01-13T02:05:36.403 に答える
13

簡単な答えは、「CSV にぎこちないデータが含まれている場合、CSV の解析に gawk を使用しない」です。「awkward」は、CSV フィールド データ内のコンマなどを意味します。

次の質問は、「他にどのような処理を行う予定ですか」です。これは、使用する代替手段に影響を与えるためです。

私はおそらく Perl と Text::CSV または Text::CSV_XS モジュールを使用して、データを読み取って処理します。awkPerl は元々 andsedキラーとして部分的に書かれたことを思い出してください。そのため、 a2pands2pプログラムは、Perl に(それぞれ) スクリプトawkを変換する Perl と共に配布されています。sed

于 2008-11-24T17:17:00.570 に答える
4

可能であれば、使用する方言と必要なフォーマットパラメータに特に注意して、 Python csvモジュールを使用して、所有しているCSVファイルを解析します。

于 2008-11-24T17:24:01.413 に答える
4

csvquote と呼ばれる単純なラッパー関数を使用して、入力をサニタイズし、awk が処理を完了した後に復元できます。最初と最後にデータをパイプすると、すべてがうまくいくはずです。

前:

gawk -f mypgoram.awk input.csv

後:

csvquote input.csv | gawk -f mypgoram.awk | csvquote -u

コードとドキュメントについては、 https://github.com/dbro/csvquoteを参照してください。

于 2013-05-04T23:35:57.590 に答える
2

csv2delim.awk

# csv2delim.awk converts comma delimited files with optional quotes to delim separated file
#     delim can be any character, defaults to tab
# assumes no repl characters in text, any delim in line converts to repl
#     repl can be any character, defaults to ~
# changes two consecutive quotes within quotes to '

# usage: gawk -f csv2delim.awk [-v delim=d] [-v repl=`"] input-file > output-file
#       -v delim    delimiter, defaults to tab
#       -v repl     replacement char, defaults to ~

# e.g. gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > test.txt

# abe 2-28-7
# abe 8-8-8 1.0 fixed empty fields, added replacement option
# abe 8-27-8 1.1 used split
# abe 8-27-8 1.2 inline rpl and "" = '
# abe 8-27-8 1.3 revert to 1.0 as it is much faster, split most of the time
# abe 8-29-8 1.4 better message if delim present

BEGIN {
    if (delim == "") delim = "\t"
    if (repl == "") repl = "~"
    print "csv2delim.awk v.m 1.4 run at " strftime() > "/dev/stderr" ###########################################
}

{
    #if ($0 ~ repl) {
    #   print "Replacement character " repl " is on line " FNR ":" lineIn ";" > "/dev/stderr"
    #}
    if ($0 ~ delim) {
        print "Temp delimiter character " delim " is on line " FNR ":" lineIn ";" > "/dev/stderr"
        print "    replaced by " repl > "/dev/stderr"
    }
    gsub(delim, repl)

    $0 = gensub(/([^,])\"\"/, "\\1'", "g")
#   $0 = gensub(/\"\"([^,])/, "'\\1", "g")  # not needed above covers all cases

    out = ""
    #for (i = 1;  i <= length($0);  i++)
    n = length($0)
    for (i = 1;  i <= n;  i++)
        if ((ch = substr($0, i, 1)) == "\"")
            inString = (inString) ? 0 : 1 # toggle inString
        else
            out = out ((ch == "," && ! inString) ? delim : ch)
    print out
}

END {
    print NR " records processed from " FILENAME " at " strftime() > "/dev/stderr"
}

test.csv

"first","second","third"
"fir,st","second","third"
"first","sec""ond","third"
" first ",sec   ond,"third"
"first" , "second","th  ird"
"first","sec;ond","third"
"first","second","th;ird"
1,2,3
,2,3
1,2,
,2,
1,,2
1,"2",3
"1",2,"3"
"1",,"3"
1,"",3
"","",""
"","""aiyn","oh"""
"""","""",""""
11,2~2,3

test.bat

rem test csv2delim
rem default is: -v delim={tab} -v repl=~
gawk                      -f csv2delim.awk test.csv > test.txt
gawk -v delim=;           -f csv2delim.awk test.csv > testd.txt
gawk -v delim=; -v repl=` -f csv2delim.awk test.csv > testdr.txt
gawk            -v repl=` -f csv2delim.awk test.csv > testr.txt
于 2008-11-27T02:39:08.250 に答える
1
{
  ColumnCount = 0
  $0 = $0 ","                           # Assures all fields end with comma
  while($0)                             # Get fields by pattern, not by delimiter
  {
    match($0, / *"[^"]*" *,|[^,]*,/)    # Find a field with its delimiter suffix
    Field = substr($0, RSTART, RLENGTH) # Get the located field with its delimiter
    gsub(/^ *"?|"? *,$/, "", Field)     # Strip delimiter text: comma/space/quote
    Column[++ColumnCount] = Field       # Save field without delimiter in an array
    $0 = substr($0, RLENGTH + 1)        # Remove processed text from the raw data
  }
}

これに続くパターンは、Column[] のフィールドにアクセスできます。ColumnCount は、検出された Column[] 内の要素の数を示します。すべての行に同じ数の列が含まれているわけではない場合、短い行を処理するときに、Column[] には、Column[ColumnCount] の後に余分なデータが含まれます。

この実装は遅いですが、以前の回答で言及されている gawk >= 4.0.0 にあるFPAT/機能をエミュレートしているようです。patsplit()

参照

于 2012-03-19T17:05:54.670 に答える
1

これが正しい方法であるかどうかは正確にはわかりません。私はむしろ、すべての値が引用されているか、引用されていないcsvファイルで作業したいと思います。ところで、awk では正規表現をフィールド セパレータにすることができます。それが有用かどうかを確認してください。

于 2008-11-24T15:15:50.360 に答える
0

これが私が思いついたものです。コメントおよび/またはより良い解決策をいただければ幸いです。

BEGIN { FS="," }
{
  for (i=1; i<=NF; i++) {
    f[++n] = $i
    if (substr(f[n],1,1)=="\"") {
      while (substr(f[n], length(f[n]))!="\"" || substr(f[n], length(f[n])-1, 1)=="\\") {
        f[n] = sprintf("%s,%s", f[n], $(++i))
      }
    }
  }
  for (i=1; i<=n; i++) printf "field #%d: %s\n", i, f[i]
  print "----------------------------------\n"
}

基本的な考え方は、フィールドをループして、引用符で始まり、引用符で終わらないフィールドには、次のフィールドが追加されるというものです。

于 2008-11-24T15:10:41.650 に答える