CSV ファイルで、テキストのカラムにカンマが入っているんだけど、そのカラムがダブルクォート文字で囲まれていないときの対処法について。
ときどきそういうデータがあるのです。
適当なその場しのぎスクリプトで CSV 出力した結果とか。
で、それをなんとかして使わないといけない場面もあるのです。
怒ってないです。
例えば、全部で10カラムのCSV。
第4カラムにテキスト。
CSV だからカンマ区切りなんだけど、テキストにカンマが含まれてる場合あり。
しかもダブルクォートで囲まれていない。
区切りのカンマと区別つかなくて困る。
こういうときは、前から3カラム(=4-1)、後ろから6カラム(=10-4)を除いた残りすべてを一つのテキストとする作戦で。
Perl で書くとこんな感じ。
splice 関数を使ったバージョン。
Perl の split 関数の第3引数の "-1" についてはこちらを参照。
Python で書くとこんな感じ。
右側から分割してくれる rsplit 関数がこの用途に便利。
カンマ入りテキストカラムが複数ある場合はちょっと難しい。めんどくさいのであきらめましょう。桁区切りカンマ入りの数値がクォート無しで複数カラムあるとかも。
あと、上のスクリプトではテキストにダブルクォートが含まれててエスケープされてない場合は考慮してません。join で "\t" を使って TSV にしとくのが良いかと。テキストに "\t" がない前提で。
ときどきそういうデータがあるのです。
適当なその場しのぎスクリプトで CSV 出力した結果とか。
で、それをなんとかして使わないといけない場面もあるのです。
怒ってないです。
例えば、全部で10カラムのCSV。
第4カラムにテキスト。
0,2,0,こんにちは,50,0,0,0,0,0 1,1,1,さようなら,0,0,0,0,0,0
CSV だからカンマ区切りなんだけど、テキストにカンマが含まれてる場合あり。
しかもダブルクォートで囲まれていない。
区切りのカンマと区別つかなくて困る。
理想: 1,1,0,"ああ,いい,ううう。",21,0,0,0,0,0 0,2,0,"あれれれ,",50,0,0,0,0,0 現実: 1,1,0,ああ,いい,ううう。,21,0,0,0,0,0 0,2,0,あれれれ,,50,0,0,0,0,0
こういうときは、前から3カラム(=4-1)、後ろから6カラム(=10-4)を除いた残りすべてを一つのテキストとする作戦で。
1,1,0,ああ,いい,ううう。,21,0,0,0,0,0 ↓ カンマで分割してリストにする 1 1 0 ああ いい ううう。 21 0 0 0 0 0 ↓ 前と後ろのカラムを取る [1 1 0] ああ いい ううう。 [21 0 0 0 0 0] ↓ ああ いい ううう。 ↓ 残りをカンマで繋ぎ直し、ダブルクォートで囲む "ああ,いい,ううう。" ↓ さっき取った、前と後ろのカラムと合わせて最終的な CSV にする 1,1,0,"ああ,いい,ううう。",21,0,0,0,0,0
Perl
Perl で書くとこんな感じ。
my $IX = 4; # 4番目のカラムがカンマ入りテキスト my $N = 10; # 全部で10カラム while (<>) { chomp; # 末尾の改行削除 my @F = split(",", $_, -1); # 読み込んだ CSV 行をカンマで切ってリストへ my @pre = @F[0..($IX-2)]; # 前から IX-1 個分のカラム my @post = @F[$#F-($N-$IX-1)..$#F]; # 後ろから N-IX 個分のカラム my $text = join(",", @F[($IX-1)..($#F-($N-$IX))]); # カンマ入りテキスト print join(",", @pre, "\"$text\"", @post)."\n"; # CSV に戻す }
splice 関数を使ったバージョン。
my $IX = 4; # 4番目のカラムがカンマ入りテキスト my $N = 4; # 全部で10カラム while (<>) { chomp; # 末尾の改行削除 my @F = split(",", $_, -1); # 読み込んだ CSV 行をカンマで切ってリストへ my @ts = join(",", splice(@F, $IX-1, (@F-$N) + 1)); # テキスト部分の抜き出し splice(@F, $IX-1, 0, qq(").join(",", @ts).qq(")); # ""をつけて戻す print join(",", @F)."\n"; # CSV に戻す }
Perl の split 関数の第3引数の "-1" についてはこちらを参照。
Python
Python で書くとこんな感じ。
右側から分割してくれる rsplit 関数がこの用途に便利。
ID = 4 N = 10 with open('sample.csv') as f: for line in f.read().splitlines(): pre = line.split(",", ID - 1) post = pre[-1].rsplit(",", N - ID) l = pre[:-1] l.append('"' + post[0] + '"') l.extend(post[1:]) print(",".join(l))
1,1,0,ああ,いい,ううう。,21,0,0,0,0,0 ↓ 最初の split で前から4分割(3箇所で切断) 1 / 1 / 0 / ああ,いい,ううう。,21,0,0,0,0,0 ↓ 一番右の塊を次の rsplit で後ろから7分割(6箇所で切断)。 ああ,いい,ううう。 / 21 / 0 / 0 / 0 / 0 / 0 ↓ 1 / 1 / 0 / ああ,いい,ううう。 / 21 / 0 / 0 / 0 / 0 / 0 ↓ 1,1,0,"ああ,いい,ううう。",21,0,0,0,0,0
おわりに
カンマ入りテキストカラムが複数ある場合はちょっと難しい。めんどくさいのであきらめましょう。桁区切りカンマ入りの数値がクォート無しで複数カラムあるとかも。
あと、上のスクリプトではテキストにダブルクォートが含まれててエスケープされてない場合は考慮してません。join で "\t" を使って TSV にしとくのが良いかと。テキストに "\t" がない前提で。