3 poin oleh GN⁺ 2024-03-21 | 1 komentar | Bagikan ke WhatsApp
  • $ pada Python re dapat cocok tidak hanya dengan akhir string, tetapi juga dengan posisi tepat sebelum newline terakhir di akhir string, meskipun mode multiline dimatikan
  • Jangan berasumsi bahwa karena ^ tampak berarti “awal string”, maka $ juga bekerja sepenuhnya simetris; makna sebenarnya berbeda-beda bergantung pada implementasi ekspresi reguler
  • Hasil $, \z, dan \Z terhadap "cat\n" berbeda antara PHP, ECMAScript, Python, Go, Java 8, .NET 7.0, dan Rust; \z di Python baru ditambahkan pada Python 3.14
  • Jika newline di akhir diperbolehkan, $ dalam mode multiline cocok dengan "cat\n" di semua platform dalam tabel, tetapi jika ingin mencocokkan hanya akhir tanpa newline, pilihan sintaksnya berbeda
  • Jika tidak boleh mencocokkan newline terakhir, pada sebagian besar platform gunakan \z, sedangkan sebelum Python 3.14 dan di ECMAScript perlu mempertimbangkan alternatif masing-masing

Posisi yang dicocokkan $ di Python re

  • Dalam modul ekspresi reguler Python re, $ dapat cocok dengan akhir string atau tepat sebelum newline terakhir di akhir string, meskipun mode multiline dimatikan
  • cat$ tampak sederhana karena cocok dengan "lolcat" dan tidak cocok dengan "internet cat video", tetapi hasilnya bisa berbeda dari dugaan jika ada newline di akhir seperti "cat\n"
  • Jika re.MULTILINE ditentukan, $ cocok dengan akhir string dan akhir setiap baris, yaitu tepat sebelum setiap newline
  • Pada nilai default pun, $ cocok dengan akhir string, dan jika ada newline di akhir string, juga cocok tepat sebelum newline tersebut

Mencocokkan dengan mengecualikan newline terakhir

  • Untuk mencocokkan akhir string secara ketat, $ saja bisa tidak cukup, dan \z serta \Z menjadi kandidat anchor akhir
  • Berdasarkan dokumentasi ekspresi reguler Python dan penjelasan sintaks ekspresi reguler lainnya, dukungan dan makna \z serta \Z berbeda-beda menurut implementasi
  • Perbedaannya untuk "cat\n" adalah sebagai berikut
    • PHP: "cat$" cocok terlepas dari apakah multiline aktif atau tidak, "cat\z" tidak cocok, dan "cat\Z" cocok
    • ECMAScript: "cat$" dalam mode multiline cocok, "cat$" tanpa multiline tidak cocok, dan \z serta \Z tidak didukung
    • Python: "cat$" cocok terlepas dari apakah multiline aktif atau tidak, sedangkan "cat\z" dan "cat\Z" tidak cocok dengan "cat\n"
    • Go dan Rust: "cat$" dalam mode multiline cocok, "cat$" tanpa multiline dan "cat\z" tidak cocok, sedangkan \Z tidak didukung
    • Java 8 dan .NET 7.0: "cat$" cocok terlepas dari apakah multiline aktif atau tidak, "cat\z" tidak cocok, dan "cat\Z" cocok
  • \z di Python baru ditambahkan pada Python 3.14, dan tidak didukung pada versi sebelumnya
  • Jika newline di akhir diperbolehkan, $ dalam mode multiline secara konsisten cocok dengan "cat\n" di semua platform dalam tabel
  • Jika tidak ingin mencocokkan newline di akhir, pada sebagian besar platform gunakan \z; sebelum Python 3.14 gunakan \Z, sedangkan di ECMAScript gunakan $ tanpa multiline
  • Data dalam tabel dikumpulkan dari regex101.com, bukan diuji dengan runtime sebenarnya

1 komentar

 
GN⁺ 2024-03-21
Komentar Hacker News
  • Sejak dulu saya menganggap ^ sebagai “awal baris” dan $ sebagai “akhir baris”
    Saat menangani regex, teks sering diproses per baris sehingga hasilnya sering sama, tetapi cara saya membayangkan operator itu tetap lebih dekat ke “baris” daripada “string”
    Mungkin karena saya pertama mengenal regex lewat grep, jadi terbiasa melihat input sebagai baris, bukan string

    • Saya juga melihat judulnya dan berpikir, “Ya jelas bukan, dari mana dengar begitu?”
      Saya sudah memakai regex hampir 20 tahun, tapi rasanya baru kali ini mendengar bahwa $ adalah akhir string; saya selalu menganggapnya akhir baris
    • Saya agak terganggu ketika artikel menyebut ^ sebagai “awal string”
      Sebenarnya, seperti $ adalah “akhir baris”, ^ juga “awal baris”, dan awal string tampaknya lebih dekat ke \A, sedangkan akhir string ke \Z
    • Saya juga berpikir begitu, tetapi setelah mencobanya langsung di Perl, $ secara default berperilaku seperti positive lookahead assertion untuk akhir string
      Ia tidak mencocokkan dan mengonsumsi karakter newline
      Hanya dalam mode multi-baris ia cocok dengan posisi newline, tetapi bahkan saat itu tampaknya tetap tidak mengonsumsinya
      Nyatanya, dengan memakai $, saya tidak bisa membuat regex yang menangkap karakter terakhir sebuah baris, mengonsumsi newline, lalu menangkap karakter pertama baris berikutnya; grup tangkapnya hanya berakhir di $
    • Buat saya, bukan grep, melainkan Vim yang menanamkan persepsi itu
  • Regex POSIX dan regex Python berbeda
    Secara umum, sintaks regex tidak universal, jadi kita harus melihat dokumentasi implementasi yang dipakai
    Menurut POSIX Bab 9, regex bekerja di atas string, tetapi sebagian utilitas membatasi pemrosesan per baris
    Selain itu, $ disebut sebagai anchor yang dipatok ke akhir string target pencocokan, jadi pada akhirnya apakah $ berarti akhir string atau akhir baris ditentukan oleh utilitas atau mode
    Alat umum seperti grep, sed, awk, dan Python pada dasarnya bekerja per baris, sehingga biasanya memperlakukannya sebagai akhir baris
    Tidak ada satu sintaks regex universal
    Tanpa mengetahui bahasa dan opsi yang digunakan, kita tidak bisa membaca atau menulis regex dengan andal
    https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...

  • Topik ini pas sekali untuk memperkenalkan Robert Elder kepada yang belum mengenalnya
    Ia membuat konten bagus di YouTube dan blog, dan dalam seri regex-nya ia menggali cukup dalam perbedaan perilaku regex yang diimplementasikan berbagai alat
    Video terbarunya juga bagus: https://www.youtube.com/watch?v=ys7yUyyQA-Y
    Ada banyak konten yang mungkin menarik bagi pembaca HN, termasuk topik seperti realitas dan kesulitan dunia konsultasi
    https://www.youtube.com/@RobertElderSoftware
    https://blog.robertelder.org/
    https://blog.robertelder.org/regular-expressions/
    https://www.youtube.com/watch?v=cK87ktENPrI

  • Saat belajar Perl, regex adalah salah satu hal pertama yang benar-benar meresap ke tubuh saya, dan sampai sekarang Perl masih terasa nyaman di sudut hati saya berkat buku “Camel”
    Pengetahuan terpenting sekarang adalah bahwa setiap implementasi berbeda, jadi saya jadi terbiasa membuka tabel referensi terkait setiap kali mengerjakan sesuatu
    Misalnya, regex Emacs tidak mendukung karakter kata berbentuk \w dan harus memakai kelas karakter mirip \s_-, yang menyebalkan, tetapi menurut saya Emacs unggul dalam dokumentasi dan kemudahan ditemukan
    Ada utilitas yang mengharuskan escape tanda kurung dan ada yang tidak, dan perilaku ini kadang bisa dikonfigurasi, kadang tidak
    Setelah melewati semua tahap bingung, kesal, dan penyangkalan, sekarang saya menerimanya saja
    Konsepnya sama di mana-mana, tetapi dialeknya berbeda

    • Kepala saya berpikir dalam regex Perl, lalu menerjemahkannya agar sesuai dengan bagian-bagian tidak konsisten dari bahasa yang sedang saya pakai
      Khususnya di shell, daripada memikirkan apakah sed/grep/awk itu GNU atau BSD, jauh lebih sering saya menyelipkan perl ke dalam pipeline
    • Saya penasaran bagaimana bisa sampai meresap begitu
      Perl terlihat seperti kucing menginjak keyboard
  • Rasanya terdengar suara banyak manajer perekrutan yang buruk menambahkan “bagaimana mencocokkan akhir string dalam regex?” ke daftar pertanyaan jebakan mereka

  • Aneh rasanya mengeluarkan Perl dari daftar terkait regex
    Dalam dokumentasi perlre, $ dijelaskan begini: cocok dengan akhir string, atau cocok sebelum newline di akhir string, atau jika memakai /m, cocok sebelum newline mana pun

    • Melewatkan Perl, bahasa yang bisa dibilang paling kuat keterkaitannya dengan regex, tampak seperti kelalaian yang cukup besar
      Mungkin itu juga menunjukkan betapa Perl belakangan ini sudah terdorong keluar dari perhatian
  • Raku, yang dulu dikenal sebagai Perl 6, menetapkan ^ dan $ sebagai awal/akhir string, lalu memperkenalkan ^^ dan $$ sebagai awal/akhir baris
    Tidak ada mode multibaris, dan memang tidak diperlukan
    \h juga berarti spasi horizontal, sedangkan \v berarti spasi vertikal
    Karena semuanya dipikirkan ulang dan ditulis ulang sepenuhnya, ada keuntungan bisa belajar dari fakta bahwa perilaku lama mengejutkan banyak orang

    • Jadi orang keras kepala seperti saya tidak bisa memakai Perl 6
      Rasanya seperti sintaks line noise yang sudah dipelajari selama puluhan tahun dicampur secara acak
      Akan lebih jelas kalau default-nya justru kebalikan
      Rasanya lebih alami jika ^ dan $ dipakai untuk baris, sedangkan ^^ dan $$ untuk string
      Karena tampilannya seperti ^^line1$\n^line2$\n^line3$\n$
      Selain itu, Perl 6 tidak ada di mana-mana, tetapi Perl 5 ada di mana-mana
    • Kalau saya, sepertinya saya akan memilih persis kebalikannya
      ^^ terlihat lebih “seperti awal” daripada ^
    • Hampir semua regex yang saya tulis berasumsi pada awal/akhir string
      Karena biasanya baris dimasukkan ke regex untuk diproses, pilihan memakai ^ dan $ tunggal untuk seluruh string mempertahankan kompatibilitas mundur sampai batas tertentu
  • Saya bertanya-tanya apakah ada orang yang menganggap regex sudah distandarkan
    Setiap kali pindah ke lingkungan baru, saya selalu harus mempelajarinya lagi

    • Pada suatu titik saya merasa mengetahui semua dialeknya
      Mungkin ada lebih banyak dialek regex, tetapi saya tidak menemuinya, dan dengan yang saya tahu sebagian besar masalah bisa diselesaikan
      Mirip seperti mengemudikan mobil sewaan
      Gerakannya sedikit berbeda dari mobil sendiri, ada fitur yang hilang dan ada fitur tambahan, tetapi secara keseluruhan kebanyakan cukup mirip
    • Pustaka standar ISO/IEC 14882 C++ mewajibkan implementasi enam sintaks regex yang secara de facto merupakan standar resmi: IEEE Std 1003.1-2008, yaitu BRE, ERE, awk, grep, egrep dari POSIX, serta ECMA-262 EcmaScript 3
      Jadi setidaknya menurut saya regex telah distandarkan dalam beberapa standar resmi yang dipublikasikan
      https://open-std.org/jtc1/sc22/…
      https://pubs.opengroup.org/onlinepubs/9699919799/…
      https://262.ecma-international.org/14.0/…
    • Cabang besar yang saya tahu kira-kira POSIX, Perl/PCRE, dan RE2 yang dipakai di ekosistem Go
      Banyak sistem, termasuk JavaScript, mengimplementasikan PCRE, karena Perl menambahkan banyak ekstensi berguna ke skema POSIX
      Seingat saya, RE2 lebih berusaha menekan masalah performa dan perilaku khas dari skema yang ada, dan saya kira semuanya diimplementasikan dengan Go
      Belakangan saya baru tahu bahwa RE2 muncul lebih dulu daripada Go
    • Bahasa-bahasa yang muncul setelah Perl umumnya memakai suatu variasi dari sintaks regex Perl, tetapi selalu ada perbedaan kecil
      Meski begitu, makna $ dan cara mengubahnya dengan mode multibaris biasanya cukup konsisten
    • Menariknya, RFC 9485 https://datatracker.ietf.org/doc/rfc9485/ “I-Regexp: An Interoperable Regular Expression Format” baru diterbitkan Oktober tahun lalu
  • Orang-orang mencampuradukkan string dan baris
    String adalah sekuens karakter, sedangkan baris bisa dilihat dengan dua cara
    Jika newline dianggap sebagai terminator baris, maka baris adalah nol atau lebih karakter non-newline yang diikuti newline, dan jika tidak ada newline di akhir, itu bukan baris yang lengkap
    POSIX memakai sudut pandang ini
    Jika newline dianggap sebagai pemisah baris, maka baris adalah sekuens nol atau lebih karakter non-newline
    Dalam kedua penafsiran, isi baris berakhir sebelum newline
    Semantik ^ dan $, baik dalam mode satu baris maupun mode multibaris, bersifat berbasis baris
    Untuk semantik berbasis string—atau jika menangani berkas, bisa juga dianggap sebagai semantik seluruh berkas—harus memakai \A dan \Z atau padanannya
    Kedua interpretasi punya kelebihan
    Saat mengirim teks melalui koneksi serial, menjadikan newline sebagai terminator baris memudahkan mengetahui apakah baris lengkap sudah diterima
    Pada berkas teks, melihat newline sebagai pemisah baris bisa lebih nyaman karena baris terakhir tidak menjadi dalam keadaan salah, tetapi dengan terminator baris kita bisa mendeteksi baris yang ditulis tidak lengkap

  • Karena hal ini, beberapa bug serius pernah terjadi di aplikasi berbasis Ruby
    Harus selalu memakai \A\z
    https://homakov.blogspot.com/2012/05/saferweb-injects-in-var...
    https://sakurity.com/blog/2015/02/28/openuri.html
    https://sakurity.com/blog/2015/06/04/mongo_ruby_regexp.html