3 คะแนน โดย GN⁺ 14 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • นิพจน์ทั่วไปมีฟีเจอร์และไวยากรณ์ที่รองรับแตกต่างกันไปตามแต่ละ implementation ดังนั้น pattern ที่ใช้ได้ในเครื่องมือหนึ่งอาจ ล้มเหลวหรือต้องแก้ไข ในสภาพแวดล้อมอื่น
  • ยิ่งคุ้นเคยกับสภาพแวดล้อมที่มีฟีเจอร์มากอย่าง Perl ก็ยิ่งมักเจอปัญหาความเข้ากันได้บ่อยขึ้น และหากต้องคำนึงถึงคอมพิวเตอร์ที่ไม่มีสิทธิ์ติดตั้งซอฟต์แวร์ด้วย การใช้ ส่วนย่อยร่วมกัน จะปลอดภัยกว่า
  • หากนิยามคำว่า “ทุกที่” อย่างเข้มงวดที่สุด ขอบเขตจะเล็กลงจนเหลือเพียง literal, character class แบบ […] และ อักขระพิเศษพื้นฐาน อย่าง . * ^ $
  • หากใช้ GNU sed, awk, grep พร้อมตัวเลือก -E ของ sed และ grep จะทำให้ฟีเจอร์ร่วมที่ใช้ได้กว้างขึ้น แต่ในชุดนี้ awk โดยทั่วไปจะกลายเป็น ตัวหารร่วมต่ำสุด
  • Emacs ต้องใส่แบ็กสแลชหน้า +? ( ) { } | และความหมายของ \s, \S ก็แตกต่างกัน ดังนั้นหากจะใช้นิพจน์ทั่วไปเดียวกันกับหลายเครื่องมือ ต้องตรวจสอบไปถึง ไวยากรณ์ที่เป็นข้อยกเว้น ด้วย

เหตุผลที่ความเข้ากันได้ของนิพจน์ทั่วไปเป็นเรื่องยาก

  • ความไม่สะดวกที่ใหญ่ที่สุดของนิพจน์ทั่วไปมาจาก ความแตกต่างระหว่าง implementation
    • ฟีเจอร์ที่รองรับในเครื่องมือหนึ่งอาจไม่มีเลยในอีกเครื่องมือหนึ่ง
    • แม้จะเป็นฟีเจอร์เดียวกัน ไวยากรณ์ก็อาจแตกต่างกันเล็กน้อย
  • Perl เป็นสภาพแวดล้อมนิพจน์ทั่วไปที่มีฟีเจอร์มาก ดังนั้นฟีเจอร์ที่ดูเป็นเรื่องปกติตามมาตรฐานของ Perl อาจไม่มีในสภาพแวดล้อมอื่น
  • อาจใช้ตัวทดแทนของเครื่องมืออื่นที่คล้าย Perl ได้ แต่ไม่ใช่วิธีมาตรฐาน จึงทำให้ส่งโค้ดที่รันได้ทันทีให้เพื่อนร่วมงานหรือลูกค้าได้ยาก
  • หากคำนึงถึงสถานการณ์ที่ต้องทำงานบนคอมพิวเตอร์ซึ่งติดตั้งซอฟต์แวร์ไม่ได้ด้วย ก็จำเป็นต้องหาแนวทางที่ใช้ ส่วนย่อยของฟีเจอร์นิพจน์ทั่วไป ที่ทำงานได้ในหลายสภาพแวดล้อม
  • ยิ่งกำหนดนิยามของ “ทุกที่” อย่างเข้มงวด ฟีเจอร์ที่ใช้ได้ก็ยิ่งลดลง
    • literal
    • character class แบบ […]
    • อักขระพิเศษ . * ^ $

ขอบเขตร่วมใน sed, awk, grep และ Emacs

  • หากจำกัดเครื่องมือเป้าหมายไว้ที่ sed, awk, grep และ Emacs ก็สามารถผ่อนเกณฑ์ของ “ทุกที่” ลงได้เล็กน้อย
  • เมื่อใช้ sed, awk, grep เวอร์ชัน GNU และใช้ตัวเลือก -E กับ sed และ grep รายการฟีเจอร์ร่วมจะกว้างขึ้น
    • ฟีเจอร์นิพจน์ทั่วไปของทั้งสามเครื่องมือคล้ายกัน
    • ฟีเจอร์ของ awk โดยทั่วไปก็รองรับในเครื่องมืออื่นด้วย
    • ข้อยกเว้นคือ ขอบเขตคำ ซึ่งใน awk ใช้ \<, \> และต่างจาก \b, \B
  • Emacs รองรับฟีเจอร์ส่วนใหญ่ของ awk แต่มีความแตกต่างด้านไวยากรณ์
    • หากต้องการให้ + ? ( ) { } | ทำหน้าที่เหมือนใน awk ต้องใส่แบ็กสแลชไว้ข้างหน้า
    • นิพจน์ที่เทียบเท่ากับ \s, \S ของ awk ใน Emacs คือ \s-, \S-
  • ใน Emacs, \s, \S ไม่ได้หมายถึงช่องว่าง/ไม่ใช่ช่องว่าง แต่เป็นการเริ่มต้น character class
    • class - หมายถึงช่องว่าง
    • \s. คืออักขระวรรคตอน
    • \S. คืออักขระที่ไม่ใช่วรรคตอน
  • ฟีเจอร์ที่ใช้ได้ภายใต้เกณฑ์นี้มีดังนี้
    • .
    • ^, $
    • […], [^…]
    • *
    • \w, \W, \s, \S
    • backreference ตั้งแต่ \1 ถึง \9
    • \b, \B
    • ?, +
    • ทางเลือก |
    • จำนวนการทำซ้ำ {n,m}
    • capture (...)
  • อย่างไรก็ตาม gawk รองรับ backreference ในสตริงแทนที่ แต่ไม่รองรับ backreference ภายในนิพจน์ทั่วไปเอง
  • look-around ถือเป็นฟีเจอร์ขั้นสูงได้ และแม้ \d อาจดูเหมือนฟีเจอร์พื้นฐานสำหรับตัวเลข แต่ก็ไม่ได้รับการรองรับในนิพจน์ทั่วไปหลายรูปแบบ

1 ความคิดเห็น

 
ความคิดเห็นใน Hacker News
  • ใน Emacs ลำบากเป็นพิเศษ เพราะแทบเหมือนต้องเดาเอาว่าอะไรควร escape บ้าง
    มีทางเลือกที่ชื่อ rx อยู่ก็จริง[0] แต่ใช้งานจริงก็ไม่ได้สนุกนัก
    นอกเหนือจากไวยากรณ์ regex เองแล้ว ในขั้นตอนใช้งานจริงก็มักเจอปัญหา encoding และ escaping บ่อย ๆ
    เช่น ถ้าป้อน regex ใน shell ก็ต้อง escape ให้ถูกต้อง หรือใน Python ก็ต้องตรวจว่าเป็น raw string หรือไม่
    ถึงอย่างนั้น การที่วิธีใช้ regex ในเครื่องมือส่วนใหญ่เข้ามาอยู่ในกรอบที่คล้ายกันได้ระดับหนึ่ง ก็แทบจะเป็นปาฏิหาริย์ของยุคสมัยใหม่
    [0]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Rx...

    • จะยิ่งสนุกขึ้นอีกถ้าเขียน Python ที่สร้าง shell script ที่มี regex
      จะเจอสถานการณ์ที่กฎการ escape ต่างชนิดซ้อนทับกันอยู่เรื่อย ๆ
    • ถ้าฝืนหาข้อดีสักอย่าง เวลาสิ่งที่ค้นหาเป็น โค้ด Elisp การที่ ( และ ) match แบบเป็นตัวอักษรตรง ๆ ก็สะดวกอยู่นิดหน่อย
    • regex ควรจะเป็นภาษาที่มีโครงสร้าง ไม่ใช่ DSL หลายแบบที่เอามาปนกันมั่ว ๆ
  • ดูเหมือนผู้เขียนจะเกือบพูดให้ถึงจุดนั้นแล้ว แต่สุดท้ายคงอยากบอกว่า POSIX Basic Regular Expressions ใช้งานได้ทุกที่
    เพียงแต่มีข้อแม้ว่าไม่ใช่ทุกคนจะตาม Single Unix Specification ฉบับที่ 8 ทัน และในฉบับนั้น BRE ก็มีการเปลี่ยนแปลงเล็กน้อย

    • การประเมินแบบนั้นดูไม่ค่อยยุติธรรมกับผู้เขียน
      ถ้าไม่มีข้อแม้แบบนั้น ก็คงไม่มีเหตุผลต้องเขียนบทความนั้นตั้งแต่แรก
  • เคยเขียนเปเปอร์เกี่ยวกับการหา regex ที่ match แบบเดียวกันทั้งใน semantics แบบ greedy และ semantics แบบ leftmost maximal
    https://par.nsf.gov/servlets/purl/10534654

  • ผมมักจะจู้จี้เรื่องการระบุให้ชัดว่าเครื่องมือไหนรับ ภาษา regex แบบใด และ match กับอะไรในบรรดา substring ใด ๆ, prefix, suffix, สตริงทั้งก้อน, หนึ่งบรรทัด, หรือ substring ภายในบรรทัด
    ตรงนี้มี [แบบที่ใช้กันแพร่หลายกว่า][1] และนอกจากนี้ยังมี PCRE กับ Python ด้วย
    ใช้เวลาพอสมควรกว่าจะรู้ว่ารูปแบบเก่า ๆ บางส่วนที่เห็นในพวก grep นั้น [ถูกกำหนดไว้ใน POSIX][2]
    [1]: https://cppreference.com/cpp/regex#Regular_expression_gramma...
    [2]: https://pubs.opengroup.org/onlinepubs/009696899/basedefs/xbd...

  • อยากแชร์หน้าของ Russ Cox เกี่ยวกับ regex
    คิดว่าเป็นแหล่งข้อมูลดี ๆ ที่น่าอ่าน
    https://swtch.com/~rsc/regexp/

  • ด้วยเหตุผลแบบนี้ RFC 9485 หรือ I-Regexp: An Interoperable Regular Expression Format จึงสำคัญ
    https://datatracker.ietf.org/doc/html/rfc9485

  • แพ็กเกจ regexp ใน standard library ของ Go ไม่รองรับ backreference เพราะใช้เอนจิน RE2
    ใช้ในการแทนที่ได้ แต่ใช้ในการ match ไม่ได้

    • regexp ไม่ได้ใช้ re2 แต่เป็นการ implement แยกต่างหากที่ใช้แนวคิดเดียวกัน
  • หลังเจอความหงุดหงิดคล้าย ๆ กันใน rule engine, template engine และ engine แนว IFTTT ก็เลยสร้าง ไลบรารี Rust สำหรับ JSONLogic และใช้ binding สำหรับภาษาอื่นด้วย
    https://github.com/GoPlasmatic/datalogic-rs

  • ในเอกสาร JSON Schema ก็มี subset ของ regex ที่แนะนำไว้เช่นกัน
    https://json-schema.org/understanding-json-schema/reference/...