質問
gawkを使用してCSVファイルをどのように解析しますか?単に FS ="、"
を設定するだけでは不十分です。引用符で囲まれたフィールド内のコンマは複数のフィールドとして扱われます。
FS ="、quot;
を使用した例:動作しません:
ファイルの内容:
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"
---------------------------
解決
簡単な答えは、「CSVに扱いにくいデータが含まれている場合、gawkを使用してCSVを解析しない」というものです。「awkward」とは、CSVフィールドデータのコンマなどを意味します。
次の質問は、「他にどのような処理を行う予定ですか」です。これは、使用する代替手段に影響するためです。
おそらくPerlとText :: CSVまたはText :: CSV_XSモジュールを使用してデータを読み取り、処理します。 Perlは元々 awk
および sed
のキラーとして書かれていたことを思い出してください-したがって、 a2p
および s2p
プログラムはまだ awk
および sed
スクリプトを(それぞれ)Perlに変換するPerlとともに配布されます。
他のヒント
gawkバージョン4マニュアル FPAT =&quot;([^、] *)|(\&quot; [^ \&quot;] + \&quot;)&quot;
FPAT
が定義されている場合、 FS
が無効になり、セパレータではなくコンテンツでフィールドが指定されます。
csvquoteという単純なラッパー関数を使用して、入力を無害化し、awkによる処理の完了後に復元できます。開始および終了時にデータをパイプ処理すると、すべて正常に動作するはずです:
before:
gawk -f mypgoram.awk input.csv
after:
csvquote input.csv | gawk -f mypgoram.awk | csvquote -u
コードとドキュメントについては、 https://github.com/dbro/csvquote を参照してください。
>許容される場合、Python csv モジュールを使用し、特別な支払いをします使用される方言と必要なフォーマットパラメータ、お持ちのCSVファイルを解析します。
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 ("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
~ repl) {
# print "Replacement character " repl " is on line " FNR ":" lineIn ";" > "/dev/stderr"
#}
if (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
~ delim) {
print "Temp delimiter character " delim " is on line " FNR ":" lineIn ";" > "/dev/stderr"
print " replaced by " repl > "/dev/stderr"
}
gsub(delim, repl)
<*> = gensub(/([^,])\"\"/, "\\1'", "g")
# <*> = gensub(/\"\"([^,])/, "'\\1", "g") # not needed above covers all cases
out = ""
#for (i = 1; i <= length(<*>); i++)
n = length(<*>)
for (i = 1; i <= n; i++)
if ((ch = substr(<*>, 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
<*>test.bat
<*>これが物事を行う正しい方法であるかどうかは正確にはわかりません。私はむしろ、すべての値が引用符で囲まれている、または何もないCSVファイルで作業します。ところで、awkでは、正規表現をフィールド区切り文字にすることができます。それが有用かどうかを確認してください。
{
ColumnCount = 0
<*> = <*> "," # Assures all fields end with comma
while(<*>) # Get fields by pattern, not by delimiter
{
match(<*>, / *"[^"]*" *,|[^,]*,/) # Find a field with its delimiter suffix
Field = substr(<*>, 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
<*> = substr(<*>, RLENGTH + 1) # Remove processed text from the raw data
}
}
これに続くパターンは、Column []のフィールドにアクセスできます。 ColumnCountは、検出されたColumn []の要素の数を示します。すべての行に同じ列数が含まれていない場合、Column []には、短い行を処理するときにColumn [ColumnCount]の後に余分なデータが含まれます。
この実装は遅いですが、前の回答で言及したgawk&gt; = 4.0.0にある FPAT
/ patsplit()
機能をエミュレートしているようです。
これが私が思いついたものです。コメントおよび/またはより良い解決策をいただければ幸いです。
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"
}
基本的な考え方は、フィールドをループすることです。引用符で始まるが引用符で終わらないフィールドには、次のフィールドが追加されます。
PerlにはText :: CSV_XSモジュールがあり、quoted-commaの奇妙さを処理するために特別に設計されています。
または、Text :: CSVモジュールを試してください。
perl -MText :: CSV_XS -ne 'BEGIN {$ csv = Text :: CSV_XS-&gt; new()} if($ csv-&gt; parse($ _)){@ f = $ csv -&gt; fields(); for $ n(0 .. $#f){print&quot; field#$ n:$ f [$ n] \ n&quot;}; print&quot; --- \ n&quot;} 'ファイル.csv
この出力を生成します:
field #0: one
field #1: two
field #2: three, four
field #3: five
---
field #0: six, seven
field #1: eight
field #2: nine
---
これは人間が読めるバージョンです。
parsecsv、chmod + xとして保存し、&quot; parsecsv file.csv&quot;
#!/usr/bin/perl
use warnings;
use strict;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new();
open(my $data, '<', $ARGV[0]) or die "Could not open '$ARGV[0]' $!\n";
while (my $line = <$data>) {
if ($csv->parse($line)) {
my @f = $csv->fields();
for my $n (0..$#f) {
print "field #$n: $f[$n]\n";
}
print "---\n";
}
}
Text :: CSV_XSモジュールがperlのデフォルトバージョンにインストールされていない可能性があるため、マシン上のperlの異なるバージョンを指す必要がある場合があります。
Can't locate Text/CSV_XS.pm in @INC (@INC contains: /home/gnu/lib/perl5/5.6.1/i686-linux /home/gnu/lib/perl5/5.6.1 /home/gnu/lib/perl5/site_perl/5.6.1/i686-linux /home/gnu/lib/perl5/site_perl/5.6.1 /home/gnu/lib/perl5/site_perl .).
BEGIN failed--compilation aborted.
PerlのどのバージョンにもText :: CSV_XSがインストールされていない場合は、次を行う必要があります:
sudo apt-get install cpanminus
sudo cpanm Text :: CSV_XS