go读写csv文件

我害怕,看到你,独自一人绝望; 更害怕,看不到你,不能和你一起迷惘……

go言語の encoding/csv パッケージの使い方。
CSVファイルの読み書きと、オプション、エラーハンドリングについて書いてます。

一行ずつ読み込む(Read)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

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

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}

// recordは配列
fmt.Printf("%#v\n", record)

// []string{"名前", "年齢", "身長", "体重"}
// []string{"Tanaka", "31", "190cm", "97kg"}
// []string{"Suzuki", "46", "180cm", "79kg"}
// []string{"Matsui", "45", "188cm", "95kg"}
}
}

ファイルから読み込む

ファイルから読み込む場合は csv.NewReader*os.File を渡す

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

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

func main() {
f, err := os.Open("file.csv")
if err != nil {
log.Fatal(err)
}

r := csv.NewReader(f)

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
fmt.Println(record)
}
}

csv.Readerのオプション

Comma: 区切り文字を変更
Comment: コメント行の先頭になる文字を指定
FieldsPerRecord: 各行のフィールド数を指定
LazyQuotes: ダブルクオートを厳密にチェックするかどうか
TrimLeadingSpace: 先頭の空白文字を無視する
ReuseRecord: スライスの再利用
TrailingComma: Deprecated(非推奨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

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

func main() {
s := `名前;年齢;身長;体重
# コメント行
Tanaka;31;190cm;97kg
# コメント行
Suzuki;46;180cm;79kg
# コメント行
Matsui;45;188cm;95kg
`

r := csv.NewReader(strings.NewReader(s))
r.Comma = ';' // 区切り文字を , から ; に変更
r.Comment = '#' // 先頭が # の場合はコメント行として扱う
r.FieldsPerRecord = 4 // 各行のフィールド数。多くても少なくてもエラーになる
r.LazyQuotes = true // true の場合、"" が値の途中に "180"cm のようになっていてもエラーにならない
r.TrimLeadingSpace = true // true の場合は、先頭の空白文字を無視する
r.ReuseRecord = true // true の場合は、Read で戻ってくるスライスを次回再利用する。パフォーマンスが上がる

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}

// recordは配列
fmt.Printf("%#v\n", record)

// []string{"名前", "年齢", "身長", "体重"}
// []string{"Tanaka", "31", "190cm", "97kg"}
// []string{"Suzuki", "46", "180cm", "79kg"}
// []string{"Matsui", "45", "188cm", "95kg"}
}
}

エラーハンドリング(csv.ParseError)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"encoding/csv"
"fmt"
"log"
"strings"
)

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

records, err := r.ReadAll()
if err != nil {
if e, ok := err.(*csv.ParseError); ok {
n := 0
switch e.Err {
case csv.ErrBareQuote:
// ダブルクオート途中で使用されていて LazyQuotes を true にしていない場合のエラー
// 例えば、 Tan"aka,31,190cm,97kg のように 途中に " がある場合
n = 1
case csv.ErrQuote:
// 先頭がダブルクオートで始まっていて、末尾がダブルクオートになっていない場合のエラー
// 例えば、 "Tanaka,31,190cm,97kg のように閉じるための " がない場合
n = 2
case csv.ErrFieldCount:
// FieldsPerRecordで指定した数と異なる場合のエラー
n = 3
}
log.Fatal("\nエラー: ", n, "\n", e.Err,
"\nStartLine:", e.StartLine, "\nLine:", e.Line, "\nColumn:", e.Column)
}
log.Fatal(err)
}

fmt.Println(records)
}

読み込んだShift-JISファイルをUTF8にする

encoding/japanesetext/transform が必要なのでダウンロードする

1
$ go get golang.org/x/text/encoding/japanese golang.org/x/text/transform
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"encoding/csv"
"fmt"
"log"
"os"

"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
)

func main() {
f, err := os.Open("file-sjis.csv")
if err != nil {
log.Fatal(err)
}

r := csv.NewReader(transform.NewReader(f, japanese.ShiftJIS.NewDecoder()))

for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}

fmt.Println(record)
}
}

一度にすべて読み込む(ReadAll)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"encoding/csv"
"fmt"
"log"
"strings"
)

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

record, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}

fmt.Printf("%#v\n", record)
// [][]string{
// []string{"名前", "年齢", "身長", "体重"},
// []string{"Tanaka", "31", "190cm", "97kg"},
// []string{"Suzuki", "46", "180cm", "79kg"},
// []string{"Matsui", "45", "188cm", "95kg"},
// }
}

一行ずつ書き込む(Write)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

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

func main() {
records := [][]string{
[]string{"名前", "年齢", "身長", "体重"},
[]string{"Tanaka", "31", "190cm", "97kg"},
[]string{"Suzuki", "46", "180cm", "79kg"},
[]string{"Matsui", "45", "188cm", "95kg"},
}

f, err := os.Create("file.csv")
if err != nil {
log.Fatal(err)
}

w := csv.NewWriter(f)

// オプション
w.Camma = ',' // デフォルトはカンマ区切りで出力される。変更する場合はこの rune 文字を変更する
w.UseCRLF = true // 改行文字を CRLF(\r\n) にする

for _, record := range records {
if err := w.Write(record); err != nil {
log.Fatal(err)
}
}

w.Flush() // バッファに残っているデータをすべて書き込む

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

書き込み結果

1
2
3
4
5
$ cat file.csv
名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg

すべて一度に書き込む(WriteAll)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

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

func main() {
records := [][]string{
[]string{"名前", "年齢", "身長", "体重"},
[]string{"Tanaka", "31", "190cm", "97kg"},
[]string{"Suzuki", "46", "180cm", "79kg"},
[]string{"Matsui", "45", "188cm", "95kg"},
}

f, err := os.Create("file.csv")
if err != nil {
log.Fatal(err)
}

w := csv.NewWriter(f)

w.WriteAll(records)

w.Flush()

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

CSVを構造体にマッピングする

以下のように書いてみたけど、reflect 使うとさらに面倒そう。
なので gocsv (gocsvのgodocページ) を使えば、読み込んだCSVを構造体にマッピングできて、 その逆で構造体からCSVに変換するのも楽にできそう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"encoding/csv"
"fmt"
"io"
"log"
"strconv"
"strings"
)

type People struct {
Name string
Age int
Height string
Weight string
}

func main() {
s := `名前,年齢,身長,体重
Tanaka,31,190cm,97kg
Suzuki,46,180cm,79kg
Matsui,45,188cm,95kg
`
r := csv.NewReader(strings.NewReader(s))

var p []People

for i := 0; ; i++ {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
if i == 0 {
continue
}

var people People
for i, v := range record {
switch i {
case 0:
people.Name = v
case 1:
people.Age, err = strconv.Atoi(v)
if err != nil {
log.Fatal(err)
}
case 2:
people.Height = v
case 3:
people.Weight = v
}
}
p = append(p, people)
}

fmt.Println(p)
}

https://golang.hateblo.jp/entry/golang-encoding-csv