Data yang masuk ke program jarang berupa potongan tunggal yang siap pakai: ada tabel CSV hasil ekspor, baris log aplikasi, pasangan kunci=nilai dari berkas konfigurasi. Parsing berarti memecah teks terformat itu menjadi bagian-bagian yang bisa diproses — dan untuk format sederhana, standard library Go sudah menyediakan seluruh bahannya. Pada chapter ini kita akan membangun parser CSV mini dengan bufio.Scanner dan strings.Split, membersihkan baris log dengan strings.Fields, lalu memakai strings.Cut dan strings.SplitN untuk format kunci-nilai.

Membaca Baris demi Baris #

Hampir semua format berbasis teks berbaris: satu baris berarti satu data. Alat bacanya adalah bufio.Scanner, yang menyajikan isi pembacaan sebagai potongan baris. Untuk data yang sudah berada di memori, strings.NewReader membungkus string menjadi pembaca tanpa perlu berkas sungguhan:

go
scanner := bufio.NewScanner(strings.NewReader("satu\ndua"))
for scanner.Scan() {
	fmt.Println(scanner.Text())
}

Output yang Dihasilkan:

text
satu
dua

Method Scan() maju satu baris dan mengembalikan false saat data habis, sedangkan Text() mengambil isi baris berjalan tanpa tanda baris baru. Pola for scanner.Scan() menjadi tulang punggung pembacaan teks di Go. Saat sumbernya berkas nyata, kebiasaan yang baik adalah memeriksa scanner.Err() setelah loop selesai — pembacaan dari strings.NewReader tidak mungkin gagal, tetapi berkas dan koneksi jaringan bisa. Pembahasan mendalam tentang bufio tersedia di chapter bufio .

Memecah CSV Manual #

Tabel CSV sederhana bisa diparsing dengan pipeline tiga langkah: baca baris dengan Scanner, pecah kolom dengan strings.Split, lalu bersihkan tiap kolom dengan strings.TrimSpace. Validasi angka memakai strconv.Atoi menjaga satu baris rusak tidak merobek seluruh proses — baris bermasalah dilompati dengan pesan, baris berikutnya tetap diproses. Program utuh parsing-demo.go di bawah menerapkan pipeline lengkap ini, termasuk satu baris umur yang sengaja tidak valid.

strings.Cut: Idiom Pemisahan Modern #

Sejak Go 1.18, pemisahan pada pemisah pertama memakai satu panggilan strings.Cut. Nilai baliknya tiga: isi sebelum pemisah, isi sesudahnya, dan boolean found yang menyatakan pemisah ditemukan atau tidak:

go
before, after, found := strings.Cut("NAMA=Budi", "=")
fmt.Println(before, after, found)

Output yang Dihasilkan:

text
NAMA Budi true

Pola ini menggantikan rangkaian lama Index + slicing dengan pemeriksaan -1 secara manual. Saat pemisah tidak ditemukan, found bernilai false dan seluruh teks berada di before dengan after kosong — pemanggil cukup memeriksa satu boolean. Kombinasi favoritnya: Cut + TrimSpace untuk baris konfigurasi seperti user = Siti yang spasi pemisahnya bisa berubah-ubah.

Fields untuk Log, SplitN untuk Batas #

Baris log punya whitespace tak beraturan: spasi bertumpuk, tab, baris baru. strings.Fields memotong sekaligus membuang semua yang kosong sehingga hasilnya selalu kata-kata padat. Sementara itu strings.SplitN(teks, pemisah, n) membatasi jumlah potongan — bagian sisanya tidak dipecah lagi:

go
fmt.Println(strings.Split("a=b=c", "="))
fmt.Println(strings.SplitN("a=b=c", "=", 2))

Output yang Dihasilkan:

text
[a b c]
[a b=c]

Split penuh menghasilkan tiga potongan, sedangkan SplitN dengan batas 2 menjaga b=c tetap utuh di potongan kedua — perilaku yang tepat saat nilai konfigurasi boleh memuat tanda = sendiri, misalnya URL dengan query string.

Program Utuh #

Dua berkas siap dijalankan dari direktori chapter. Berkas pertama memparsing tabel CSV:

go
package main

import (
	"bufio"
	"fmt"
	"strconv"
	"strings"
)

func main() {
	data := "nama, kota, umur\nBudi, Jakarta, 25\nSiti, Bandung, dua puluh\nDewi, Solo, 27"

	// bufio.Scanner membaca baris demi baris dari string.
	scanner := bufio.NewScanner(strings.NewReader(data))
	scanner.Scan() // buang baris kepala tabel
	for scanner.Scan() {
		// Split kolom lalu bersihkan spasi di tiap kolom.
		kolom := strings.Split(scanner.Text(), ",")
		for j := range kolom {
			kolom[j] = strings.TrimSpace(kolom[j])
		}

		// Kolom umur divalidasi sebagai angka.
		umur, err := strconv.Atoi(kolom[2])
		if err != nil {
			fmt.Printf("%s: kolom umur tidak valid (%v)\n", kolom[0], err)
			continue
		}
		fmt.Printf("%s (%s) berumur %d tahun\n", kolom[0], kolom[1], umur)
	}
}
bash
go run parsing-demo.go

Output yang Dihasilkan:

text
Budi (Jakarta) berumur 25 tahun
Siti: kolom umur tidak valid (strconv.Atoi: parsing "dua puluh": invalid syntax)
Dewi (Solo) berumur 27 tahun

Berkas kedua merangkum Cut, Fields, SplitN, dan pipeline konfigurasi:

go
package main

import (
	"fmt"
	"strings"
)

func main() {
	// strings.Cut memisah pada kemunculan pemisah pertama.
	before, after, found := strings.Cut("NAMA=Budi Santoso", "=")
	fmt.Println("Cut    :", before, "|", after, "| ditemukan:", found)

	// Pemisah tidak ada: seluruh teks menjadi bagian pertama.
	before, after, found = strings.Cut("teks-polos", "=")
	fmt.Println("Cut    :", before, "|", after, "| ditemukan:", found)

	// strings.Fields memotong whitespace bertumpuk sekaligus.
	log := "  galat:   koneksi   basisdata   gagal  "
	kata := strings.Fields(log)
	fmt.Println("Fields :", kata, "(", len(kata), "kata )")
	fmt.Println("level  :", kata[0], "pesan:", strings.Join(kata[1:], " "))

	// strings.SplitN membatasi jumlah potongan yang dihasilkan.
	path := "/go/string-dan-text-processing/parsing-text"
	bagian := strings.SplitN(path, "/", 3)
	fmt.Println("SplitN :", bagian)

	// Pipeline Cut + TrimSpace untuk baris konfigurasi.
	for _, baris := range []string{"user = Siti", "kota=Bandung"} {
		kunci, nilai, _ := strings.Cut(baris, "=")
		fmt.Printf("[%s] = [%s]\n", strings.TrimSpace(kunci), strings.TrimSpace(nilai))
	}
}
bash
go run cut-demo.go

Output yang Dihasilkan:

text
Cut    : NAMA | Budi Santoso | ditemukan: true
Cut    : teks-polos |  | ditemukan: false
Fields : [galat: koneksi basisdata gagal] ( 4 kata )
level  : galat: pesan: koneksi basisdata gagal
SplitN : [ go string-dan-text-processing/parsing-text]
[user] = [Siti]
[kota] = [Bandung]

Mari kita telaah baris demi baris kedua program di atas:

  1. Baris 10-15 (Cut dua kali): pemanggilan pertama memisah NAMA dari Budi Santoso pada tanda = pertama; pemanggilan kedua menunjukkan teks tanpa pemisah — found bernilai false dan seluruh teks jatuh ke before.
  2. Baris 18-21 (Fields): baris log dengan spasi bertumpuk menjadi 4 kata bersih; tanda kurung pada format memperlihatkan batas potongan.
  3. Baris 21 (Join kata[1:]): level log diambil dari kata pertama, sisanya dirangkai kembali menjadi satu pesan utuh koneksi basisdata gagal.
  4. Baris 24-26 (SplitN): path dipecah maksimal 3 potongan — elemen pertama kosong karena path diawali /, dan potongan ketiga menyimpan sisa path tanpa dipecah lagi.
  5. Baris 29-32 (pipeline konfigurasi): dua baris dengan format spasi berbeda (user = Siti dan kota=Bandung) menghasilkan pasangan kunci-nilai yang sama bersihnya berkat Cut plus TrimSpace.

Pada parsing-demo.go, perhatikan pula baris scanner.Scan() sebelum loop yang membuang baris kepala tabel, dan blok if err != nil yang melompati baris Siti tanpa menghentikan program. Dengan empat alat ini — Scanner, Split/Fields, Cut, dan SplitN — format teks berbaris tertangani tanpa satu pun regex. Namun begitu polanya menjadi rumit — validasi bentuk email, menangkap pola berulang — regex memanggil masuk, dan itulah pokok bahasan chapter Regular Expression .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

cut-demo.go

Unduh berkas
go
package main

import (
	"fmt"
	"strings"
)

func main() {
	// strings.Cut memisah pada kemunculan pemisah pertama.
	before, after, found := strings.Cut("NAMA=Budi Santoso", "=")
	fmt.Println("Cut    :", before, "|", after, "| ditemukan:", found)

	// Pemisah tidak ada: seluruh teks menjadi bagian pertama.
	before, after, found = strings.Cut("teks-polos", "=")
	fmt.Println("Cut    :", before, "|", after, "| ditemukan:", found)

	// strings.Fields memotong whitespace bertumpuk sekaligus.
	log := "  galat:   koneksi   basisdata   gagal  "
	kata := strings.Fields(log)
	fmt.Println("Fields :", kata, "(", len(kata), "kata )")
	fmt.Println("level  :", kata[0], "pesan:", strings.Join(kata[1:], " "))

	// strings.SplitN membatasi jumlah potongan yang dihasilkan.
	path := "/go/string-dan-text-processing/parsing-text"
	bagian := strings.SplitN(path, "/", 3)
	fmt.Println("SplitN :", bagian)

	// Pipeline Cut + TrimSpace untuk baris konfigurasi.
	for _, baris := range []string{"user = Siti", "kota=Bandung"} {
		kunci, nilai, _ := strings.Cut(baris, "=")
		fmt.Printf("[%s] = [%s]\n", strings.TrimSpace(kunci), strings.TrimSpace(nilai))
	}
}

parsing-demo.go

Unduh berkas
go
package main

import (
	"bufio"
	"fmt"
	"strconv"
	"strings"
)

func main() {
	data := "nama, kota, umur\nBudi, Jakarta, 25\nSiti, Bandung, dua puluh\nDewi, Solo, 27"

	// bufio.Scanner membaca baris demi baris dari string.
	scanner := bufio.NewScanner(strings.NewReader(data))
	scanner.Scan() // buang baris kepala tabel
	for scanner.Scan() {
		// Split kolom lalu bersihkan spasi di tiap kolom.
		kolom := strings.Split(scanner.Text(), ",")
		for j := range kolom {
			kolom[j] = strings.TrimSpace(kolom[j])
		}

		// Kolom umur divalidasi sebagai angka.
		umur, err := strconv.Atoi(kolom[2])
		if err != nil {
			fmt.Printf("%s: kolom umur tidak valid (%v)\n", kolom[0], err)
			continue
		}
		fmt.Printf("%s (%s) berumur %d tahun\n", kolom[0], kolom[1], umur)
	}
}