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.csv は col2
はカンマだけでなく改行も含むというよく分からないデータにしてみた。
$ 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
Ruby の csv ライブラリを使う
Ruby の csv ライブラリを使うと、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 処理用のライブラリを使う。
Ruby の csv ライブラリを使うのとほぼ同じだが、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 を出力するとき、数値項目はカンマを含めない」ことだと思う。