ทำไมนิพจน์ปกติของฉันทำงานใน X แต่ไม่ใช่ใน Y


76

ฉันเขียนนิพจน์ปกติซึ่งทำงานได้ดีในบางโปรแกรม (grep, sed, awk, perl, python, ruby, ksh, bash, zsh, หา, emacs, vi, vim, gedit, …) แต่เมื่อฉันใช้มันในโปรแกรมอื่น (หรือตัวแปร unix ที่แตกต่างกัน) มันหยุดจับคู่ ทำไม?

คำตอบ:


102

น่าเสียดายที่เครื่องมือต่าง ๆ มีไวยากรณ์นิพจน์ปกติแตกต่างกันเล็กน้อยและบางครั้งการใช้งานบางอย่างมีส่วนขยายที่เครื่องมืออื่นไม่สนับสนุนด้วยเหตุผลทางประวัติศาสตร์ ในขณะที่มีพื้นดินทั่วไปดูเหมือนว่านักเขียนเครื่องมือทุกคนทำให้ตัวเลือกที่แตกต่างกันบางอย่าง

ผลลัพธ์คือถ้าคุณมีนิพจน์ทั่วไปที่ทำงานในเครื่องมือหนึ่งคุณอาจต้องแก้ไขเพื่อให้ทำงานในเครื่องมืออื่น ความแตกต่างที่สำคัญระหว่างเครื่องมือทั่วไปคือ:

  • ไม่ว่าจะเป็นผู้ประกอบการที่+?|(){}ต้องการแบ็กสแลช;
  • ส่วนขยายใดที่ได้รับการสนับสนุนเกินกว่าพื้นฐาน.[]*^$และโดยปกติแล้ว+?|()

ในคำตอบนี้ผมรายการมาตรฐานหลัก ตรวจสอบเอกสารของเครื่องมือที่คุณใช้เพื่อดูรายละเอียด

การเปรียบเทียบเอ็นจิ้นนิพจน์ทั่วไปของวิกิพีเดียมีตารางที่แสดงรายการคุณสมบัติที่รองรับโดยการใช้งานทั่วไป

นิพจน์ปกติพื้นฐาน (BRE)

การแสดงออกปกติพื้นฐานประมวลกฎหมายโดยมาตรฐาน POSIX มันเป็นไวยากรณ์ที่ใช้โดยgrep, และsed viไวยากรณ์นี้มีคุณสมบัติดังต่อไปนี้:

  • ^และ$จับคู่ที่จุดเริ่มต้นและจุดสิ้นสุดของบรรทัดเท่านั้น
  • . จับคู่อักขระใด ๆ (หรืออักขระใด ๆ ยกเว้นขึ้นบรรทัดใหม่)
  • […]จับคู่อักขระหนึ่งตัวที่อยู่ในวงเล็บ (ชุดอักขระ) หากอักขระตัวแรกหลังจากวงเล็บเปิดเป็น a ^อักขระที่ไม่อยู่ในรายการจะถูกจับคู่แทน หากต้องการรวม a ]ให้วางไว้ทันทีหลังจากเปิด[(หรือหลังจากนั้น[^ถ้าเป็นชุดลบ) หาก-อยู่ระหว่างอักขระสองตัวมันจะระบุช่วง หากต้องการรวมตัวอักษร-ให้วางไว้ในตำแหน่งที่ไม่สามารถแยกวิเคราะห์เป็นช่วงได้
  • แบ็กสแลชก่อน^$.*\[อัญประกาศอักขระถัดไป
  • * ตรงกับอักขระก่อนหน้าหรือนิพจน์ย่อย 0, 1 หรือมากกว่านั้น
  • \(…\)เป็นกลุ่มวากยสัมพันธ์สำหรับใช้กับ*ผู้ปฏิบัติงานหรือ backreferences และการ\DIGITแทนที่
  • Backreferences \1, \2... ตรงกับข้อความที่แน่นอนจับคู่โดยกลุ่มที่สอดคล้องกันเช่น\(fo*\)\(ba*\)\1การแข่งขันแต่ไม่foobaafoo foobaafoไม่มีวิธีมาตรฐานในการอ้างถึงกลุ่มที่ 10 ขึ้นไป (ความหมายมาตรฐานของ\10คือกลุ่มแรกตามด้วย a 0)

คุณลักษณะต่อไปนี้เป็นมาตรฐาน แต่หายไปจากการใช้งานที่ จำกัด บางอย่าง:

  • \{m,n\}จับคู่อักขระก่อนหน้าหรือนิพจน์ย่อยระหว่างmถึงnครั้ง nหรือเมตรสามารถละเว้นและหมายความว่าม.\{m\}
  • ภายในวงเล็บปีกกาคลาสอักขระสามารถใช้ได้ตัวอย่างเช่น[[:alpha:]]ตรงกับตัวอักษรใด ๆ การใช้งานที่ทันสมัยของการแสดงออกวงเล็บ ) ได้แก่เรียงองค์ประกอบเช่นเรียนและความเท่าเทียมกันเช่น[.ll.][=a=]

ต่อไปนี้เป็นส่วนขยายทั่วไป (โดยเฉพาะในเครื่องมือ GNU) แต่ไม่พบในการปรับใช้ทั้งหมด ตรวจสอบคู่มือของเครื่องมือที่คุณใช้

  • \|สำหรับการสลับ: foo\|barการแข่งขันหรือfoobar
  • \?(short for \{0,1\}) และ\+(short for \{1,\}) จับคู่อักขระก่อนหน้าหรือ subexpression ที่มากที่สุด 1 ครั้งหรืออย่างน้อย 1 ครั้งตามลำดับ
  • \nจับคู่ขึ้นบรรทัดใหม่\tจับคู่แท็บ ฯลฯ
  • \wตรงกับคำใด ๆ ที่เป็นองค์ประกอบ (สั้น ๆ[_[:alnum:]]แต่มีความแตกต่างเมื่อมันมาถึงการแปล) และ\Wตรงกับตัวละครใด ๆ ที่ไม่ได้เป็นคำที่เป็นส่วนประกอบ
  • \<และ\>จับคู่สตริงว่างเฉพาะที่จุดเริ่มต้นหรือจุดสิ้นสุดของคำตามลำดับ \bจับคู่อย่างใดอย่างหนึ่งและ\Bตรงกับที่\bไม่

โปรดทราบว่าเครื่องมือที่ไม่มีตัว\|ดำเนินการไม่มีพลังเต็มของนิพจน์ปกติ Backreferences อนุญาตให้มีสิ่งพิเศษบางอย่างที่ไม่สามารถทำได้ด้วยนิพจน์ทั่วไปในแง่คณิตศาสตร์

นิพจน์ทั่วไปที่ขยายเพิ่ม (ERE)

ขยายการแสดงออกปกติจะถูกประมวลผลโดยมาตรฐาน POSIX ข้อได้เปรียบที่สำคัญของพวกเขาเหนือ BRE คือความสม่ำเสมอ: ตัวดำเนินการมาตรฐานทั้งหมดคือเครื่องหมายวรรคตอนเปลือยเครื่องหมายแบ็กสแลชก่อนอักขระเครื่องหมายวรรคตอนจะอ้างอิงราคาเสมอ มันเป็นไวยากรณ์ที่ใช้โดยawk, grep -Eหรือegrepแอฟริกาsed -rและทุบตีของ=~ผู้ประกอบการ ไวยากรณ์นี้มีคุณสมบัติดังต่อไปนี้:

  • ^และ$จับคู่ที่จุดเริ่มต้นและจุดสิ้นสุดของบรรทัดเท่านั้น
  • . จับคู่อักขระใด ๆ (หรืออักขระใด ๆ ยกเว้นขึ้นบรรทัดใหม่)
  • […]จับคู่อักขระหนึ่งตัวที่อยู่ในวงเล็บ (ชุดอักขระ) การเติมเต็มด้วยการเริ่มต้น^และช่วงทำงานเหมือนใน BRE (ดูด้านบน) คลาสของอักขระสามารถใช้ได้ แต่หายไปจากการใช้งานไม่กี่ครั้ง การใช้งานที่ทันสมัยยังสนับสนุนคลาสที่เทียบเท่าและองค์ประกอบการเรียง เครื่องหมายแบ็กสแลชที่อยู่ในเครื่องหมายวงเล็บจะอ้างอิงอักขระถัดไปในการนำไปใช้บางส่วน แต่ไม่ใช่ทั้งหมด ใช้\\เพื่อหมายถึงแบ็กสแลชสำหรับการพกพา
  • (…)เป็นกลุ่มวากยสัมพันธ์สำหรับใช้กับ*หรือ\DIGITทดแทน
  • |สำหรับการสลับ: foo|barการแข่งขันหรือfoobar
  • *, +และ?ตรงกับตัวอักษรก่อนหรือ subexpression จำนวนครั้ง: 0 หรือมากกว่าสำหรับ*1 หรือมากกว่าสำหรับ+, 0 หรือ ?1
  • เครื่องหมายทับขวาจะใส่เครื่องหมายอักขระถัดไปหากไม่ใช่ตัวอักษรผสมตัวเลข
  • {m,n}ตรงกับอักขระก่อนหน้าหรือ subexpression ระหว่างmและnครั้ง (หายไปจากการใช้งานบางอย่าง); nหรือเมตรสามารถละเว้นและหมายความว่าม.{m}
  • ส่วนขยายทั่วไปบางส่วนเช่นเดียวกับใน BRE: backreferences (สะดุดตาใน awk ยกเว้นในการใช้งาน busybox ซึ่งคุณสามารถใช้); ตัวอักษรพิเศษ, ฯลฯ .; ขอบเขตคำและองค์ประกอบของคำและ...\DIGIT$0 ~ "(...)\\1"\n\t\b\B\b\B

PCRE (นิพจน์ทั่วไปที่เข้ากันได้กับ Perl)

PCRE เป็นส่วนเสริมของ ERE แต่เดิมเปิดตัวโดยPerlและนำไปใช้โดย GNU grep -Pและเครื่องมือที่ทันสมัยและภาษาการเขียนโปรแกรมมักจะผ่านห้องสมุดPCRE ดูเอกสารประกอบ Perlสำหรับการจัดรูปแบบที่ดีด้วยตัวอย่าง ไม่รองรับคุณสมบัติทั้งหมดของ Perl เวอร์ชันล่าสุดโดย PCRE (เช่นการใช้รหัส Perl ได้รับการสนับสนุนใน Perl เท่านั้น) ดูคู่มือ PCREสำหรับข้อมูลสรุปของคุณสมบัติที่รองรับ ส่วนเพิ่มเติมที่สำคัญของ ERE คือ:

  • (?:…)เป็นกลุ่มที่ไม่ได้จับภาพ: ชอบ(…)แต่ไม่นับรวมสำหรับการอ้างอิงย้อนกลับ
  • (?=FOO)BAR(lookahead) การแข่งขันBARแต่ก็ต่อเมื่อมีการแข่งขันFOOเริ่มต้นที่ตำแหน่งเดียวกัน นี้จะเป็นประโยชน์มากที่สุดในการยึดการแข่งขันโดยไม่รวมข้อความต่อไปนี้ในการแข่งขัน: foo(?=bar)การแข่งขันแต่ถ้ามันตามมาด้วยfoobar
  • (?!FOO)BAR(lookahead เชิงลบ) จับคู่BARแต่ยังไม่มีการจับคู่FOOที่ตำแหน่งเดียวกัน ตัวอย่างเช่น(?!foo)[a-z]+ตรงกับคำตัวพิมพ์เล็กใด ๆ ที่ไม่ได้เริ่มต้นด้วยfoo; [a-z]+(?![0-9)ตรงกับคำใด ๆ ที่เป็นตัวพิมพ์เล็กที่ไม่ได้ตามด้วยตัวเลข (ดังนั้นfoo123มันตรงกับfoแต่ไม่foo)
  • (?<=FOO)BAR(lookbehind) ตรงแต่ถ้ามันจะนำหน้าทันทีโดยการแข่งขันBAR ต้องมีความยาวเป็นที่รู้จัก (คุณไม่สามารถใช้ตัวดำเนินการซ้ำซ้อนเช่น) สิ่งนี้มีประโยชน์มากที่สุดในการยึดการจับคู่โดยไม่รวมข้อความก่อนหน้าในการจับคู่: จับคู่เท่านั้นหากมีการเว้นวรรคหรือนำหน้าต้นของสตริงFOOFOO*(?<=^| )foofoo
  • (?<!FOO)BAR(lookbehind เชิงลบ) จับคู่BARแต่เฉพาะในกรณีที่ไม่ได้นำหน้าด้วยการจับคู่FOOทันที FOOต้องมีความยาวเป็นที่รู้จัก (คุณไม่สามารถใช้ตัวดำเนินการซ้ำซ้อนเช่น*) สิ่งนี้มีประโยชน์มากที่สุดในการยึดการจับคู่โดยไม่รวมข้อความก่อนหน้าในการจับคู่: (?<![a-z])fooจับคู่fooเท่านั้นหากไม่มีตัวอักษรพิมพ์เล็กนำหน้า

Emacs

ไวยากรณ์ของ Emacsอยู่ตรงกลางระหว่าง BRE และ ERE นอกจาก Emacs มันเป็นไวยากรณ์เริ่มต้นสำหรับ-regexใน GNU find Emacs นำเสนอตัวดำเนินการดังต่อไปนี้:

  • ^, $, ., […], *, +, ?เช่นเดียวกับใน ERE
  • \(…\), \|, \{…\}, เช่นเดียวกับใน BRE\DIGIT
  • เพิ่มเติมลำดับทับขวาตัวอักษร ; \<และ\>สำหรับขอบเขตของคำ และอื่น ๆ อีกมากมายในเวอร์ชั่นล่าสุดของ Emacs ที่มักไม่รองรับเอนจิ้นอื่นที่มีรูปแบบคล้าย Emacs

เชลล์คร่ำครวญ

Shell globs (wildcard) ทำการจับคู่รูปแบบกับไวยากรณ์ที่แตกต่างอย่างสิ้นเชิงจากนิพจน์ทั่วไปและมีประสิทธิภาพน้อยกว่า นอกจากเชลล์แล้วไวด์การ์ดเหล่านี้ยังสามารถใช้ได้กับเครื่องมืออื่น ๆ เช่นfind -nameและตัวกรอง rsync รูปแบบ POSIXมีคุณสมบัติดังต่อไปนี้:

  • ? จับคู่อักขระเดี่ยวใด ๆ
  • […]เป็นชุดอักขระเช่นเดียวกับในไวยากรณ์นิพจน์ทั่วไปทั่วไป เชลล์บางตัวไม่รองรับคลาสอักขระ เชลล์บางตัวต้องการ!แทนที่จะ^ลบล้างชุด
  • *ตรงกับลำดับของตัวอักษรใด ๆ (มักจะยกเว้น/เมื่อจับคู่เส้นทางแฟ้มถ้า/ได้รับการยกเว้นจาก*นั้น**บางครั้งรวมถึง/แต่ตรวจสอบเอกสารของเครื่องมือ)
  • แบ็กสแลชอัญประกาศอักขระถัดไป

Ksh เสนอคุณสมบัติเพิ่มเติมที่ให้รูปแบบการจับคู่เต็มกำลังของการแสดงออกปกติ shopt -s extglobคุณสมบัติเหล่านี้ยังมีอยู่ในทุบตีหลังจากทำงาน Zsh มีรูปแบบที่แตกต่างกันแต่ยังสามารถรองรับไวยากรณ์ของ ksh setopt ksh_globได้


REs รวยอื่น ๆ ที่คุณอาจต้องการพูดถึงคือvim's และ AT&T libast (ดังksh93)
Stéphane Chazelas

@ StéphaneChazelasนอกจาก vim แล้วโปรแกรมอะไรที่ใช้ vim regexps นอกเหนือจาก ksh แล้วโปรแกรมใดที่ใช้ libast
Gilles

ทั้งหมดของ AT & T ชุดเครื่องมือที่ใช้ AT & T RES ( grep, tw, expr... ) ยกเว้นkshชุดเครื่องมือนั้นไม่ค่อยพบนอก AT&T
Stéphane Chazelas

ตามความเข้าใจของฉัน (และของ Wikipedia) คำว่า "คลาสของตัวละคร" ของคุณหมายถึง "คลาสของตัวละคร POSIX" ... อย่างไรก็ตามregex(7)เห็นด้วยกับคุณและเรียกว่า[these]"การแสดงออกของวงเล็บ" และ (ภายใน "การแสดงออกของวงเล็บ") [:these:]" ฉันไม่แน่ใจว่าจะพูดยังไงให้ดีที่สุด
Adam Katz

สิ่งที่คุณเรียกพวกเขาพวกเขาสนับสนุนช่วง เป็นที่น่าสังเกตว่าควร-ระบุช่วงและควรหลีกเลี่ยงอย่างใดอย่างหนึ่งก่อน (หลังจากตัวเลือก^) หรือสุดท้ายถ้าจะต้องดำเนินการตามตัวอักษรอย่างแท้จริง (ฉันเคยเห็นข้อบกพร่องมากมายที่เกิดจากเช่น[A-z]- สังเกตการเปลี่ยนแปลงในตัวอักษร - ซึ่งตรงกับตัวอักษรของรหัส 65 ถึง 122 และบังเอิญรวมถึงแต่ละ: [\]^_`ฉันยังเห็นความถูกต้อง แต่ยังสับสน[!-~]เพื่อให้ตรงกับอักขระที่พิมพ์ได้ทั้งหมดใน ANSI ซึ่งฉันชอบที่จะเห็นเป็น[\x21-\x7e]ซึ่งเป็นอย่างน้อยตรงไปตรงมาในการกระทำของตนแม้ว่าจะทำให้เกิดความสับสนในมิติที่แตกต่างกัน).
อดัมแคทซ์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.