ความแตกต่างระหว่าง [0-9], [[: digit:]] และ \ d


35

ในบทความวิกิพีเดียนิพจน์ปกติมันก็ดูเหมือนว่า[[:digit:]]= = [0-9]\d

สถานการณ์อะไรบ้างที่พวกเขาไม่เท่ากัน? อะไรคือความแตกต่าง?

หลังจากการวิจัยบางอย่างฉันคิดว่าความแตกต่างอย่างหนึ่งคือการแสดงออกของวงเล็บปีกกา[:expr:]ขึ้นอยู่กับสถานที่


3
ไม่บทความวิกิพีเดียที่คุณเชื่อมโยงกับการตอบคำถามของคุณ? ตัวประมวลผล / เอ็นจินนิพจน์ทั่วไปที่แตกต่างกันสนับสนุนไวยากรณ์ต่าง ๆ สำหรับคลาสอักขระ (เหนือสิ่งอื่นใด)
igal

@igal wiki กล่าวว่ามีความแตกต่าง แต่ไม่ได้ให้รายละเอียดมากนัก ฉันกำลังถามรายละเอียดบางอย่างเช่น isaac, thrig กล่าว ฉันสนใจในความแตกต่างของ grep, sed, awk ... ไม่ว่าจะเป็น GNU หรือไม่
harbinn

คำตอบ:


40

ใช่มันคือ[[:digit:]]~ [0-9]~ \d(โดยที่ ~ หมายถึง aproximate)
ในภาษาการเขียนโปรแกรมส่วนใหญ่ (ที่รองรับ) \d[[:digit:]](เหมือนกัน) น้อยกว่ากัน(ไม่อยู่ใน POSIX แต่มันอยู่ใน GNU )
\d[[:digit:]]grep -P

มีตัวเลขจำนวนมากใน UNICODEตัวอย่างเช่น:

123456789 # Hindu-Arabic เลขอารบิค
٠١٢٣٤٥٦٧٨٩ # ARABIC-INDIC
۰۱۲۳۴۵۶۷۸۹ # EXTENDED ARABIC-INDIC/PERSIAN
߀߁߂߃߄߅߆߇߈߉ # NKO DIGIT
०१२३४५६७८९ # DEVANAGARI

ซึ่งทั้งหมดอาจจะรวมอยู่ในหรือ[[:digit:]]\d

แต่โดยทั่วไปเพียงตัวเลข[0-9] ASCII0123456789


มีหลายภาษา: Perl, Java, Python, C. ซึ่ง[[:digit:]](และ\d) เรียกร้องให้มีความหมายเพิ่มเติม ตัวอย่างเช่นรหัส perl นี้จะตรงกับตัวเลขทั้งหมดจากด้านบน:

$ a='0123456789 ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९'

$ echo "$a" | perl -C -pe 's/[^\d]//g;' ; echo
0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९

ซึ่งเทียบเท่ากับเลือกอักขระทั้งหมดที่มีคุณสมบัติ Unicode ของNumericและdigits:

$ echo "$a" | perl -C -pe 's/[^\p{Nd}]//g;' ; echo
0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९

grep ใดที่สามารถทำซ้ำได้ (เวอร์ชันเฉพาะของ pcre อาจมีรายการรหัสจุดตัวเลขภายใน Perl ที่แตกต่างกันกว่า Perl):

$ echo "$a" | grep -oP '\p{Nd}+'
0123456789
٠١٢٣٤٥٦٧٨٩
۰۱۲۳۴۵۶۷۸۹
߀߁߂߃߄߅߆߇߈߉
०१२३४५६७८९

เปลี่ยนเป็น [0-9] เพื่อดู:

$ echo "$a" | grep -o '[0-9]\+'
0123456789

POSIX

สำหรับเฉพาะ POSIX BRE หรือ ERE: ไม่สนับสนุน (ไม่อยู่ใน POSIX แต่อยู่ใน GNU ) เป็นสิ่งจำเป็นโดย POSIX เพื่อให้สอดคล้องกับคลาสตัวละครหลักซึ่ง ISO C จำเป็นต้องเป็นอักขระตั้งแต่ 0 ถึง 9 และไม่มีอะไรอื่น ดังนั้นเพียงใน C สถานที่เกิดเหตุทั้งหมด, , และหมายถึงเหมือนกันว่า ไม่เคยมีใครตีความเป็นไปได้ที่มีอยู่ในระบบสาธารณูปโภคอื่น ๆ และมันก็เป็นเรื่องธรรมดาที่จะหมายถึงเฉพาะ รับการสนับสนุนจากสาธารณูปโภคไม่กี่
\dgrep -P[[:digit:]][0-9][0123456789]\d[[:digit:]][0123456789][[:digit:]][0123456789]\d

สำหรับ[0-9]ความหมายของการแสดงออกช่วงที่กำหนดโดย POSIX ในสถานที่ C; ในสถานที่อื่น ๆ มันอาจแตกต่างกัน (อาจเป็นคำสั่ง codepoint หรือคำสั่งการเปรียบเทียบหรืออย่างอื่น)

เปลือกหอย

การใช้งานบางอย่างอาจเข้าใจช่วงที่แตกต่างจากลำดับ ASCII ธรรมดา (ตัวอย่างเช่น ksh93):

$ LC_ALL=en_US.utf8 ksh -c 'a="'"$a"'";echo "${a//[0-9]}"'
  ۹ ߀߁߂߃߄߅߆߇߈߉ ९

และนั่นคือแหล่งที่มาของข้อผิดพลาดที่รอให้เกิดขึ้น


ในทางปฏิบัติในระบบ POSIX iswctype()และ BRE / ERE / ไวด์การ์ดในยูทิลิตี้ POSIX [0-9] และ [[: หลัก:]] จับคู่กับ 0123456789 เท่านั้น และจะมีการอธิบายอย่างชัดเจนในการแก้ไขมาตรฐานครั้งต่อไป
Stéphane Chazelas

ผมไม่ทราบว่าperlของ\dในโหมด Unicode ตรงกันบนตัวเลขทศนิยมจากสคริปต์อื่น ๆ ขอบคุณสำหรับสิ่งนั้น ด้วย PCRE ให้ดู(*UCP)ใน GNU grep -Po '(*UCP)\d'หรือgrep -Po '(*UCP)[[:digit:]]คลาสที่ต้องยึดตามคุณสมบัติ Unicode
Stéphane Chazelas

ฉันยอมรับว่า[:digit:]ไวยากรณ์จะแนะนำให้คุณต้องการใช้การแปลเป็นสิ่งที่ผู้ใช้คิดว่าเป็นหลัก ฉันไม่เคยใช้[:digit:]เพราะในทางปฏิบัติเหมือนกับ[0-9]และในกรณีใด ๆ ฉันต้องการจับคู่กับ 0123456789 เสมอฉันไม่เคยตั้งใจจะจับคู่٠١٢٣٤٥٦٧٨٩และฉันไม่สามารถนึกถึงกรณีการใช้งานที่ใคร ๆ ก็อยากเทียบเลขทศนิยม ในสคริปต์ใด ๆ ที่มีโปรแกรมอรรถประโยชน์ POSIX เห็นแล้วยังอภิปรายปัจจุบันเกี่ยวกับ[:blank:]ใน zsh ML คลาสของตัวละครเหล่านั้นยุ่งเหยิงไปหน่อย
Stéphane Chazelas

13

ขึ้นอยู่กับว่าคุณกำหนดตัวเลขอย่างไร [0-9]มีแนวโน้มที่จะเป็นแค่ ASCII (หรืออาจเป็นอย่างอื่นที่ไม่ใช่ ASCII หรือ superset ของ ASCII แต่มีตัวเลข 10 หลักเหมือนกันใน ASCII เท่านั้นที่มีการแทนค่าบิตที่แตกต่างกัน (EBCDIC)) \dในทางกลับกันอาจเป็นเพียงตัวเลขหลัก (Perl รุ่นเก่าหรือ Perl รุ่นใหม่ที่มี/aการเปิดใช้งานการแสดงออกปกติ) หรืออาจเป็นการจับคู่แบบ Unicode \p{Digit}ซึ่งค่อนข้างเป็นชุดตัวเลขขนาดใหญ่กว่า[0-9]หรือ/\d/aจับคู่

$ perl -E 'say "match" if 42 =~ m/\d/'
match
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/\d/'
match
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/\d/a'
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/[0-9]/'
$ 

perldoc perlrecharclass สำหรับข้อมูลเพิ่มเติมหรือดูเอกสารประกอบของภาษาที่เป็นปัญหาเพื่อดูว่ามันทำงานอย่างไร

แต่เดี๋ยวก่อนมีอีกมาก! สถานที่เกิดเหตุนอกจากนี้ยังอาจแตกต่างกันไปในสิ่งที่\dตรงกับดังนั้น\dอาจไม่ตรงกับตัวเลขน้อยกว่าชุดที่สมบูรณ์ของ Unicode ดังกล่าวและ (หวังว่าปกติ) [0-9]นอกจากนี้ยังมี สิ่งนี้คล้ายกับความแตกต่างใน C ระหว่างisdigit(3)( [0-9]) และisnumber(3)( [0-9รวมถึงสิ่งอื่นจากสถานที่)

อาจมีการโทรที่สามารถรับค่าของตัวเลขแม้ว่าจะไม่ใช่[0-9]:

$ perl -MUnicode::UCD=num -E 'say num(4)'
4
$ perl -MUnicode::UCD=num -E 'say num("\N{U+09EA}")'
4
$ 

ฉันคิดว่าisnumber()เป็นสิ่งที่ BSD อย่างน้อยตามหน้าคนที่ดูเหมือนดังนั้น
ilkkachu

ฉันมีความลำเอียง BSD ใช่แล้ว
1818 ที่ 18:18

การตั้งค่าสถานะ / a เป็นตัว จำกัด เฉพาะเพื่อลดรายการของตัวเลข Unicode ให้ตรงกันเท่านั้น…ตัวปรับ / a สามารถใช้เพื่อบังคับ \ d ให้ตรงกับ ASCII 0 ถึง 9เท่านั้น [0-9]เช่นนี้มันจะบังคับให้ตรงกับที่เหมือนกันและเพียง
Isaac

5

ความหมายที่แตกต่างกัน[0-9], [[:digit:]]และ\dถูกนำเสนอในคำตอบอื่น ๆ ที่นี่ฉันต้องการเพิ่มความแตกต่างในการใช้งานของเครื่องมือ regex

            [[:digit:]]    \d
grep -E               ✓     ×
grep -P               ✓     ✓
sed                   ✓     ×
sed -E                ✓     ×

ดังนั้น[[:digit:]]การทำงานเสมอ , \dขึ้นอยู่กับ ในคู่มือของ grep มีการกล่าวถึงว่า[[:digit:]]อยู่0-9ในCโลแคล

PS1: ถ้าคุณรู้มากขึ้นโปรดขยายตาราง

PS2: GNU grep 3.1 และ GNU 4.4 ใช้สำหรับทดสอบ


2
1) มีหลายรุ่นgrepและsedมีความแตกต่างมากที่สุดระหว่าง GNU กับรุ่นอื่น ๆ คำตอบนี้อาจมีประโยชน์มากขึ้นหากกล่าวถึงรุ่นที่grepและsedมันหมายถึง หรือแหล่งที่มาของตารางนั้นคืออะไรสำหรับเรื่องนั้น 2) ตารางนั้นอาจถูกถอดความเป็นข้อความเนื่องจากไม่มีสิ่งใดที่ต้องการให้เป็นรูปภาพ
ilkkachu

@ilkkachu 1) GNU grep 3.1 ล่าสุดและ GNU 4.4 ใช้สำหรับการทดสอบ 2) ฉันไม่สร้างตาราง ดูเหมือนว่า @ muru แปลงตารางเป็นรูปแบบข้อความที่สวยงาม
harbinn

@harbinn โปรดแก้ไขให้เป็นคำตอบของคุณ
Dan D.

@DanD เพิ่มข้อมูลรุ่นแล้ว ขอบคุณสำหรับความสนใจ
harbinn

1
โปรดทราบว่าreโมดูลไพ ธ อนในตัวไม่รองรับ [[: หลัก:]] แต่การเพิ่มในregexไลบรารี่รองรับมันดังนั้นฉันจะงอเล็กน้อยที่ใช้งานได้เสมอ มันมักจะทำงานในสถานการณ์การร้องเรียน posix
Steve Barnes

4

ความแตกต่างทางทฤษฎีได้รับการอธิบายอย่างดีในคำตอบอื่น ๆ ดังนั้นจึงยังคงอธิบายถึงความแตกต่างในทางปฏิบัติ

ต่อไปนี้เป็นกรณีการใช้งานทั่วไปที่มากขึ้นสำหรับการจับคู่ตัวเลข


การดึงข้อมูล One-shot

บ่อยครั้งเมื่อคุณต้องการกระทืบตัวเลขบางตัวตัวเลขเหล่านั้นจะอยู่ในไฟล์ข้อความที่จัดรูปแบบอย่างเชื่องช้า คุณต้องการแยกมันเพื่อใช้ในโปรแกรมของคุณ คุณอาจบอกรูปแบบตัวเลข (โดยดูที่ไฟล์) และตำแหน่งปัจจุบันของคุณดังนั้นคุณสามารถใช้แบบฟอร์มใด ๆ ได้ตราบใดที่มันทำงานเสร็จ \dต้องใช้การกดแป้นที่น้อยที่สุดดังนั้นจึงใช้กันมาก

อินพุตฆ่าเชื้อ

คุณมีการป้อนข้อมูลของผู้ใช้ที่ไม่น่าเชื่อถือ (อาจมาจากเว็บฟอร์ม) และคุณต้องแน่ใจว่าไม่มีความประหลาดใจใด ๆ บางทีคุณอาจต้องการเก็บไว้ในเขตข้อมูลตัวเลขในฐานข้อมูลหรือใช้เป็นพารามิเตอร์ในคำสั่งเชลล์เพื่อเรียกใช้บนเซิร์ฟเวอร์ ในกรณีนี้คุณต้องการ[0-9]เพราะมันเป็นข้อ จำกัด และคาดเดาได้มากที่สุด

การตรวจสอบข้อมูล

คุณมีข้อมูลบางส่วนที่คุณจะไม่ใช้กับสิ่งที่ "อันตราย" แต่มันก็ดีถ้ารู้ว่ามันมีจำนวนมาก ตัวอย่างเช่นโปรแกรมของคุณอนุญาตให้ผู้ใช้ป้อนที่อยู่และคุณต้องการไฮไลท์ตัวพิมพ์ที่เป็นไปได้หากข้อมูลที่ป้อนไม่มีหมายเลขบ้าน ในกรณีนี้คุณอาจต้องการให้กว้างที่สุดเท่าที่[[:digit:]]จะเป็นไปได้


ดูเหมือนจะเป็นกรณีการใช้งานที่พบมากที่สุดสามกรณีสำหรับการจับคู่แบบหลัก หากคุณคิดว่าฉันพลาดสิ่งสำคัญโปรดส่งความคิดเห็น


เป็นคนดี, ปัญหาเกี่ยวกับความปลอดภัยเกี่ยวข้องเช่น ReDoS หรือคนอื่น ๆ
frams
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.