古い記事
ランダムジャンプ
新しい記事
CSV ファイルで、テキストのカラムにカンマが入っているんだけど、そのカラムがダブルクォート文字で囲まれていないときの対処法について。

ときどきそういうデータがあるのです。
適当なその場しのぎスクリプトで 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" がない前提で。