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:
scanner := bufio.NewScanner(strings.NewReader("satu\ndua"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}Output yang Dihasilkan:
satu
duaMethod 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:
before, after, found := strings.Cut("NAMA=Budi", "=")
fmt.Println(before, after, found)Output yang Dihasilkan:
NAMA Budi truePola 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:
fmt.Println(strings.Split("a=b=c", "="))
fmt.Println(strings.SplitN("a=b=c", "=", 2))Output yang Dihasilkan:
[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:
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)
}
}go run parsing-demo.goOutput yang Dihasilkan:
Budi (Jakarta) berumur 25 tahun
Siti: kolom umur tidak valid (strconv.Atoi: parsing "dua puluh": invalid syntax)
Dewi (Solo) berumur 27 tahunBerkas kedua merangkum Cut, Fields, SplitN, dan pipeline konfigurasi:
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))
}
}go run cut-demo.goOutput yang Dihasilkan:
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:
- Baris 10-15 (Cut dua kali): pemanggilan pertama memisah
NAMAdariBudi Santosopada tanda=pertama; pemanggilan kedua menunjukkan teks tanpa pemisah —foundbernilaifalsedan seluruh teks jatuh kebefore. - Baris 18-21 (Fields): baris log dengan spasi bertumpuk menjadi 4 kata bersih; tanda kurung pada format memperlihatkan batas potongan.
- Baris 21 (Join kata[1:]): level log diambil dari kata pertama, sisanya dirangkai kembali menjadi satu pesan utuh
koneksi basisdata gagal. - Baris 24-26 (SplitN): path dipecah maksimal 3 potongan — elemen pertama kosong karena path diawali
/, dan potongan ketiga menyimpan sisa path tanpa dipecah lagi. - Baris 29-32 (pipeline konfigurasi): dua baris dengan format spasi berbeda (
user = Sitidankota=Bandung) menghasilkan pasangan kunci-nilai yang sama bersihnya berkatCutplusTrimSpace.
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 #
- Dokumentasi package strings — Cut, SplitN, Fields, dan pembaca Reader.
- Dokumentasi package bufio — Scanner dan pola pembacaan berbaris.
- Dokumentasi package encoding/csv — parser CSV lengkap untuk produksi.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
cut-demo.go
Unduh berkaspackage 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 berkaspackage 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)
}
}