คู่มือการใช้ไพวิกิพีเดีย/นิพจน์ปรกติในไพวิกิพีเดีย
- ส่วนหรือหน้านี้เขียนด้วยความเข้าใจว่าผู้อ่านมีความรู้พื้นฐานเกี่ยวกับนิพจน์ปรกติแล้ว หากต้องการศึกษาโปรดอ่านที่ตำรานิพจน์ปรกติ
ในไพวิกิพีเดีย อาจจะมีการใช้นิพจน์ปรกติในสองโอกาส คือในการใช้งานสคริปต์ replace.py หรือไม่ก็ใช้ในการเขียนสคริปต์ เนื่องจากไพวิกิพีเดียเขียนขึ้นด้วยภาษาไพทอน นิพจน์ปรกติของไพวิกิพีเดียจึงอาศัยนิพจน์ปรกติของภาษาไพทอนเป็นฐาน ส่งผลให้วากยสัมพันธ์เหมือนกันทุกประการ
นิพจน์ปรกติของภาษาไพทอนรองรับความสามารถของนิพจน์ปรกติของภาษาเพิร์ล ซึ่งมีความสามารถเพิ่มเข้ามามากมายเทียบกับมาตรฐานนิพจน์ปรกติทั่วไป
เพื่อแยกแยะระหว่างนิพจน์ปรกติทั่วไปกับข้อความ จะขอเขียนนิพจน์ปรกติคร่อมด้วยเครื่องหมาย /.../
แทนที่จะคร่อมด้วย "..." ตามปกติ อย่างไรก็ตาม ขอให้เข้าใจว่าในภาษาไพทอนนิพจน์ปรกติคร่อมด้วย "..." เหมือนข้อความทั่วไป
ต่อจากนี้จะแสดงถึงความสามารถหรือวากยสัมพันธ์เฉพาะของนิพจน์ปรกติบนไพวิกิพีเดีย/ภาษาไพทอนพอเป็นสังเขปเท่านั้น โดยจะไม่ลงลึกถึงการใช้งานนิพจน์ปรกติ และอาจมีตัวอย่างประกอบเพื่อให้เข้าใจได้ง่าย
อักขระควบคุม
[แก้ไข | แก้ไขต้นฉบับ]อักขระควบคุม (metacharacters) เป็นเครื่องหมายพิเศษที่ใช้ในการควบคุมรูปแบบของข้อความ ได้แก่
* + ? { } [ ] \ | ( )
ตารางด้านล่างนี้จะอธิบายเฉพาะอักขระที่มีความกำกวมในการใช้อย่างย่อเท่านั้น
อักขระ | คำอธิบาย |
---|---|
* + |
ความหมายเหมือนมาตรฐานนิพจน์ปรกติ โดยจะมีการจับแบบโลภ |
( ) |
ใช้ในการทำ group โปรดสังเกตว่าไม่ได้ใช้ \( \) เหมือน Basic Regular Expression แต่จะเหมือน Extended Regular Expression แทน
group ในลักษณะนี้เป็น capturing group ซึ่งสามารถใช้ร่วมกับการอ้างย้อนหลัง ได้ด้วย |
{ } |
ใช้ในการระบุจำนวนในการทำซ้ำ โปรดสังเกตว่าไม่ได้ใช้ \{ \} เหมือน Basic Regular Expression แต่จะเหมือน Extended Regular Expression แทน
|
การหนีอักขระด้วยเครื่องหมายทับหลัง
[แก้ไข | แก้ไขต้นฉบับ]อักขระสัญลักษณ์โดยส่วนใหญ่จะเป็นอักขระควบคุมด้วย จึงไม่สามารถใส่อักขระสัญลักษณ์โดด ๆ เพื่อให้นิพจน์ปรกติจับได้ ทางออกที่จะให้นิพจน์ปรกติจับกับตัวสัญลักษณ์เหล่านั้น คือการหนีอักขระดังกล่าว (escape character) โดยให้ใส่เครื่องหมายทับหลัง (\
) เข้าไปก่อนหน้าสัญลักษณ์ต่าง ๆ ลักษณะเดียวกันกับ \n \r \0
เช่นต้องการจะจับกับข้อความ "a+" ในข้อความ "qwea+ewqaaaqwe" หากใช้นิพจน์ปรกติ /a+/
จะเป็นการจับกับ "a" และ "aaa" ใน "qwea+ewqaaaqwe" แทน เพราะ +
เป็นอักขระควบคุมในการจับซ้ำ แต่ถ้าใช้นิพจน์ปรกติ /a\+/
ก็จะจับได้ "a+" ใน "qwea+ewqaaaqwe" ตามที่ต้องการ
เครื่องหมายทับหลัง (\
) ควรใส่เข้าไปก่อนหน้าเครื่องหมายทุกอย่างที่จะจับ (ควรใช้กับทุกอักขระที่ไม่ใช่ alphanumeric characters หรือก็คือ อักขระทุกตัวที่ไม่ใช่ตัวอักษร ตัวเลข และเครื่องหมายขีดล่าง ('_')) ถึงแม้ว่าสัญลักษณ์ดังกล่าวจะไม่ใช่อักขระควบคุมก็ตาม เพื่อให้ไม่เกิดความกำกวม อย่างไรก็ตามในทางปฏิบัติ หากการเขียนอักขระนั้นโดด ๆ ไม่ทำให้เกิดความหมายพิเศษ ก็ไม่จำเป็นจะต้องหนีอักขระดังกล่าวเสมอไป เช่น เครื่องหมาย # @ ! < >
สำหรับผู้เขียนภาษาไพทอน โปรดระวังไว้ว่าการดำเนินการหนีอักขระข้างต้น เป็นการหนีอักขระเพื่อนิพจน์ปรกติ แต่เมื่อนำนิพจน์ปรกติดังกล่าวมาเขียนในคู่อัญประกาศ "..." ก็ต้องทำการหนีอักขระเพื่อข้อความในภาษาไพทอนอีกรอบ เช่น หากต้องการจะจับกับอักขระ "\" ด้วยนิพจน์ปรกติ ลำพังตัวนิพจน์ปรกติเองก็ต้องเป็น /\\/
และเมื่อมาเขียนในภาษาไพทอนก็จะกลายเป็น "\\\\"
วิธีหลีกเลี่ยงการที่จะต้องหนีอักขระถึงสองรอบก็คือการเลือกใช้ข้อความดั้งเดิม (raw string) แทนที่ข้อความธรรมดา โดยใช้ r"..." แทนที่ "..." จากตัวอย่างข้างต้น หากต้องการจะจับกับอักขระ "\" ก็สามารถเขียนในภาษาไพทอนได้ว่า r"\\"
ชุดอักขระ
[แก้ไข | แก้ไขต้นฉบับ]ชุดอักขระ (character set) คือกลุ่มของอักขระที่นิยามมาให้แล้ว ตารางด้านล่างนี้แสดงชุดอักขระทั้งหมด
รหัส | ความหมาย | เทียบเท่า |
---|---|---|
. |
อักขระทุกตัวยกเว้น \n แต่หากอยู่ในโหมด dotall จะทำให้รวม \n ด้วย
| |
\d |
อักขระที่เป็นตัวเลข | /[0-9]/
|
\D |
อักขระที่ไม่ใช่อักขระตัวเลข | /[^0-9]/
|
\s |
อักขระที่เป็น whitespace | /[ \t\n\r\f\v]/
|
\S |
อักขระที่ไม่เป็น whitespace | /[^ \t\n\r\f\v]/
|
\w |
อักขระที่เป็นตัวอักษร ตัวเลข และ เครื่องหมายขีดล่าง ('_') | /[a-zA-Z0-9_]/
|
\W |
อักขระที่ไม่เป็นตัวอักษร ตัวเลข และ เครื่องหมายขีดล่าง ('_') | /[^a-zA-Z0-9_]/
|
นอกจากนี้ ยังมีชุดอักขระซึ่งมีความยาวเป็นศูนย์ กล่าวคือเป็นรูปแบบที่นิพจน์ปรกติต้องพยายามจับเพื่อดำเนินการต่อไป แต่เมื่อจับได้แล้วจะไม่นำอักขระเหล่านี้มาประมวลผล ตารางด้านล่างนี้แสดงชุดอักขระความยาวศูนย์ทั้งหมด
รหัส | ความหมาย |
---|---|
^ |
จับกับต้นข้อความ หากอยู่ในโหมด multiline จะทำให้จับต้นบรรทัดด้วย |
$ |
จับกับท้ายข้อความ หากอยู่ในโหมด multiline จะทำให้จับท้ายบรรทัดด้วย |
\A |
ในกรณีโหมดทั่วไปจะเหมือนกับ ^ คือจับกับต้นข้อความ แต่เมื่ออยู่ในโหมด multiline จะได้ว่า ^ จับกับต้นบรรทัดทั้งหลายด้วย แต่ \A จะยังคงจับกับต้นข้อความเหมือนเดิม
|
\Z |
ในกรณีโหมดทั่วไปจะเหมือนกับ $ คือจับกับท้ายข้อความ แต่เมื่ออยู่ในโหมด multiline จะได้ว่า $ จับกับท้ายบรรทัดทั้งหลายด้วย แต่ \Z จะยังคงจับกับท้ายข้อความเหมือนเดิม
|
\b |
เรียกว่า word boundary เป็นการจับในตำแหน่งระหว่าง \w กับ \W
|
\B |
จับในตำแหน่งที่อยู่ระหว่าง \w กับ \w หรือ \W กับ \W
|
หากต้องการจะจับกับอักขระสัญลักษณ์ต่าง ๆ ที่เป็นส่วนประกอบของชุดอักขระก็ต้องทำการหนีอักขระด้วยเช่นเดียวกัน นั่นก็คือถ้าจะจับกับ "." "\" "^" "$" ก็ต้องเขียนเป็น "\."
"\\"
"\^"
"\$"
แทน
โลภกับไม่โลภ
[แก้ไข | แก้ไขต้นฉบับ]การจับข้อความซ้ำโดยเครื่องหมาย *
หรือ +
จะพยายามจับให้ไกลที่สุดเท่าที่เป็นไปได้ไปก่อน
หากไม่ต้องการให้มีการจับแบบโลภ กล่าวคือพยายามหยุดการจับซ้ำทันที่ถ้าเป็นไปได้ ให้ใส่เครื่องหมาย ?
เข้าไปด้านหลังเครื่องหมาย *
หรือ +
เพื่อให้นิพจน์ปรกติพยายามที่จะหยุดการจับซ้ำทันทีที่สามารถจับอักขระถัดไปได้
ตัวอย่างเช่น สำหรับข้อความ "abcdbcdbc" ตัวอย่างการจับซ้ำแบบโลภ คือใช้นิพจน์ปรกติ /b.*d/
ซึ่งจะได้ "bcdbcd" จาก "abcdbcdbc"
หากต้องการจะจับให้ได้ "bcd" จาก "abcdbcdbc" แทนที่ "abcdbcdbc" ก็ต้องทำการจับแบบไม่โลภ นั่นคือใช้นิพจน์ปรกติ /b.*?d/
แทน
โหมด
[แก้ไข | แก้ไขต้นฉบับ]โหมด (mode, flag) เป็นลักษณะของการประมวลผลรูปแบบที่แตกต่างกันออกไป มีทั้งหมดดังนี้
โหมด | พารามิเตอร์ใน replace.py | ชื่อ flag | inline flag | คำอธิบาย |
---|---|---|---|---|
dotall | -dotall |
re.DOTALL |
s |
จะทำให้จุด . จับกับอักขระทุก ๆ ตัวรวมถึง \n ด้วย
|
ignorecase | -nocase |
re.IGNORECASE |
i |
จะไม่พิจารณาความแตกต่างระหว่างตัวอักษรพิมพ์เล็กกับพิมพ์ใหญ่ เช่น "aBcDe" = "AbCdE" |
multiline | ไม่มี | re.MULTILINE |
m |
อักขระควบคุม ^ และ $ จะจับกับต้น/ท้ายบรรทัดด้วย
|
verbose | ไม่มี | re.VERBOSE |
x |
ทำให้อักขระวรรค (' ') ไม่มีผลในการจับอักขระวรรค (ต้องใช้ \ อย่างเดียวเท่านั้น) และอักขระ '#' กลายเป็นการคอมเมนต์แทน (ยกเว้น '#' ที่อยู่ใน [...] ) การทำเช่นนี้ทำให้สามารถอ่านรูปแบบนิพจน์ปกติได้ง่ายยิ่งขึ้น
|
unicode | ไม่มี | re.UNICODE |
u |
ทำให้ \w \b \s \d จับกับอักขระยูนิโค้ดอื่น ๆ ด้วย
|
locale | ไม่มี | re.LOCALE |
L |
เกี่ยวกับการตั้ง locale ของเครื่องด้วย จึงไม่อธิบายและไม่แนะนำให้ใช้ |
ในการใช้งาน replace.py อาจมีบางโหมดที่สามารถกำหนดได้จากพารามิเตอร์ เช่น โหมด dotall และ ignorecase (nocase) และโหมดทั้งหลายสามารถเรียกใช้พร้อมกันได้ด้วย เช่น python replace.py -regex -dotall -nocase "Abc.*" "def" -page:xxx
หากหน้า xxx มีข้อความ
qweqweqweqweABCqwe qewqweqw e
ก็จะถูกแทนที่เป็น
qweqweqweqwedef
แต่บางโหมดเช่น multiline ไม่มีพารามิเตอร์ให้ใช้ในการเปิดโหมด ก็อาจใช้ inline flag ในการกำหนด โดยใส่ (?flag1flag2flag3...flagN)
ไว้ด้านหน้าสุดของข้อความ โดยที่ flag1 flag2 ... flagN เป็นค่า inline flag ที่ปรากฎในตารางด้านบน เช่น หากต้องการกำหนด multiline กับ ignorecase พร้อมกัน ก็อาจใช้ python replace.py -regex "(?mi)^ *Q" "z" -page:xxx
หากหน้า xxx มีข้อความ
QweqweqweqweABCqwe qewqweqw e d
ก็จะถูกแทนที่เป็น
zweqweqweqweABCqwe zewqweqw e d
สำหรับ การเขียนโปรแกรมภาษาไพทอน สามารถกำหนดโหมดได้ผ่านพารามิเตอร์เพิ่มเติม หากต้องการระบุหลาย ๆ โหมดพร้อมกัน ให้ใช้ vertical bar (|
) คั่น เช่น
re.findall("aBc.*", mystr, flags = re.DOTALL | re.IGNORECASE)
สำหรับการแทนที่ข้อความ ก็มีลักษณะแบบเดียวกัน
re.sub("aBc.*", "123", mystr, flags = re.DOTALL | re.IGNORECASE)
หรือไม่เช่นนั้นก็อาจใช้ inline flag คล้ายกับที่เคยกล่าวมาแล้วก็ได้
re.findall("(?si)aBc.*", mystr)
การอ้างย้อนหลัง
[แก้ไข | แก้ไขต้นฉบับ]การอ้างย้อนหลัง (backreference) คือความสามารถในการจับข้อความจากข้อความค้นหา มาใส่อีกครั้งในข้อความค้นหาด้วยกันเอง หรือจะจับมาใส่ในข้อความแทนที่ก็ได้ โดยกำหนดข้อความที่จะจับจาก capturing group ((...)
) ในข้อความค้นหา และใช้รหัสอ้างย้อนหลัง (\1 \2 \3 ...
) ในการอ้างย้อนหลัง ซึ่งรหัสอ้างย้อนหลังนี้จะปรากฎใน ข้อความค้นหา หรือ ข้อความแทนที่ ก็ได้
สำหรับข้อความที่ต้องการจะจัดกลุ่ม แต่ไม่ต้องการจับให้มาอ้างในภายหลัง สามารถใช้ non-capturing group ((?:...)
) ได้
สำหรับผู้เขียนภาษาไพทอน ตามที่ได้กล่าวมาแล้วว่าข้อความปกติต้องมีการหนีตัวอักษรถ้ามีสัญลักษณ์ โปรดตระหนักว่า \number
เมื่อ number เป็นเลขฐาน 8 จะกลายเป็นอักขระในเลขฐาน 8 แทน ดังนั้นถ้าจะใช้การอ้างย้อนหลัง ก็อย่าลืมที่จะหนีอักขระ หรือไม่ก็หันไปใช้ข้อความดั้งเดิมแทน
ตัวอย่างการใช้การอ้างย้อนหลังกับรูปแบบด้วยกันเอง เช่น ต้องการจะจับข้อความที่มีวรรณยุกต์ตัวเดียวกันซ้อน ได้แก่ "่่" "้้" "๊๊" "๋๋" ก็อาจเขียนเป็นนิพจน์ปรกติได้ว่า /([่้๊๋])\1/
ซึ่งจะทำให้ในข้อความ "เทวะ, อันข้้านี้ไซร้ มานี่้อย่่างไร บทราบสำนึกสักนิด" มีการจับ "้้" และ "่่" จาก "เทวะ, อันข้้านี้ไซร้ มานี่้อย่่างไร บทราบสำนึกสักนิด" สังเกตว่า "่้" จาก "มานี่้" จะไม่ถูกจับเนื่องจาก "่" กับ "้" เป็นคนละอักขระกัน แต่หากใช้นิพจน์ปรกติ /[่้๊๋]{2}/
ก็จะจับกรณีนี้ด้วย
ตัวอย่างการใช้การอ้างย้อนหลังกับข้อความแทนที่ เช่น หากต้องการจะแทนที่ {{เรตติง-a|b}} → {{เรตติง|b|a}} (ตัวอย่างคือ {{เรตติง-5|3}} → {{เรตติง|3|5}}) ก็อาจกำหนดข้อความค้นหาเป็น /\{\{เรตติง-(.*?)|(.*?)\}\}/
และข้อความแทนที่เป็น "{{เรตติง|\2|\1}}"
การยืนยันด้วยอักขระความยาวศูนย์
[แก้ไข | แก้ไขต้นฉบับ]การยืนยันด้วยอักขระความยาวศูนย์ (zero-width assertion) หรือที่รู้จักกันในชื่อ lookaround เป็นรูปแบบซึ่งนิพจน์ปรกติจะพยายามจับข้อความนั้น แต่ไม่นับข้อความนั้นเป็นข้อความที่ค้นเจอ ประกอบไปด้วย
ประเภท | รูปแบบสัญลักษณ์ |
---|---|
positive lookahead | (?=)
|
negative lookahead | (?!)
|
positive lookbehind | (?<=)
|
negative lookbehind | (?<!)
|
ตัวอย่างเช่น สมมุติมีข้อความ
<b>ข้อความตัวหนา</b> สามารถกระทำได้โดยเขียน <nowiki><b>ข้อความตัวหนา</b></nowiki>
หากต้องการจะจับ <b> หรือ </b> โดยที่ไม่ยุ่งกับข้อความที่อยู่ใน <nowiki>...</nowiki> อาจมองปัญหาใหม่เป็นการจับ <b> หรือ </b> โดยที่จะต้องไม่มี </nowiki> เป็นข้อความอยู่ด้านหลัง ยกเว้นกรณีที่เจอ <nowiki> ก่อน (หากเจอ <nowiki> ก่อนแสดงว่าจริง ๆ แล้วยังไม่ได้เริ่มแท็ก)
จากปัญหาข้างต้น สามารถเขียนนิพจน์ปรกติออกมาได้ว่า </?b>(?!(?:(?!</?nowiki>).)*</nowiki>)
ลำดับที่ | นิพจน์ปรกติ | คำอธิบาย |
---|---|---|
1 | </?b> |
จับกับ <b> หรือ </b> |
2 | (?! |
เริ่ม negative lookahead |
3 | (?: |
เริ่ม non-capturing group |
4 | (?!</?nowiki>) |
negative lookahead ให้ ไม่จับกับ <nowiki> หรือ </nowiki> |
5 | . |
นอกจากนั้นให้จับ |
6 | )* |
จบ non-capturing group และให้จำ group นี้ซ้ำไปเรื่อย ๆ |
3 - 6 | กล่าวคือให้จับกับอะไรก็ได้ไปเรื่อย ๆ แต่เมื่อเจอ <nowiki> หรือ </nowiki> แล้วให้หยุด | |
7 | </nowiki>) |
จับกับ </nowiki> และจบ negative lookahead |
1 - 7 | กล่าวคือให้เริ่มจับ <b> หรือ </b> แล้วทดสอบดูว่าข้างหน้ามี </nowiki> หรือไม่ ถ้ามีให้การจับล้มเหลว แต่ถ้าเจอ <nowiki> ก่อนให้การทดสอบนี้ล้มเหลวแทน นั่นคือสามารถจับได้ |