arizonagunlist.com – Anda punya 50.000 baris log server dan perlu mengambil semua alamat IP yang mengakses satu halaman. Atau kolom nomor telepon di spreadsheet ditulis dalam lima format berbeda. Mencari dengan Ctrl+F tidak akan cukup, karena yang Anda cari bukan satu kata tetap, melainkan bentuk teks: empat angka, tanda hubung, dua angka.
Regular expression (regex) adalah bahasa kecil untuk mendeskripsikan bentuk teks. Sekali paham dasarnya, satu baris pola bisa menggantikan puluhan baris kode manual. Artikel ini membahas blok bangunan regex, contoh yang sudah diuji, jebakan yang paling sering memakan korban, dan kapan sebaiknya Anda tidak memakai regex sama sekali.
Semua contoh kode di bawah dijalankan dengan Python 3.13 dan pandas 3.0.5. Sintaks regex berbeda sedikit antar bahasa dan pustaka, jadi saya tandai bagian yang spesifik Python.
Cara kerja regex dalam satu paragraf
Anda menulis sebuah pola, lalu mesin regex mencoba mencocokkannya dengan teks dari kiri ke kanan. Pola terdiri dari karakter biasa (cocok dengan dirinya sendiri) dan karakter khusus yang punya arti (seperti ., +, ^). Mesin yang dipakai Python, JavaScript, dan banyak bahasa lain bersifat backtracking: kalau satu jalur gagal, ia mundur dan mencoba jalur lain. Detail ini terlihat membosankan sekarang, tetapi menjelaskan dua perilaku penting nanti: kenapa regex bisa “rakus” dan kenapa sebagian pola bisa membuat program macet.
Blok bangunan dasar
Tabel ini merangkum sintaks inti. Sumber utama saya adalah dokumentasi modul re Python dan referensi regex MDN untuk JavaScript.
| Elemen | Arti | Contoh |
|---|---|---|
. |
Satu karakter apa saja, kecuali baris baru | a.c cocok dengan abc |
\d \w \s |
Angka, karakter kata, spasi | \d\d cocok dengan 42 |
\D \W \S |
Kebalikannya | |
[abc] |
Salah satu karakter dalam himpunan | [aeiou] |
[^abc] |
Karakter apa pun selain himpunan | [^"] |
^ $ |
Awal dan akhir teks (atau baris, dengan mode multiline) | ^Rp |
\b |
Batas kata | \bat\b tidak cocok dengan attempt |
A|B |
Alternatif: A atau B | GET|POST |
(...) |
Grup yang menangkap | (\d+)-(\d+) |
(?:...) |
Grup yang tidak menangkap | (?:\.\d{3})* |
* + ? |
0 atau lebih, 1 atau lebih, 0 atau 1 | \d+ |
{m,n} |
Antara m dan n kali | \d{2,4} |
Dokumentasi Python mencatat satu hal yang sering terlewat: di dalam kurung siku [...], sebagian karakter khusus kehilangan artinya, dan \b di dalam himpunan berarti backspace, bukan batas kata.
Selalu tulis pola sebagai raw string
Di Python, awali string pola dengan r, misalnya r"\d+". Dokumentasi menjelaskan bahwa tanpa itu, backslash harus digandakan, dan escape yang tidak valid dalam string biasa kini menghasilkan SyntaxWarning yang rencananya menjadi SyntaxError.
Cocokkan atau cari? Tiga fungsi yang sering tertukar
Ini sumber bug nomor satu bagi pemula. Dengan teks abc123 dan pola \d+:
import re
s = "abc123"
re.match(r"\d+", s) # None: match() hanya mencoba dari AWAL teks
re.search(r"\d+", s) # cocok "123": search() mencari di mana saja
re.fullmatch(r"\d+", s) # None: seluruh teks harus cocok
Hasil uji saya: False, True, False. Aturan praktis: untuk validasi pakai fullmatch; untuk mengambil potongan dari teks yang lebih panjang pakai search atau findall.
Ada perangkap terkait $. Menurut dokumentasi Python, $ cocok di akhir teks atau tepat sebelum baris baru di akhir. Saya uji pola ^\d{4}-\d{2}-\d{2}$ pada "2026-11-02\n": search mengembalikan cocok, fullmatch tidak. Data dari file sering membawa \n tersembunyi, jadi fullmatch lebih aman untuk validasi.
Rakus (greedy) versus malas (lazy)
Kuantifier secara bawaan rakus: ia mengambil sebanyak mungkin, lalu mundur jika perlu. Tutorial pythontutorial.net menunjukkan contoh klasik, dan saya jalankan ulang:
h = '<button type="submit" class="btn">Send</button>'
re.findall(r'".+"', h) # ['"submit" class="btn"'] (satu hasil panjang)
re.findall(r'".+?"', h) # ['"submit"', '"btn"'] (dua hasil, malas)
re.findall(r'"[^"]*"', h) # ['"submit"', '"btn"'] (himpunan negatif)
Menambahkan ? setelah kuantifier membuatnya malas (cocok sesedikit mungkin). Dokumentasi Python juga mencatat versi possessive (*+, ++) sejak Python 3.11, yang tidak pernah mundur.
Menurut saya, himpunan negatif seperti [^"]* sering lebih baik daripada .+?. Ia menyatakan maksud dengan jelas (“segala hal kecuali tanda kutip”) dan menghindari satu kelas masalah performa yang saya bahas di bagian jebakan. Ini preferensi penulis, tetapi situs regular-expressions.info menyarankan prinsip serupa: pastikan hanya ada satu cara untuk mencocokkan teks yang sama.
Grup, nama grup, dan penggantian
Kurung membuat grup yang menangkap potongan cocok. Nama grup membuat hasil mudah dibaca. Contoh mengurai baris log:
log = '192.168.1.10 - - [02/Nov/2026:19:20:01 +0700] "GET /artikel/regex HTTP/1.1" 200 5123'
pat = re.compile(
r'(?P<ip>\d{1,3}(?:\.\d{1,3}){3}) .*?\[(?P<tgl>[^\]]+)\] '
r'"(?P<metode>GET|POST) (?P<path>\S+) [^"]*" (?P<status>\d{3})'
)
pat.search(log).groupdict()
# {'ip': '192.168.1.10', 'tgl': '02/Nov/2026:19:20:01 +0700',
# 'metode': 'GET', 'path': '/artikel/regex', 'status': '200'}
Catatan: sintaks (?P<nama>...) khas Python; JavaScript memakai (?<nama>...). Itu bagian dari perbedaan antar mesin.
Untuk mengganti teks dengan referensi ke grup:
re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\3-\2-\1', 'Tgl 02/11/2026 dan 15/01/2027')
# 'Tgl 2026-11-02 dan 2027-01-15'
Satu jebakan: findall berperilaku berbeda tergantung ada tidaknya grup. Dengan grup menangkap, hasilnya daftar tuple:
re.findall(r'(\w+)=(\d+)', 'width=20 height=10') # [('width','20'), ('height','10')]
re.findall(r'(?:\w+)=(?:\d+)', 'width=20 height=10') # ['width=20', 'height=10']
Jika Anda hanya butuh pengelompokan tanpa penangkapan, pakai (?:...).
Lookahead dan lookbehind: mencocokkan konteks tanpa ikut mengambilnya
Ini disebut assertion di MDN: mereka memeriksa posisi tanpa menghabiskan karakter.
| Sintaks | Arti |
|---|---|
(?=...) |
Diikuti oleh … |
(?!...) |
Tidak diikuti oleh … |
(?<=...) |
Didahului oleh … |
(?<!...) |
Tidak didahului oleh … |
txt = 'Harga Rp1.250.000 dan Rp75.000, diskon 10%'
re.findall(r'(?<=Rp)\d+(?:\.\d{3})*', txt) # ['1.250.000', '75.000']
re.findall(r'\d+(?=%)', txt) # ['10']
Dokumentasi Python mencatat lookbehind harus berlebar tetap. Saya coba (?<=Rp\s*)\d+ dan Python memang menolak dengan pesan look-behind requires fixed-width pattern. Mesin JavaScript modern lebih longgar dalam hal ini, jadi jangan menganggap pola yang jalan di satu bahasa otomatis jalan di bahasa lain.
Flag yang mengubah perilaku
| Flag Python | Efek |
|---|---|
re.I |
Tidak peka huruf besar-kecil |
re.M |
^ dan $ cocok di tiap baris |
re.S |
. juga cocok dengan baris baru |
re.X |
Abaikan spasi dan komentar dalam pola (pola jadi terbaca) |
re.A |
\w, \d, \s, \b hanya ASCII |
Uji singkat saya: pola baris1.baris2 pada "baris1\nbaris2" tidak cocok tanpa re.S, dan ^b\w+ hanya mengambil baris pertama tanpa re.M. Di JavaScript flag-nya berbeda hurufnya (i, m, s, g, u, v, y, d).
Flag re.X layak dibiasakan untuk pola panjang:
email = re.compile(r'''
^(?P<user>[\w.+-]+) # nama pengguna
@
(?P<domain>[\w-]+(?:\.[\w-]+)+)$ # domain dengan titik
''', re.X)
Jebakan Unicode: \d tidak selalu berarti 0–9
Di Python 3, untuk string str, \d mencocokkan digit Unicode, bukan hanya 0–9. Saya uji dengan '٣٤٥' (angka Arab-Indik): re.fullmatch(r'\d+', ...) cocok dan int() mengubahnya menjadi 345, sedangkan dengan re.A tidak cocok. Untuk data yang dimasukkan pengguna, gunakan re.A atau [0-9] bila Anda hanya ingin digit ASCII.
Regex untuk analisis data dengan pandas
Metode str.extract pada pandas mengambil grup dari kecocokan pertama di tiap string. Dokumentasi pandas menjelaskan nama grup menjadi nama kolom, dan string tanpa kecocokan menghasilkan NaN. Contoh yang saya jalankan:
import pandas as pd
s = pd.Series(["INV-2026-0012 Rp1.250.000", "INV-2026-0013 Rp75.000", "tanpa nomor"])
s.str.extract(r'INV-(?P<tahun>\d{4})-(?P<urut>\d+)')
# tahun urut
# 0 2026 0012
# 1 2026 0013
# 2 NaN NaN
Untuk semua kecocokan per baris, dokumentasi menyebut extractall. Untuk filter, s.str.contains(r'^INV-\d{4}-\d+', regex=True) menghasilkan [True, True, False]. Perhatikan: kolom urut tetap berupa teks ('0012'), jadi konversi tipe tetap tugas Anda.
Contoh praktis: nomor telepon dan email
Dua pola ini paling sering dicari dan paling sering disalahpahami.
Nomor seluler Indonesia (pola buatan saya, bukan standar resmi):
hp = re.compile(r'^(?:\+62|62|0)8\d{8,11}$')
Uji: 081234567890 dan +6281234567890 cocok; 0212345678 (nomor telepon rumah), 08123, dan 6281234567 (terlalu pendek) tidak. Pola ini hanya mengecek bentuk, bukan apakah nomor itu aktif atau sesuai alokasi operator. Rentang panjang 8\d{8,11} adalah asumsi saya; sesuaikan bila data Anda menyimpang.
Email: artikel OpenReplay (Juni 2026) menegaskan regex hanya menyelesaikan satu dari tiga persoalan validasi email, yaitu sintaks, dan itu pun tidak sempurna. Pola yang terlalu ketat menolak alamat valid (misalnya yang memakai tanda plus), pola yang longgar meloloskan alamat yang tidak bisa dikirimi surat. Rekomendasinya: pakai pola permisif sebagai pemeriksa salah ketik, andalkan <input type="email"> untuk sintaks di browser, dan buktikan kepemilikan alamat dengan email konfirmasi. Pola re.X di atas lolos untuk a.b+c@contoh.co.id dan menolak salah@ dan x@y dalam uji saya, tetapi jangan jadikan itu satu-satunya penjaga.
Jebakan performa: catastrophic backtracking dan ReDoS
OWASP menjelaskan Regular expression Denial of Service (ReDoS): pola tertentu melambat secara eksponensial pada input tertentu, sehingga satu input buatan bisa membuat program menggantung. Contoh pola “jahat” di halaman OWASP: (a+)+$, ([a-zA-Z]+)*$, (a|aa)+$, dan (a|a?)+$, dengan input seperti deretan a diikuti karakter yang membuat kecocokan gagal. Regular-expressions.info menjelaskan penyebabnya: kuantifier bersarang atau tumpang tindih membuat teks yang sama bisa dicocokkan dengan banyak cara, dan saat gagal mesin mencoba semuanya.
Saya mengukurnya sendiri di Python 3.13 dengan re.match(r'^(a+)+$', 'a'*n + '!'):
Jumlah a |
Pola jahat ^(a+)+$ |
Pola aman ^a+$ |
|---|---|---|
| 16 | 0,0032 detik | 0,0001 detik |
| 18 | 0,0103 detik | 0,000008 detik |
| 20 | 0,0472 detik | 0,00002 detik |
| 22 | 0,1816 detik | 0,00002 detik |
Waktunya sekitar empat kali lipat setiap tambah dua huruf, yaitu dua kali lipat per huruf. Ekstrapolasi sederhana dari pola ini menunjukkan input puluhan karakter bisa memakan waktu sangat lama, tetapi angka itu hitungan kasar saya, bukan hasil ukur. Pengukuran waktu juga bergantung pada mesin dan versi Python.
Cara menghindarinya:
- Spesifik. Regular-expressions.info mencontohkan menulis ulang
(x+x+)+ymenjadixx+y, dan untuk CSV memakai[^,\r\n]*bukan.*?. - Hindari kuantifier bersarang pada hal yang bisa saling tumpang tindih.
- Gunakan possessive quantifier atau atomic group bila mesin Anda mendukung. Di Python 3.11+, saya uji
^(?:a++)+$pada 40 hurufaplus!, dan selesai dalam sekitar 0,0001 detik. - Jangan biarkan input pengguna menjadi pola regex. Peringatan ini muncul di OWASP dan regular-expressions.info. Kalau Anda perlu mencari teks literal dari pengguna, gunakan
re.escape. Saya ujire.escape('1.000'): ia hanya cocok dengan1.000, bukan1x000. - Batasi panjang input sebelum menjalankan regex. Ini saran pertahanan berlapis dari saya, bukan kutipan sumber tertentu.
Kapan sebaiknya tidak memakai regex
Regex hebat untuk bentuk teks yang teratur. Ia buruk untuk struktur bersarang. Dua contoh: HTML dan JSON sebaiknya diurai dengan pengurai khusus, bukan regex. Menurut saya ini praktik umum, dan contoh <.*> pada <a><b> di atas memperlihatkan betapa mudahnya pola salah mengambil terlalu banyak. Begitu pula tanggal: ^\d{4}-\d{2}-\d{2}$ meloloskan 2026-13-45 dalam uji saya, karena regex mengecek bentuk, bukan kalender. Untuk validasi tanggal, gunakan pustaka tanggal.
Kesalahan yang sering terjadi
| Kesalahan | Akibat | Perbaikan |
|---|---|---|
Memakai match padahal maksudnya search |
Hasil None yang membingungkan |
Pahami ketiga fungsi |
Validasi dengan $ tanpa fullmatch |
Lolos dengan \n di ujung |
Pakai fullmatch |
| Tidak memakai raw string | Backslash hilang, peringatan | Awali dengan r |
.* yang terlalu rakus |
Mengambil lebih dari yang dimaksud | [^...]* atau kuantifier malas |
| Lupa meloloskan karakter khusus dari input literal | 1.000 cocok dengan 1x000 |
re.escape |
| Kuantifier bersarang | Pola macet pada input tertentu | Tulis ulang spesifik, atau possessive |
Menganggap \d hanya 0–9 |
Angka Unicode ikut lolos | re.A atau [0-9] |
| Mengandalkan regex untuk validasi email atau tanggal | Menolak yang valid, meloloskan yang salah | Pola longgar plus verifikasi lain |
| Menyalin pola antar bahasa tanpa uji | Beda sintaks, beda perilaku | Uji di mesin target |
| Pola panjang tanpa komentar | Tak terbaca | Pakai re.X |
Pertanyaan yang sering diajukan
Apakah regex sama di semua bahasa?
Tidak persis. Inti sintaksnya mirip, tetapi nama grup ((?P<n>) vs (?<n>)), batas lookbehind, flag, dan fitur seperti possessive berbeda. Uji pola di bahasa target.
Bagaimana cara belajar paling cepat?
Mulai dari literal, kelas karakter, dan kuantifier, lalu grup. Uji tiap pola di REPL atau alat uji regex dengan beberapa contoh yang cocok dan tidak cocok. Itu saran saya, bukan hasil studi.
Kenapa pola saya cocok di alat uji tapi gagal di kode?
Penyebab umum: tidak memakai raw string, perbedaan mesin atau flag, atau match vs search.
Apakah regex lambat?
Biasanya cepat untuk pola yang baik. Masalahnya muncul pada pola bersarang yang rawan backtracking berlebihan, seperti ditunjukkan tabel di atas.
Bisakah regex menggantikan semua pengurai teks?
Tidak. Gunakan pengurai khusus untuk format bersarang seperti HTML dan JSON.
Kesimpulan
Regex paling berguna bila Anda memperlakukannya sebagai alat mendeskripsikan bentuk teks, bukan mantra yang ditempel dari internet. Kuasai lima hal: kelas karakter, kuantifier, jangkar, grup, dan perbedaan match, search, dan fullmatch. Tulis pola dengan raw string, uji dengan contoh yang cocok dan yang tidak cocok, dan waspadai kuantifier bersarang. Lalu pilih alat yang tepat: regex untuk bentuk teks teratur, pustaka khusus untuk tanggal, email, HTML, dan JSON.
Catatan keamanan: jika aplikasi Anda menerima teks dari luar, perlakukan pola regex sebagai bagian dari permukaan serangan (ReDoS). Uji performa pola Anda pada input terburuk sebelum rilis.
Bahan riset
- Dokumentasi Python, modul
re(sumber teks rst) - MDN, referensi Regular expressions (cermin cs.unb.ca, 2023)
- pandas,
Series.str.extract - OWASP, Regular expression Denial of Service (ReDoS)
- Regular-Expressions.info, catastrophic backtracking
- PythonTutorial.net, Python Regex Greedy
- OpenReplay, email validation regex (2026)
Catatan jujur dari Bright
- Dokumentasi resmi tidak terbuka langsung. Halaman docs.python.org (HOWTO dan referensi
re) dan developer.mozilla.org ditolak oleh daftar izin sesi ini. Saya memakai versi sumber teks rst milik Python yang muncul di hasil pencarian, dan cermin MDN dari situs mata kuliah universitas (diedit terakhir 27 Oktober 2023, tidak berafiliasi dengan MDN). Isinya mungkin tidak mutakhir persis, dan saya tidak membuka Python HOWTO resmi. - Dokumentasi Python tidak sepenuhnya sempurna. Hasil bacaan sumber rst menandai kemungkinan kesalahan kecil pada contoh
NOFLAG(urutan argumen terbalik). Saya tidak memakai contoh itu. - Sebagian sumber punya kepentingan. Regular-expressions.info ditulis satu orang (Jan Goyvaerts) yang menjual RegexBuddy, dan angka langkah di sana berasal dari alat itu. Saya hanya memakai prinsipnya, bukan angka langkahnya. Halaman OWASP lama dan tidak punya bagian mitigasi khusus; saran mitigasi di artikel ini berasal dari regular-expressions.info dan pengujian saya sendiri. OpenReplay adalah blog vendor produk observabilitas.
- Tutorial greedy dari pythontutorial.net adalah situs tutorial komersial, dipakai hanya sebagai ilustrasi yang saya ulang uji sendiri.
- Semua contoh kode saya jalankan di Python 3.13.16 dan pandas 3.0.5, dan hasil yang ditulis di sini adalah hasil nyata. Yang tidak saya uji: perilaku di JavaScript, PCRE, atau mesin lain. Pernyataan perbedaan antar bahasa berasal dari dokumentasi (nama grup, lookbehind, flag) dan bukan dari uji langsung.
- Waktu eksekusi ReDoS diukur sekali pada mesin sesi ini dan bisa berbeda di tempat lain. Kalimat bahwa input puluhan karakter bisa sangat lama adalah ekstrapolasi saya, tidak diukur.
- Pola nomor HP (
^(?:\+62|62|0)8\d{8,11}$) buatan saya. Saya tidak memverifikasinya terhadap aturan penomoran resmi operator, sehingga panjang digit adalah asumsi. - Yang murni pendapat saya: preferensi
[^"]*dibanding.+?, anjuran memakaifullmatchuntuk validasi, saran membatasi panjang input, dan peringatan agar tidak memakai regex untuk HTML dan JSON. Pernyataan terakhir ini tidak saya dukung dengan sumber yang saya buka. - Tidak saya telusuri: performa regex di mesin non-backtracking (seperti RE2), fitur Unicode lanjutan (
\p{...}secara rinci), dan pustakaregexpihak ketiga. Beberapa hasil pencarian (kumpulan cheatsheet, Scribd, blog dev.to) tidak dibuka.

Leave A Comment