BAB 39: Regexp — Pencocokan Pola Teks

Pelajari cara menggunakan package regexp di Go untuk mencari, memvalidasi, dan memanipulasi string berdasarkan pola ekspresi reguler.

Package strings yang kamu pelajari di bab sebelumnya sangat efektif untuk pencocokan teks yang pola-nya tetap dan sudah diketahui — “apakah string ini mengandung kata X”, “ganti semua kemunculan Y dengan Z”. Tapi bagaimana jika polanya dinamis? Misalnya: “temukan semua yang terlihat seperti nomor telepon”, atau “validasi bahwa input ini mengikuti format email yang valid”, atau “ekstrak semua kata yang diawali huruf kapital”?

Di sinilah regular expression (regex) berperan. Go mengimplementasikan spesifikasi RE2 melalui package regexp — sebuah engine yang menjamin waktu eksekusi linear sehingga tidak rentan terhadap serangan ReDoS. Itu artinya regex di Go sedikit berbeda dari Perl atau PCRE, tapi jauh lebih aman untuk digunakan di produksi.

Kompilasi dan Pencocokan Dasar

Langkah pertama untuk menggunakan regex di Go adalah mengkompilasi pola menjadi objek *regexp.Regexp menggunakan regexp.Compile().

// catatan.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // pola: kata yang terdiri dari huruf kecil
    pola, err := regexp.Compile(`[a-z]+`)
    if err != nil {
        fmt.Println("pola tidak valid:", err)
        return
    }

    catatan := "Revisi dokumen anggaran proyek infrastruktur"

    // apakah ada yang cocok?
    fmt.Println(pola.MatchString(catatan))  // true

    // ambil satu kecocokan pertama
    fmt.Println(pola.FindString(catatan))   // "evisi"
}
true
evisi

Perhatikan bahwa FindString mengembalikan "evisi" bukan "Revisi" — karena pola [a-z]+ hanya cocok dengan huruf kecil, dan karakter pertama R tidak masuk. Go tidak otomatis mencocokkan keseluruhan kata; ia mencari substring pertama yang sesuai.

regexp.Compile() mengembalikan error jika pola tidak valid. Untuk program yang menggunakan pola statis (konstanta saat kompilasi), gunakan regexp.MustCompile() yang langsung panic jika pola salah — lebih ringkas tapi hanya aman untuk pola yang sudah terjamin valid.

Mencari Semua Kecocokan

FindAllString mengembalikan semua substring yang cocok dengan pola. Parameter kedua mengontrol batas jumlah hasil — gunakan -1 untuk mendapatkan semua.

// catatan.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // pola: kata (satu atau lebih huruf alfanumerik)
    pola := regexp.MustCompile(`[a-zA-Z]+`)

    catatan := "Rapat Senin jam 09:00 di Ruang Merak lantai 3"

    // ambil semua kata (hanya huruf, tanpa angka dan tanda baca)
    semua := pola.FindAllString(catatan, -1)
    fmt.Println(semua)

    // batasi hanya 3 hasil pertama
    tiga := pola.FindAllString(catatan, 3)
    fmt.Println(tiga)
}
[Rapat Senin jam di Ruang Merak lantai]
[Rapat Senin jam]

Perbedaan dengan FindString: FindAllString mengembalikan []string, sedangkan FindString mengembalikan string tunggal. Gunakan FindAllString ketika kamu perlu mengumpulkan semua kecocokan untuk diproses lebih lanjut.

Mendapatkan Posisi Kecocokan

Terkadang kamu tidak hanya butuh nilai yang cocok, tapi juga tahu di mana posisinya dalam string asli. FindStringIndex mengembalikan sepasang indeks [start, end].

// catatan.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    pola := regexp.MustCompile(`\d{4}-\d{2}-\d{2}`)

    laporan := "Periode laporan: 2026-01-01 hingga 2026-03-31"

    // posisi kecocokan pertama
    idx := pola.FindStringIndex(laporan)
    fmt.Println(idx)                          // [17 27]
    fmt.Println(laporan[idx[0]:idx[1]])       // 2026-01-01

    // semua posisi kecocokan
    semuaIdx := pola.FindAllStringIndex(laporan, -1)
    for _, pos := range semuaIdx {
        fmt.Printf("posisi %v: %q\n", pos, laporan[pos[0]:pos[1]])
    }
}
[17 27]
2026-01-01
posisi [17 27]: "2026-01-01"
posisi [37 47]: "2026-03-31"

Pola \d{4}-\d{2}-\d{2} cocok dengan format tanggal ISO 8601 (YYYY-MM-DD). \d adalah shorthand untuk digit [0-9], dan {n} berarti tepat n kali pengulangan.

Penggantian dengan Pola

ReplaceAllString mengganti semua substring yang cocok dengan teks baru.

// catatan.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // sensor nomor kartu kredit: 16 digit yang mungkin dipisah spasi
    polaSensor := regexp.MustCompile(`\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}`)

    pesan := "Nomor kartu: 4532 1234 5678 9012, silakan konfirmasi."

    disensor := polaSensor.ReplaceAllString(pesan, "****-****-****-****")
    fmt.Println(disensor)
}
Nomor kartu: ****-****-****-****. silakan konfirmasi.

Untuk penggantian yang lebih dinamis — di mana nilai pengganti bergantung pada nilai yang cocok — gunakan ReplaceAllStringFunc.

// catatan.go
package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    // ubah setiap kata yang diawali huruf kapital menjadi huruf kecil semua
    polaKapital := regexp.MustCompile(`[A-Z][a-z]+`)

    judul := "Laporan Keuangan Kuartal Pertama Tahun 2026"

    normalized := polaKapital.ReplaceAllStringFunc(judul, func(kata string) string {
        return strings.ToLower(kata)
    })
    fmt.Println(normalized)
}
laporan keuangan kuartal pertama tahun 2026

ReplaceAllStringFunc memanggil fungsi untuk setiap kecocokan, menerima substring yang cocok sebagai argumen, dan menggunakan nilai kembaliannya sebagai pengganti.

Memisahkan String dengan Pola

Split memotong string menggunakan kecocokan regex sebagai separator — lebih fleksibel dari strings.Split yang hanya bisa menerima separator berupa string tetap.

// catatan.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // pisahkan berdasarkan satu atau lebih spasi/tab
    polaSpasi := regexp.MustCompile(`\s+`)

    baris := "Jakarta    Surabaya\tBandung  Medan"
    kota := polaSpasi.Split(baris, -1)
    fmt.Println(kota)
    fmt.Println("jumlah kota:", len(kota))

    // pisahkan berdasarkan koma atau titik koma (dengan spasi opsional)
    polaPemisah := regexp.MustCompile(`\s*[,;]\s*`)

    daftar := "go; python, java ; rust,  javascript"
    bahasa := polaPemisah.Split(daftar, -1)
    fmt.Println(bahasa)
}
[Jakarta Surabaya Bandung Medan]
jumlah kota: 4
[go python java rust javascript]

\s+ cocok dengan satu atau lebih karakter whitespace (spasi, tab, newline). Ini sangat berguna untuk mem-parsing input pengguna yang tidak konsisten dalam penggunaan spasi.

Capture Groups

Salah satu fitur paling powerful dari regex adalah kemampuan untuk mengekstrak bagian-bagian tertentu dari string yang cocok menggunakan capture group (tanda kurung dalam pola).

// catatan.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // ekstrak komponen tanggal: tahun, bulan, hari
    polaTanggal := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)

    jadwal := "Deadline proyek: 2026-06-15, review: 2026-07-01"

    // FindStringSubmatch: [kecocokan_penuh, group1, group2, group3]
    komponen := polaTanggal.FindStringSubmatch(jadwal)
    if komponen != nil {
        fmt.Println("tanggal lengkap:", komponen[0])
        fmt.Println("tahun:", komponen[1])
        fmt.Println("bulan:", komponen[2])
        fmt.Println("hari:", komponen[3])
    }

    // FindAllStringSubmatch: semua kecocokan dengan semua group-nya
    semua := polaTanggal.FindAllStringSubmatch(jadwal, -1)
    for _, m := range semua {
        fmt.Printf("%s/%s/%s\n", m[3], m[2], m[1])
    }
}
tanggal lengkap: 2026-06-15
tahun: 2026
bulan: 06
hari: 15
15/06/2026
01/07/2026

FindStringSubmatch mengembalikan slice di mana indeks 0 adalah kecocokan penuh, dan indeks berikutnya adalah isi masing-masing capture group. Ini sangat berguna untuk mem-parsing format terstruktur seperti tanggal, URL, atau log.

Untuk pola yang digunakan berkali-kali, kompilasi sekali di luar fungsi (misalnya sebagai variabel paket level) menggunakan regexp.MustCompile. Mengkompilasi ulang setiap pemanggilan fungsi adalah pemborosan yang tidak perlu.

Studi Kasus: Validator Format Kode Referensi

Menggabungkan teknik-teknik di atas untuk memvalidasi dan mengekstrak informasi dari kode referensi internal.

// catatan.go
package main

import (
    "fmt"
    "regexp"
    "strings"
)

// format kode referensi: DIV-YYYY-NNN
// contoh: OPS-2026-001, FIN-2026-042, IT-2026-100
var polaKodeRef = regexp.MustCompile(`^([A-Z]{2,4})-(\d{4})-(\d{3})$`)

type KodeReferensi struct {
    Divisi string
    Tahun  string
    Urutan string
    Kode   string
}

func parseKodeRef(input string) (KodeReferensi, bool) {
    bersih := strings.TrimSpace(input)
    match := polaKodeRef.FindStringSubmatch(bersih)
    if match == nil {
        return KodeReferensi{}, false
    }
    return KodeReferensi{
        Divisi: match[1],
        Tahun:  match[2],
        Urutan: match[3],
        Kode:   match[0],
    }, true
}

func main() {
    sampel := []string{
        "OPS-2026-001",
        "FIN-2026-042",
        "IT-2026-100",
        "INVALID",
        "ops-2026-001",   // lowercase — tidak valid
        "ABCDE-2026-001", // divisi terlalu panjang
        "  HR-2026-007 ", // spasi di tepi — dimaafkan
    }

    for _, s := range sampel {
        ref, ok := parseKodeRef(s)
        if !ok {
            fmt.Printf("%-20q tidak valid\n", s)
            continue
        }
        fmt.Printf("%-20q => divisi: %-5s tahun: %s urutan: %s\n",
            s, ref.Divisi, ref.Tahun, ref.Urutan)
    }
}
"OPS-2026-001"       => divisi: OPS   tahun: 2026 urutan: 001
"FIN-2026-042"       => divisi: FIN   tahun: 2026 urutan: 042
"IT-2026-100"        => divisi: IT    tahun: 2026 urutan: 100
"INVALID"            tidak valid
"ops-2026-001"       tidak valid
"ABCDE-2026-001"     tidak valid
"  HR-2026-007 "     => divisi: HR    tahun: 2026 urutan: 007

^ dan $ di awal dan akhir pola memastikan seluruh string harus cocok — tanpa keduanya, pola akan tetap cocok meski ada karakter tambahan di sekitarnya.

Go mengimplementasikan RE2, bukan PCRE. Beberapa fitur regex yang umum di bahasa lain tidak tersedia di Go: tidak ada lookahead/lookbehind, tidak ada backreference (\1), dan tidak ada atomic group. Jika kamu migrasi regex dari JavaScript atau Python, periksa kompatibilitas polamu di regex101.com dengan mode “Golang”.

Latihan

Latihan 1 — Validasi format email sederhana: Tulis fungsi validasiEmail(email string) bool yang mengembalikan true jika email mengikuti format sederhana: nama@domain.ekstensi (nama dan domain hanya boleh berisi huruf, angka, titik, atau underscore; ekstensi 2–4 huruf).

Latihan 2 — Ekstraksi hashtag: Buat fungsi ekstrakHashtag(teks string) []string yang mengembalikan semua hashtag (kata diawali # tanpa spasi) dari sebuah teks. Misalnya, dari "Update #golang dan #backend sudah selesai #2026" harus mengembalikan ["#golang", "#backend", "#2026"].

Latihan 3 — Sensor data sensitif: Buat fungsi sensorLog(baris string) string yang mengganti semua kemunculan nomor NIK (16 digit berurutan) dalam baris log dengan [NIK_DISENSOR].


Dengan package regexp, kamu kini punya alat yang jauh lebih ekspresif untuk bekerja dengan teks. Pola yang sebelumnya butuh puluhan baris kode pencocokan manual bisa diselesaikan dengan satu ekspresi. Regex dan package strings adalah dua sisi dari koin yang sama: gunakan strings untuk operasi yang polanya tetap dan sederhana, gunakan regexp ketika pola perlu dideskripsikan secara abstrak dan fleksibel.

Sejauh ini kita bekerja dengan data teks dalam bentuk yang masih bisa dibaca manusia. Tapi ada situasi di mana data perlu diubah ke representasi yang berbeda sebelum dikirim — misalnya saat mengirim data biner melalui protokol yang hanya mendukung teks, atau menyimpan konten gambar ke dalam JSON. Di sinilah encoding Base64 diperlukan, dan itu topik yang akan kita bahas selanjutnya.

Referensi

  1. 1Package regexp — Go Standard Library Documentation
  2. 2RE2 Syntax Reference — Google
  3. 3Regular Expressions — Go by Example