ฉันเขียนนิพจน์ปกติซึ่งทำงานได้ดีในบางโปรแกรม (grep, sed, awk, perl, python, ruby, ksh, bash, zsh, หา, emacs, vi, vim, gedit, …) แต่เมื่อฉันใช้มันในโปรแกรมอื่น (หรือตัวแปร unix ที่แตกต่างกัน) มันหยุดจับคู่ ทำไม?
ฉันเขียนนิพจน์ปกติซึ่งทำงานได้ดีในบางโปรแกรม (grep, sed, awk, perl, python, ruby, ksh, bash, zsh, หา, emacs, vi, vim, gedit, …) แต่เมื่อฉันใช้มันในโปรแกรมอื่น (หรือตัวแปร unix ที่แตกต่างกัน) มันหยุดจับคู่ ทำไม?
คำตอบ:
น่าเสียดายที่เครื่องมือต่าง ๆ มีไวยากรณ์นิพจน์ปกติแตกต่างกันเล็กน้อยและบางครั้งการใช้งานบางอย่างมีส่วนขยายที่เครื่องมืออื่นไม่สนับสนุนด้วยเหตุผลทางประวัติศาสตร์ ในขณะที่มีพื้นดินทั่วไปดูเหมือนว่านักเขียนเครื่องมือทุกคนทำให้ตัวเลือกที่แตกต่างกันบางอย่าง
ผลลัพธ์คือถ้าคุณมีนิพจน์ทั่วไปที่ทำงานในเครื่องมือหนึ่งคุณอาจต้องแก้ไขเพื่อให้ทำงานในเครื่องมืออื่น ความแตกต่างที่สำคัญระหว่างเครื่องมือทั่วไปคือ:
+?|(){}
ต้องการแบ็กสแลช;.[]*^$
และโดยปกติแล้ว+?|()
ในคำตอบนี้ผมรายการมาตรฐานหลัก ตรวจสอบเอกสารของเครื่องมือที่คุณใช้เพื่อดูรายละเอียด
การเปรียบเทียบเอ็นจิ้นนิพจน์ทั่วไปของวิกิพีเดียมีตารางที่แสดงรายการคุณสมบัติที่รองรับโดยการใช้งานทั่วไป
การแสดงออกปกติพื้นฐานประมวลกฎหมายโดยมาตรฐาน POSIX มันเป็นไวยากรณ์ที่ใช้โดยgrep
, และsed
vi
ไวยากรณ์นี้มีคุณสมบัติดังต่อไปนี้:
^
และ$
จับคู่ที่จุดเริ่มต้นและจุดสิ้นสุดของบรรทัดเท่านั้น.
จับคู่อักขระใด ๆ (หรืออักขระใด ๆ ยกเว้นขึ้นบรรทัดใหม่)[…]
จับคู่อักขระหนึ่งตัวที่อยู่ในวงเล็บ (ชุดอักขระ) หากอักขระตัวแรกหลังจากวงเล็บเปิดเป็น a ^
อักขระที่ไม่อยู่ในรายการจะถูกจับคู่แทน หากต้องการรวม a ]
ให้วางไว้ทันทีหลังจากเปิด[
(หรือหลังจากนั้น[^
ถ้าเป็นชุดลบ) หาก-
อยู่ระหว่างอักขระสองตัวมันจะระบุช่วง หากต้องการรวมตัวอักษร-
ให้วางไว้ในตำแหน่งที่ไม่สามารถแยกวิเคราะห์เป็นช่วงได้^$.*\[
อัญประกาศอักขระถัดไป*
ตรงกับอักขระก่อนหน้าหรือนิพจน์ย่อย 0, 1 หรือมากกว่านั้น\(…\)
เป็นกลุ่มวากยสัมพันธ์สำหรับใช้กับ*
ผู้ปฏิบัติงานหรือ backreferences และการ\DIGIT
แทนที่\1
, \2
... ตรงกับข้อความที่แน่นอนจับคู่โดยกลุ่มที่สอดคล้องกันเช่น\(fo*\)\(ba*\)\1
การแข่งขันแต่ไม่foobaafoo
foobaafo
ไม่มีวิธีมาตรฐานในการอ้างถึงกลุ่มที่ 10 ขึ้นไป (ความหมายมาตรฐานของ\10
คือกลุ่มแรกตามด้วย a 0
)คุณลักษณะต่อไปนี้เป็นมาตรฐาน แต่หายไปจากการใช้งานที่ จำกัด บางอย่าง:
\{m,n\}
จับคู่อักขระก่อนหน้าหรือนิพจน์ย่อยระหว่างmถึงnครั้ง nหรือเมตรสามารถละเว้นและหมายความว่าม.\{m\}
[[:alpha:]]
ตรงกับตัวอักษรใด ๆ การใช้งานที่ทันสมัยของการแสดงออกวงเล็บ ) ได้แก่เรียงองค์ประกอบเช่นเรียนและความเท่าเทียมกันเช่น[.ll.]
[=a=]
ต่อไปนี้เป็นส่วนขยายทั่วไป (โดยเฉพาะในเครื่องมือ GNU) แต่ไม่พบในการปรับใช้ทั้งหมด ตรวจสอบคู่มือของเครื่องมือที่คุณใช้
\|
สำหรับการสลับ: foo\|bar
การแข่งขันหรือfoo
bar
\?
(short for \{0,1\}
) และ\+
(short for \{1,\}
) จับคู่อักขระก่อนหน้าหรือ subexpression ที่มากที่สุด 1 ครั้งหรืออย่างน้อย 1 ครั้งตามลำดับ\n
จับคู่ขึ้นบรรทัดใหม่\t
จับคู่แท็บ ฯลฯ\w
ตรงกับคำใด ๆ ที่เป็นองค์ประกอบ (สั้น ๆ[_[:alnum:]]
แต่มีความแตกต่างเมื่อมันมาถึงการแปล) และ\W
ตรงกับตัวละครใด ๆ ที่ไม่ได้เป็นคำที่เป็นส่วนประกอบ\<
และ\>
จับคู่สตริงว่างเฉพาะที่จุดเริ่มต้นหรือจุดสิ้นสุดของคำตามลำดับ \b
จับคู่อย่างใดอย่างหนึ่งและ\B
ตรงกับที่\b
ไม่โปรดทราบว่าเครื่องมือที่ไม่มีตัว\|
ดำเนินการไม่มีพลังเต็มของนิพจน์ปกติ Backreferences อนุญาตให้มีสิ่งพิเศษบางอย่างที่ไม่สามารถทำได้ด้วยนิพจน์ทั่วไปในแง่คณิตศาสตร์
ขยายการแสดงออกปกติจะถูกประมวลผลโดยมาตรฐาน POSIX ข้อได้เปรียบที่สำคัญของพวกเขาเหนือ BRE คือความสม่ำเสมอ: ตัวดำเนินการมาตรฐานทั้งหมดคือเครื่องหมายวรรคตอนเปลือยเครื่องหมายแบ็กสแลชก่อนอักขระเครื่องหมายวรรคตอนจะอ้างอิงราคาเสมอ มันเป็นไวยากรณ์ที่ใช้โดยawk
, grep -E
หรือegrep
แอฟริกาsed -r
และทุบตีของ=~
ผู้ประกอบการ ไวยากรณ์นี้มีคุณสมบัติดังต่อไปนี้:
^
และ$
จับคู่ที่จุดเริ่มต้นและจุดสิ้นสุดของบรรทัดเท่านั้น.
จับคู่อักขระใด ๆ (หรืออักขระใด ๆ ยกเว้นขึ้นบรรทัดใหม่)[…]
จับคู่อักขระหนึ่งตัวที่อยู่ในวงเล็บ (ชุดอักขระ) การเติมเต็มด้วยการเริ่มต้น^
และช่วงทำงานเหมือนใน BRE (ดูด้านบน) คลาสของอักขระสามารถใช้ได้ แต่หายไปจากการใช้งานไม่กี่ครั้ง การใช้งานที่ทันสมัยยังสนับสนุนคลาสที่เทียบเท่าและองค์ประกอบการเรียง เครื่องหมายแบ็กสแลชที่อยู่ในเครื่องหมายวงเล็บจะอ้างอิงอักขระถัดไปในการนำไปใช้บางส่วน แต่ไม่ใช่ทั้งหมด ใช้\\
เพื่อหมายถึงแบ็กสแลชสำหรับการพกพา(…)
เป็นกลุ่มวากยสัมพันธ์สำหรับใช้กับ*
หรือ\DIGIT
ทดแทน|
สำหรับการสลับ: foo|bar
การแข่งขันหรือfoo
bar
*
, +
และ?
ตรงกับตัวอักษรก่อนหรือ subexpression จำนวนครั้ง: 0 หรือมากกว่าสำหรับ*
1 หรือมากกว่าสำหรับ+
, 0 หรือ ?
1{m,n}
ตรงกับอักขระก่อนหน้าหรือ subexpression ระหว่างmและnครั้ง (หายไปจากการใช้งานบางอย่าง); nหรือเมตรสามารถละเว้นและหมายความว่าม.{m}
\DIGIT
$0 ~ "(...)\\1"
\n
\t
\b
\B
\b
\B
PCRE เป็นส่วนเสริมของ ERE แต่เดิมเปิดตัวโดยPerlและนำไปใช้โดย GNU grep -P
และเครื่องมือที่ทันสมัยและภาษาการเขียนโปรแกรมมักจะผ่านห้องสมุดPCRE ดูเอกสารประกอบ Perlสำหรับการจัดรูปแบบที่ดีด้วยตัวอย่าง ไม่รองรับคุณสมบัติทั้งหมดของ Perl เวอร์ชันล่าสุดโดย PCRE (เช่นการใช้รหัส Perl ได้รับการสนับสนุนใน Perl เท่านั้น) ดูคู่มือ PCREสำหรับข้อมูลสรุปของคุณสมบัติที่รองรับ ส่วนเพิ่มเติมที่สำคัญของ ERE คือ:
(?:…)
เป็นกลุ่มที่ไม่ได้จับภาพ: ชอบ(…)
แต่ไม่นับรวมสำหรับการอ้างอิงย้อนกลับ(?=FOO)BAR
(lookahead) การแข่งขันBAR
แต่ก็ต่อเมื่อมีการแข่งขันFOO
เริ่มต้นที่ตำแหน่งเดียวกัน นี้จะเป็นประโยชน์มากที่สุดในการยึดการแข่งขันโดยไม่รวมข้อความต่อไปนี้ในการแข่งขัน: foo(?=bar)
การแข่งขันแต่ถ้ามันตามมาด้วยfoo
bar
(?!FOO)BAR
(lookahead เชิงลบ) จับคู่BAR
แต่ยังไม่มีการจับคู่FOO
ที่ตำแหน่งเดียวกัน ตัวอย่างเช่น(?!foo)[a-z]+
ตรงกับคำตัวพิมพ์เล็กใด ๆ ที่ไม่ได้เริ่มต้นด้วยfoo
; [a-z]+(?![0-9)
ตรงกับคำใด ๆ ที่เป็นตัวพิมพ์เล็กที่ไม่ได้ตามด้วยตัวเลข (ดังนั้นfoo123
มันตรงกับfo
แต่ไม่foo
)(?<=FOO)BAR
(lookbehind) ตรงแต่ถ้ามันจะนำหน้าทันทีโดยการแข่งขันBAR
ต้องมีความยาวเป็นที่รู้จัก (คุณไม่สามารถใช้ตัวดำเนินการซ้ำซ้อนเช่น) สิ่งนี้มีประโยชน์มากที่สุดในการยึดการจับคู่โดยไม่รวมข้อความก่อนหน้าในการจับคู่: จับคู่เท่านั้นหากมีการเว้นวรรคหรือนำหน้าต้นของสตริงFOO
FOO
*
(?<=^| )foo
foo
(?<!FOO)BAR
(lookbehind เชิงลบ) จับคู่BAR
แต่เฉพาะในกรณีที่ไม่ได้นำหน้าด้วยการจับคู่FOO
ทันที FOO
ต้องมีความยาวเป็นที่รู้จัก (คุณไม่สามารถใช้ตัวดำเนินการซ้ำซ้อนเช่น*
) สิ่งนี้มีประโยชน์มากที่สุดในการยึดการจับคู่โดยไม่รวมข้อความก่อนหน้าในการจับคู่: (?<![a-z])foo
จับคู่foo
เท่านั้นหากไม่มีตัวอักษรพิมพ์เล็กนำหน้าไวยากรณ์ของ Emacsอยู่ตรงกลางระหว่าง BRE และ ERE นอกจาก Emacs มันเป็นไวยากรณ์เริ่มต้นสำหรับ-regex
ใน GNU find Emacs นำเสนอตัวดำเนินการดังต่อไปนี้:
^
, $
, .
, […]
, *
, +
, ?
เช่นเดียวกับใน ERE\(…\)
, \|
, \{…\}
, เช่นเดียวกับใน BRE\DIGIT
\<
และ\>
สำหรับขอบเขตของคำ และอื่น ๆ อีกมากมายในเวอร์ชั่นล่าสุดของ Emacs ที่มักไม่รองรับเอนจิ้นอื่นที่มีรูปแบบคล้าย EmacsShell globs (wildcard) ทำการจับคู่รูปแบบกับไวยากรณ์ที่แตกต่างอย่างสิ้นเชิงจากนิพจน์ทั่วไปและมีประสิทธิภาพน้อยกว่า นอกจากเชลล์แล้วไวด์การ์ดเหล่านี้ยังสามารถใช้ได้กับเครื่องมืออื่น ๆ เช่นfind -name
และตัวกรอง rsync รูปแบบ POSIXมีคุณสมบัติดังต่อไปนี้:
?
จับคู่อักขระเดี่ยวใด ๆ[…]
เป็นชุดอักขระเช่นเดียวกับในไวยากรณ์นิพจน์ทั่วไปทั่วไป เชลล์บางตัวไม่รองรับคลาสอักขระ เชลล์บางตัวต้องการ!
แทนที่จะ^
ลบล้างชุด*
ตรงกับลำดับของตัวอักษรใด ๆ (มักจะยกเว้น/
เมื่อจับคู่เส้นทางแฟ้มถ้า/
ได้รับการยกเว้นจาก*
นั้น**
บางครั้งรวมถึง/
แต่ตรวจสอบเอกสารของเครื่องมือ)Ksh เสนอคุณสมบัติเพิ่มเติมที่ให้รูปแบบการจับคู่เต็มกำลังของการแสดงออกปกติ shopt -s extglob
คุณสมบัติเหล่านี้ยังมีอยู่ในทุบตีหลังจากทำงาน Zsh มีรูปแบบที่แตกต่างกันแต่ยังสามารถรองรับไวยากรณ์ของ ksh setopt ksh_glob
ได้
grep
, tw
, expr
... ) ยกเว้นksh
ชุดเครื่องมือนั้นไม่ค่อยพบนอก AT&T
regex(7)
เห็นด้วยกับคุณและเรียกว่า[these]
"การแสดงออกของวงเล็บ" และ (ภายใน "การแสดงออกของวงเล็บ") [:these:]
" ฉันไม่แน่ใจว่าจะพูดยังไงให้ดีที่สุด
-
ระบุช่วงและควรหลีกเลี่ยงอย่างใดอย่างหนึ่งก่อน (หลังจากตัวเลือก^
) หรือสุดท้ายถ้าจะต้องดำเนินการตามตัวอักษรอย่างแท้จริง (ฉันเคยเห็นข้อบกพร่องมากมายที่เกิดจากเช่น[A-z]
- สังเกตการเปลี่ยนแปลงในตัวอักษร - ซึ่งตรงกับตัวอักษรของรหัส 65 ถึง 122 และบังเอิญรวมถึงแต่ละ: [\]^_`
ฉันยังเห็นความถูกต้อง แต่ยังสับสน[!-~]
เพื่อให้ตรงกับอักขระที่พิมพ์ได้ทั้งหมดใน ANSI ซึ่งฉันชอบที่จะเห็นเป็น[\x21-\x7e]
ซึ่งเป็นอย่างน้อยตรงไปตรงมาในการกระทำของตนแม้ว่าจะทำให้เกิดความสับสนในมิติที่แตกต่างกัน).
vim
's และ AT&T libast (ดังksh93
)