全力で怠けたい

怠けるために全力を尽くしたいブログ。

CSV 内のカンマを除去するワンライナー3選 + おまけ2つ。

Twitter で「CSV 内の数値項目がカンマを含んでいるが、そのカンマを除去したい」というのを見かけたので、ワンライナーでやってみた。 あと、おまけとして普通にプログラムを書くやり方でもやってみた。

はじめに

個人的には、システム同士の間などでデータ連係をするとき「CSV でいいんじゃない、簡単だし」みたいな会話が聞こえてきたら、相当苦労しそうな予感がする。 実際、CSV を少しでも扱った経験があれば「相当面倒くさい」データ形式だということは一定の理解を得られると思うし、CSV を扱った経験がなくても CSV の国際標準を定める RFC 4180 をサーッと流し読みすると、結構いろいろなことを考慮しないといけなさそうだな、とピンとくると思う。

今回は「CSV 内の数値項目がカンマを含んでいるが *1 、そのカンマを除去したい」という問題をワンライナーで解決していく *2

処理する CSV

数値項目がカンマを含んでいる 2つの CSV ファイル、data.csv と new-lines-and-comma.csv を用意した。

2つの CSV ファイルとも1行目がヘッダー行になっていて、col2 がカンマを含む数値項目となっている。 また、new-lines-and-comma.csvcol2 はカンマだけでなく改行も含むというよく分からないデータにしてみた。

$  cat data.csv
col1,col2,col3
a,"1,2,3",b

$ cat new-lines-and-comma.csv
col1,col2,col3
"comma is ,","1,2,
3",100

ワンライナー3選

xsv コマンド + よく知られたコマンドの組み合わせ

xsv コマンドを使うと、CSV ファイルを分割したり結合したりといったことが簡単にできる。

xsv コマンドの table サブコマンドを使うと、CSV をスペース区切りのデータに変換できる。また。col2 のようにカンマを含む項目も正しく処理できる。

$ xsv table data.csv
col1  col2   col3
a     1,2,3  b

CSV をスペース区切りのデータに変換したら、よく使うコマンドを組み合わせてカンマを除去できる。

$ xsv table data.csv | \
    teip -f 2 -- tr -d ',' | \
    awk 'BEGIN{OFS=","}{print $1,$2,$3}'
col1,col2,col3
a,123,b

ただ、CSV ファイルが new-lines-and-comma.csv のようにデータ内に改行を含むときはうまく処理できない。

$  xsv table new-lines-and-comma.csv
col1        col2  col3
comma is ,  "1,2,
3"          100

$ xsv table new-lines-and-comma.csv | \
    teip -f 2 -- tr -d ',' | \
    awk 'BEGIN{OFS=","}{print $1,$2,$3}'
col1,col2,col3
comma,is,,
3",100,

q コマンドを使う

CSV や TSV に対して SQL クエリを実行できる q コマンドを使うと、CSV ファイル内のカンマを簡単に除去できる。

以下のように replace 関数でカンマを除去してしまえばよい。

$  q -H -O -d, 'select col1, replace(col2, ",", "") as col2, col3 from data.csv'
col1,col2,col3
a,123,b

また、CSV ファイルが new-lines-and-comma.csv のようにデータ内に改行を含む場合も、意図通りに処理できる。

$ q -H -O -d, 'select col1, replace(col2, ",", "") as col2, col3 from new-lines-and-comma.csv'
col1,col2,col3
"comma is ,","12
3",100

Rubycsv ライブラリを使う

Rubycsv ライブラリを使うと、CSV ファイル内のカンマを簡単に除去できる。

$ ruby -r csv -e 'CSV.read("data.csv").each{|row| row[1].gsub!(",", ""); puts row.to_csv}'
col1,col2,col3
a,123,b

また、CSV ファイルが new-lines-and-comma.csv のようにデータ内に改行を含む場合も、意図通りに処理できる。

$  ruby -r csv -e 'CSV.read("new-lines-and-comma.csv").each{|row| row[1].gsub!(",", ""); puts row.to_csv}'
col1,col2,col3
"comma is ,","12
3",100

おまけ2つ

汎用言語の csv 処理用のライブラリを使う

汎用言語の csv 処理用のライブラリを使う。

Rubycsv ライブラリを使うのとほぼ同じだが、Ruby 以外の汎用のプログラミング言語CSV を処理するライブラリを備えているので、そのライブラリを使えば CSV ファイル内のカンマを簡単に除去できる。

たとえば、Go なら以下のようなプログラムを書くと今回の目的が達成できる。

package main

import (
    "encoding/csv"
    "io"
    "log"
    "os"
    "strings"
)

func main() {
    r := csv.NewReader(os.Stdin)
    w := csv.NewWriter(os.Stdout)

    for {
        row, err := r.Read()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalln(err)
        }
     
        row[1] = strings.ReplaceAll(row[1], ",", "")
        if err := w.Write(row); err != nil {
            log.Fatalln("error writing record to csv:", err)
        }
    }

    w.Flush()

    if err := w.Error(); err != nil {
        log.Fatalln(err)
    }
}

data.csv も new-lines-and-comma.csv も意図通りに処理できる。

$ go build -o main main.go

$ cat data.csv | ./main
col1,col2,col3
a,123,b

$ cat new-lines-and-comma.csv | ./main
col1,col2,col3
"comma is ,","12
3",100

一番確実な解決法

いろいろ書いたけど、「CSV 内の数値項目がカンマを含んでいるが、そのカンマを除去したい」という問題の一番簡単な解決法は、「CSV を出力するとき、数値項目はカンマを含めない」ことだと思う。

参考サイト

*1:CSV 内の数値項目がカンマを含んでいたら、それはもう文字列なのでは? という疑問は一旦置いておく

*2:実行速度も一旦置いておく