4.11.1 مقدمه #
CSV یکی از فرمتهای متداول برای ذخیره دادههای جدولی است. خروجی اکثر برنامههای قابلیت دستکاری داده، همراه با نرمافزارهای آفیس، به فرمت CSV تولید میشود. فرمت CSV چندین ستون در یک ردیف را با استفاده از کاما (,) جدا کرده و هر ردیف را با استفاده از عبارت جدید (newline) جدا میکند.
در زبان برنامهنویسی Go نیز پکیج encoding/csv وجود دارد که در آن، توابع مربوط به خواندن و نوشتن دادههای CSV به صورت دستی یا از طریق پروندهها فراهم شده است. با استفاده از این پکیج، میتوان دادههای CSV را به دادههای جدولی تبدیل کرد و برعکس.
به عنوان مثال، در ادامه یک فایل CSV به نام “data.csv” حاوی اطلاعات چند شخص را در نظر بگیرید:
Name,Age,City
John,25,New York
Jane,30,San Francisco
Bob,40,Los Angeles
4.11.2 نحوه خواندن فایل csv #
برای خواندن فایل csv میتوان با استفاده از پکیج encoding/csv پرونده CSV را باز کرد:
1package main
2
3import (
4 "encoding/csv"
5 "fmt"
6 "os"
7)
8
9func main() {
10 f, err := os.Open("data.csv")
11 if err != nil {
12 panic(err)
13 }
14
15 r := csv.NewReader(f)
16 records, err := r.ReadAll()
17 if err != nil {
18 panic(err)
19 }
20
21 for _, row := range records {
22 for _, col := range row {
23 fmt.Print(col, "\t")
24 }
25 fmt.Println()
26 }
27}
در این کد، تابع os.Open برای باز کردن پرونده CSV استفاده میشود. یک رابط csv.Reader ایجاد شده و یک رشته ساختارمند، پرونده CSV را میخواند. سپس با استفاده از یک حلقه، دادههای جدولی چاپ میشود.
فرادادههای CSV بسیار گسترده هستند و میتوانند شامل شماره دسته، توضیحات، یادداشتهای شخصی و غیره باشند. برای کار با این نوع دادهها، پکیج encoding/csv امکاناتی مانند تنظیمات csv.Reader را فراهم میکند، که در آن، میتوانیم تنظیماتی مانند علامتگذاری مناسب فایل CSV و دیگر علامتگذاریها را بهبود ببخشیم.
4.11.3 ReadAll فایل csv #
تابع ReadAll تمام رکوردهای باقی مانده را از reader می خواند. هر رکورد یک قسمتی از fieldها است.
first_name,last_name,occupation
John,Doe,gardener
Lucy,Smith,teacher
Brian,Bethamy,programmer
نام این فایل users.csv است. خط اول نام ستون ها هستند.
1package main
2
3import (
4 "encoding/csv"
5 "fmt"
6 "log"
7 "os"
8)
9
10type User struct {
11 firstName string
12 lastName string
13 occupation string
14}
15
16func main() {
17
18 records, err := readData("users.csv")
19
20 if err != nil {
21 log.Fatal(err)
22 }
23
24 for _, record := range records {
25
26 user := User{
27 firstName: record[0],
28 lastName: record[1],
29 occupation: record[2],
30 }
31
32 fmt.Printf("%s %s is a %s\n", user.firstName, user.lastName,
33 user.occupation)
34 }
35}
36
37func readData(fileName string) ([][]string, error) {
38
39 f, err := os.Open(fileName)
40
41 if err != nil {
42 return [][]string{}, err
43 }
44
45 defer f.Close()
46
47 r := csv.NewReader(f)
48
49 // skip first line
50 if _, err := r.Read(); err != nil {
51 return [][]string{}, err
52 }
53
54 records, err := r.ReadAll()
55
56 if err != nil {
57 return [][]string{}, err
58 }
59
60 return records, nil
61}
اسم فایل بالا read_all.go میباشد و این مثال فایل users.csv را می خواند. هر line به یک User
type را بر میگرداند.
در اینجا از خط اول که شامل نام ستون هاست می گذریم.
1records, err := r.ReadAll()
در نهایت همه رکوردها را یک جا با ReadAll دریافت می کنیم.
1$ go run read_all.go
2John Doe is a gardener
3Lucy Smith is a teacher
4Brian Bethamy is a programmer
4.11.4 delimiter CSV دلخواه #
علیرغم نام CSV ، CSV ممکن است دارای جداکننده های دیگری غیر از کاما باشد. این به دلیل استاندارد نبودن قالب CSV است.
# user.csv
# this is users.csv file
John;Doe;gardener
Lucy;Smith;teacher
Brian;Bethamy;programmer
در فایل users.csv فیلدها با نقطه ویرگول از هم جدا شده اند. این فایل حاوی یک comment نیز میباشد.
1//different_delimiter.go//
2
3package main
4
5import (
6 "encoding/csv"
7 "fmt"
8 "log"
9 "os"
10)
11
12func main() {
13
14 f, err := os.Open("users.csv")
15
16 if err != nil {
17
18 log.Fatal(err)
19 }
20
21 r := csv.NewReader(f)
22 r.Comma = ';'
23 r.Comment = '#'
24
25 records, err := r.ReadAll()
26
27 if err != nil {
28 log.Fatal(err)
29 }
30
31 fmt.Print(records)
32}
این مثال تمام داده های این فایل را می خواند.
در اینجا separator و کاراکتر comment را تنظیم می کنیم تا package بداند چگونه فایل را parse یا تجریه تحلیل کند.
4.11.5 نوشتن CSV #
تابع Write یک رکورد CSV را برای writer می نویسد. رکورد برشی از strings است که هر string یک فیلد است. write ها buffer شده هستند، بنابراین باید Flush فراخوانی شود تا اطمینان حاصل شود که رکورد برای writer اصلی نوشته شده است.
1//write_fun.go//
2package main
3
4import (
5 "encoding/csv"
6 "log"
7 "os"
8)
9
10func main() {
11
12 records := [][]string{
13 {"first_name", "last_name", "occupation"},
14 {"John", "Doe", "gardener"},
15 {"Lucy", "Smith", "teacher"},
16 {"Brian", "Bethamy", "programmer"},
17 }
18
19 f, err := os.Create("users.csv")
20 defer f.Close()
21
22 if err != nil {
23
24 log.Fatalln("failed to open file", err)
25 }
26
27 w := csv.NewWriter(f)
28 defer w.Flush()
29
30 for _, record := range records {
31 if err := w.Write(record); err != nil {
32 log.Fatalln("error writing record to file", err)
33 }
34 }
35}
در مثال بالا، چند رکورد را با تابع Write در فایل users.csv نوشتیم.
4.11.6 نوشتن WriteAll CSV #
تابع WriteAll چندین رکورد CSV را با استفاده از Write برای writer مینویسد و سپس Flush را فراخوانی میکند.
1
2//write_all.go//
3
4package main
5
6import (
7 "encoding/csv"
8 "log"
9 "os"
10)
11
12func main() {
13
14 records := [][]string{
15 {"first_name", "last_name", "occupation"},
16 {"John", "Doe", "gardener"},
17 {"Lucy", "Smith", "teacher"},
18 {"Brian", "Bethamy", "programmer"},
19 }
20
21 f, err := os.Create("users.csv")
22 defer f.Close()
23
24 if err != nil {
25
26 log.Fatalln("failed to open file", err)
27 }
28
29 w := csv.NewWriter(f)
30 err = w.WriteAll(records) // calls Flush internally
31
32 if err != nil {
33 log.Fatal(err)
34 }
35}
در نهایت ما چند رکورد را در یک لحظه با WriteAll می نویسیم.