数値と文字列の列があるコンマ区切りファイルがあります。文字列の列は引用符で囲まれ、引用符の間にコンマを含めることができます。列をFS =","
で識別するにはどうすればよいですか?
サンプルレコード
"prabhat,kumar",19,2000,"bangalore,India"
[〜#〜] awk [〜#〜] では、
$1 = "prabhat,kumar"
$2 = 19
$3 = "2000"
$4 = "bangalore,india"
FS=","
を設定すると問題が発生します。
入力は:
"prabhat,kumar",19,2000,"bangalore,India","ABC,DEF","GHI",123,"KLM","NOP,QRS"
"prabhat,kumar",19,2000,"bangalore,India","ABC,DEF","GHI",123,"KLM","NOP,QRS"
出力は次のようになります。
"prabhat,kumar"|19|2000|"bangalore,India"|"ABC,DEF"|"GHI"|123|"KLM"|"NOP,QRS"
"prabhat,kumar"|19|2000|"bangalore,India"|"ABC,DEF"|"GHI"|123|"KLM"|"NOP,QRS"
私が試しているコード:
awk -F"," '{for(i=1;i<=NF;i++){if(i%NF==0){ORS="\n"} {if($i ~ /^\"/ || $i ~ /\"$/) {a=a OFS $i;j++;{if(j%2==0){sub(/^\,/,X,a); print a;j=0;a=""}}} else {print $i}}} {ORS="|"}}' ORS="|" OFS=, p.txt
まず、適切なCSVパーサーを使用する必要があります。たとえば、PerlではText::CSV
を使用できます。
cpanm
をインストールします(Perlを使用している場合は、後で私に感謝します)
$ Sudo apt-get install cpanminus
Debianベースのシステムを使用していない場合は、ディストリビューションのパッケージマネージャーを使用してインストールできるはずです。
Text::CSV
モジュールをインストールする
$ Sudo cpanm Text::CSV
ファイルを解析する
$ Perl -MText::CSV -le '
$csv = Text::CSV->new({binary=>1});
while ($row = $csv->getline(STDIN)){
print "1:$row->[0], 2:$row->[1], 3:$row->[2], 4:$row->[3]"}' < file.csv
1:prabhat,kumar, 2:19, 3:2000, 4:bangalore,India
上記のように、最初のフィールドは$row->[0]
、2番目のフィールドは$row->[1]
などです。
それは正しい方法でした。より単純ですが、汚いハックは、引用符で囲まれたコンマを別の文字に置き換えることです。次に、通常はawk
を使用し、最後にそれらを再びコンマに戻します。ここでは###
を使用していますが、自分のフィールドの1つに含まれないことが確実なものであれば何でも使用できます。
$ sed -r 's/("[^",]+),([^",]+")/\1###\2/g' file.csv |
awk -F, '{print $1,$3}' | sed 's/###/,/g'
"prabhat,kumar" 2000
GNU awk
の場合:
_$ awk -vFPAT='[^,]*|"[^"]*"' '{ gsub("^\"|\"$","",$1); gsub("^\"|\"$","",$4); print $1 $4} '
prabhat,kumarbangalore,India
_
_$1
_と_$4
_を並べて印刷しただけなので、出力形式は少し見苦しいです。好みに合わせて変更できると思います。
フィールドを囲む二重引用符を保持する必要がある場合は、両方のgsub();
関数を削除します。
通常、awk
はレコードのフィールドをFS
(フィールドセパレーター)変数の内容で区切ります。変数はデフォルトで空白(タブ、スペース、改行)になります。セパレータは、awk
にレコードの終了位置を伝えます。 csv
ファイルでは、レコードはコンマで終わります(awk
に_-vFS=,
_として渡されます)が、もちろん、あなたの例と同様の例では、これは単純すぎて壊れます。
または、FPAT
(フィールドパターン)はawk
内のレコードを定義します。 awk
にレコードの終了位置を通知する代わりに、レコード全体を含む定義を作成します。あなたの例の複雑なcsv
では、これは_[^,]*|"[^"]*"
_です
これは次のように分類されます。
[^,]
_)をできるだけ多く(_*
_)。 2つのコンマの間のすべてがフィールドです。|
_)"
_)に続く二重引用符なし(_[^"]
_)可能な限り多くの回数(_*
_)に続く単一の二重引用符(_"
_ )。二重引用符内のすべて(コンマを含む)は1つのフィールドとしてカウントされます。RubyはCSV解析に便利です。
Ruby -rcsv -ne 'puts CSV.generate_line(CSV.parse_line($_), :col_sep=>"|")' file
prabhat,kumar|19|2000|bangalore,India|ABC,DEF|GHI|123|KLM|NOP,QRS
prabhat,kumar|19|2000|bangalore,India|ABC,DEF|GHI|123|KLM|NOP,QRS
出力には引用符がないことに注意してください。これは、どのフィールドにもフィールドセパレータが含まれていないためです。引用符が必要な場合は、すべてのフィールド(整数も含む)を強制的に引用できます。
Ruby -rcsv -ne 'puts CSV.generate_line(CSV.parse_line($_), :col_sep=>"|",:force_quotes=>true)' file
"prabhat,kumar"|"19"|"2000"|"bangalore,India"|"ABC,DEF"|"GHI"|"123"|"KLM"|"NOP,QRS"
"prabhat,kumar"|"19"|"2000"|"bangalore,India"|"ABC,DEF"|"GHI"|"123"|"KLM"|"NOP,QRS"
おそらくqsvの方があなたの速度でしょうか?
sed 's/.*/,&,/;:t
s/,"/"/;s/",/"/;s/,\([^"]*\),/"\1/;tt
' <<\DATA
"prabhat,kumar",19,2000,"bangalore,India"
DATA
"prabhat,kumar"19"2000"bangalore,India"
代わりにPerl
を使用できます。
$ echo '"prabhat,kumar",19,2000,"bangalore,India"' |
Perl -F',(?![[:alpha:]])' -anle 'print "$F[0] $F[3]"'
"prabhat,kumar" "bangalore,India"
これは私のために働きました:
$ echo '"prabhat,kumar",19,2000,"bangalore,India"' |
awk -F, '{print $1,$2,$3,$4,$5,$6}'|
awk -F\" '{print $2,$3,$4}'|awk -F\ '{print $1","$2,$3,$4,$5","$6}'`