นิพจน์ทั่วไปที่ทำงานได้ “ทุกที่”
(johndcook.com)- นิพจน์ทั่วไปมีฟีเจอร์และไวยากรณ์ที่รองรับแตกต่างกันไปตามแต่ละ 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.คืออักขระที่ไม่ใช่วรรคตอน
- class
- ฟีเจอร์ที่ใช้ได้ภายใต้เกณฑ์นี้มีดังนี้
.^,$[…],[^…]*\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...
จะเจอสถานการณ์ที่กฎการ escape ต่างชนิดซ้อนทับกันอยู่เรื่อย ๆ
(และ)match แบบเป็นตัวอักษรตรง ๆ ก็สะดวกอยู่นิดหน่อยดูเหมือนผู้เขียนจะเกือบพูดให้ถึงจุดนั้นแล้ว แต่สุดท้ายคงอยากบอกว่า 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/...