CSVといえば、1行1レコードとして、各フィールドをカンマで区切っているファイル形式として、いまさら説明する必要がないほどによく使われています。
あまりにもポピュラーなのに、ちゃんとした形式はドキュメントが整備されてこなかった経緯があって、rfc4180がやっとInformationalとして発行されているぐらいです。
このファイル形式の問題点は、SJISかUTF-8かというマルチバイト固有のエンコーディングの問題もありますが、やはりデリミタが文字列として入っている場合の処理が面倒になるという点でしょう。
自分でデータを作成する場合にはデータにデリミタを含まないと決める事でawkやら各種スクリプト言語でループ+split()を使い簡単に処理をさせる事ができます。
しかし「デリミタを含まない」という前提が崩れてしまったり、もらったデータにはダブルクォートやらデリミタが含まれていて区切り文字でsplitする事ができない場面もあると思います。
デリミタをデータに含んでいる場合にはrfc4180にも書かれている通り、ダブルクォート'"'で囲む事が一般的です。ちなみにWindows版のExcel, OpenOffice(oocalc)でCSV形式で出力させると次のようになります。
Excel 2007が出力するCSVファイル
no#,dirname,dirname2
1,X11R6,"b,in"
OpenOffice Calc (oocalc) 3.1が出力するCSVファイル
"no#","dirname","dirname2"
1,"X11R6","b,in"
まとめ
"bin"の間にカンマ','を含んでいると、excel、oocalcどちらもダブルクォートで囲みますが、”X11R6"のようにexcelではダブルクォート、カンマを含まないデータはそのまま、oocalcでは数字以外はダブルクォートで囲むように処理されています。
CSVで保存する目的は他のソフトウェアやマシンにデータを渡すことが多いと思いますが、処理する先では元のデータに戻すために、ダブルクォートで囲まれていないカンマでレコードを分割し、ダブルクォート2つを1つに置換してから、最終的にはフィールドを囲むダブルクォートを取り去る必要があります。
ありふれたawkを使う方法は微妙に面倒なので、あまり普通のサーバーには入っていないかもしれないrubyを使って切り分けるcsv_split()を作ってみました
#!/usr/bin/ruby
def csv_split(reg_char, line)
res = []
quote_flag = false
prev = nil
for item in line.split(reg_char)
if (item.count('"') % 2) == 1
quote_flag = (not quote_flag)
end
## yield during quotation
if quote_flag
if prev == nil
prev = item
else
prev = "#{prev},#{item}"
end
next
end
## return the result
if not quote_flag and prev != nil
res << escape_result("#{prev},#{item}")
prev = nil
else
res << escape_result(item)
end
end
yield res
end
def escape_result(item)
res = item
res = res.gsub(Regexp.new('""'),'"').gsub(Regexp.new('^"'), '').gsub(Regexp.new('"$'), '')
return res
end
## main ##
open(ARGV[0]).each do |line|
line.chop!()
csv_split(/,/, line) do |f1,f2,f3|
if header == false
header = true
next
end
print "#{f1}\t#{f2}\t#{f3}\n"
end
end