מחלקות תווים#
מחלקת תווים מתאימה בדיוק לתו אחד, הנבחר מתוך קבוצה שמגדירים. בעוד a מילולי מתאים רק ל־a, המחלקה [abc] מתאימה לכל אחד מתוך a, b או c.
/cat/; # matches 'cat'
/[bcr]at/; # matches 'bat', 'cat', or 'rat'
/item[0123456789]/; # matches 'item0' through 'item9'
המחלקה עדיין צורכת תו אחד מן המחרוזת. [bcr]at לעולם אינו מתאים ל־at (אין אות), ולעולם אינו מתאים ל־brat (שתי אותיות במקום שבו המחלקה מצפה לאחת).
טווחים#
בתוך […], מקף בין שני תווים מציין טווח רציף בקבוצת התווים הבסיסית:
/[0-9]/; # any ASCII digit
/[a-z]/; # any ASCII lowercase letter
/[a-zA-Z]/; # any ASCII letter
/[0-9a-fA-F]/; # any hex digit
ניתן לשלב טווחים עם תווים בודדים:
/[0-9bx-z]aa/; # matches '0aa'..'9aa', 'baa', 'xaa', 'yaa', 'zaa'
מקף שהוא ראשון או אחרון בתוך המחלקה הוא מילולי:
/[-ab]/; # matches '-', 'a', or 'b'
/[ab-]/; # same
שלילה#
סימן ה־caret ^ כתו הראשון בתוך […] הופך את המחלקה לשלילה:
/[^a]/; # any character except 'a'
/[^0-9]/; # any non-digit
caret במקום אחר הוא מילולי:
/[a^]/; # matches 'a' or '^'
מחלקה שלילית עדיין מתאימה לתו אחד — [^a] אינו מתאים למחרוזת הריקה; הוא דורש תו אחד שאינו a.
תווים מיוחדים בתוך מחלקה#
בתוך […] קבוצת התווים המיוחדים מצטמצמת ל־- ] \ ^ $ (ולמתחם התבנית). האחרים — ., *, +, ?, (, ), {, }, | — הם מילוליים במחלקה:
/[.+*]/; # matches a literal '.', '+', or '*'
/[()]/; # matches '(' or ')'
כדי להתאים ] בתוך המחלקה, יש לבצע לו escape או להציבו ראשון (לאחר ^ מוביל אם קיים):
/[\]]/; # matches ']'
/[]ab]/; # matches ']', 'a', or 'b'
$ ו־\ מעט מסורבלים מכיוון שהם מקיימים אינטראקציה עם השיבוץ וה־escape:
my $x = 'bcr';
/[$x]at/; # matches 'bat', 'cat', or 'rat' — interpolated
/[\$x]at/; # matches '$at' or 'xat' — '$' is literal
/[\\$x]at/; # matches '\at' plus interpolation of $x
\b בתוך מחלקת תווים פירושו backspace (\x08), ולא ״גבול מילה״. מחוץ למחלקה הוא היגד גבול המילה. זוהי מלכודת המשמעות הכפולה הנפוצה ביותר בתחביר ה־regex — ראו את פרק anchors and assertions לצורת הגבול.
מחלקות מקוצרות#
לכמה מחלקות נפוצות יש שמות מקוצרים השמישים גם בתוך […] וגם מחוצה ל־[…]:
קיצור | מתאים |
|---|---|
| ספרה |
| תו שאינו ספרה |
| תו מילה (אלפאנומרי או |
| תו שאינו תו מילה |
| רווח לבן (רווח, טאב, |
| תו שאינו רווח לבן |
| רווח לבן אופקי (רווח, טאב, Unicode) |
| תו שאינו רווח לבן אופקי |
| רווח לבן אנכי ( |
| תו שאינו רווח לבן אנכי |
| שבירת שורה: |
| כל תו פרט ל־ |
תחת Unicode (ברירת המחדל), \d, \w, \s מתאימים ליותר מאשר ASCII בלבד. \d מתאים לכל ספרת Unicode (ספרות דווה־נאגרי, ספרות ערביות־הודיות, ועוד רבות), \w מתאים לכל אות בכל מערכת כתב בתוספת סימני ניקוד וסימני פיסוק מחברים, ו־\s מוסיף תווי רווח של Unicode כגון רווח שאינו שובר שורה.
כדי להגביל אלה ל־ASCII, יש להוסיף את המתאם /a או להשתמש בטווחים מפורשים כמו [0-9] ו־[A-Za-z_0-9].
"item0" =~ /\w\w\w\w\d/; # matches
"abc\x{0660}" =~ /\w\w\w\d/; # matches: U+0660 is an Arabic-Indic zero
"abc\x{0660}" =~ /\w\w\w\d/a;# does not match under /a
\R הוא הקיצור לשבירת שורה — הוא מתאים לכל אחד מרצפי שבירת השורה המוכרים כטוקן יחיד. שימושי לניתוח טקסט שעשוי להכיל CRLF, LF או מסיימי שורה נדירים יותר לסירוגין. שלא כמחלקה, \R עשוי להתאים לשני תווים (במקרה של CRLF), ולכן לא יכול להופיע בתוך […].
\N (אות גדולה) פירושו ״כל תו פרט ל־\n״, ואינו מושפע מן המתאם /s. זוהי המשמעות הכפולה שיש להיזהר ממנה: \N{NAME} (עם סוגריים מסולסלים) הוא escape לתו Unicode בעל שם (ראו פרק unicode); \N חשוף הוא מחלקת לא־שורה־חדשה.
הנקודה#
. מתאים לכל תו יחיד פרט לשורה חדשה. תחת המתאם /s (המכוסה בפרק modifiers), . מתאים גם לשורה חדשה:
"a\nb" =~ /a.b/; # does not match
"a\nb" =~ /a.b/s; # matches
כאשר רוצים ״כל תו כולל שורה חדשה״ ללא /s, האידיום הקלאסי הוא [\s\S] (או [\d\D]):
"a\nb" =~ /a[\s\S]b/; # matches without /s
הטריק הוא שכל תו הוא או רווח לבן או לא־רווח־לבן; המחלקה מכסה את שניהם.
הרכבת מחלקות#
ניתן לערבב קיצורים, טווחים ותווים בודדים בתוך מחלקה אחת:
/[\d\s]/; # a digit or whitespace
/[A-Z\d_]/; # uppercase letter, digit, or underscore
/[a-zA-Z\d]/; # letter or digit (ASCII)
חוק דה־מורגן חשוב: [^\d\w] אינו [\D\W]. הראשון דורש שהתו יהיה גם לא־ספרה וגם לא־תו־מילה. אבל כל ספרה היא תו מילה, ולכן [^\d\w] מתפשט ל־[^\w], כלומר \W. יש להיזהר בעת שילוב קיצורים שליליים.
מחלקות POSIX#
מחלקות תווים של POSIX משתמשות בצורה [:name:] ופועלות רק בתוך […]:
POSIX | שקול |
|---|---|
| אלפביתי |
| אלפאנומרי |
| ספרה (כמו |
| תו מילה (הרחבה של Perl) |
| רווח לבן (כמו |
| אותיות גדולות |
| אותיות קטנות |
| ספרה הקסדצימלית |
| 0x00–0x7F |
| תו בקרה |
| ניתן להדפסה, לא רווח |
| ניתן להדפסה, כולל רווח |
| פיסוק |
| רווח או טאב |
לשלילת מחלקת POSIX יש להוסיף ^ בתוך הנקודתיים:
/[[:^digit:]]/; # same as \D
/[[:alpha:][:digit:]]/; # letter or digit — equivalent to \w minus '_'
מחלקות POSIX מצייתות לאותם כללי Unicode־מול־ASCII כמו הקיצורים: ללא /a, [:alpha:] הוא קבוצת התווים האלפביתיים של Unicode.
POSIX מגדיר גם שני מבנים קשורים שמיושמים לעיתים נדירות:
רכיבי ארגון
[.span-ll.]— מתאים לרכיב סידור רב־תווי כיחידה אחת (למשלllבספרדית מבחינה היסטורית).מחלקות שקילות
[[=n=]]— מתאים לכל תו שהוא שקול תחת כללי הסידור של ההגדרה האזורית (למשל וריאנטים מנוקדים שלn).
Perl מזהה את התחביר אך מתייחסת לשתי הצורות כתווים מילוליים. בפועל אין סקריפט נייד הסומך עליהם; הם מתועדים לשם השלמות.
תכונות Unicode#
Unicode מגדיר אלפי תכונות. הכתיב הוא \p{Name} עבור ״בעל תכונה זו״ ו־\P{Name} עבור ״אינו בעל תכונה זו״:
/\p{Lu}/; # any uppercase letter, any script
/\p{Greek}/; # any character in the Greek script
/\p{Number}/; # any numeric character
/\P{ASCII}/; # any non-ASCII character
קיימים כינויים קצרים בני אות אחת לתכונות נפוצות, ובהם הסוגריים המסולסלים מושמטים: \pL הוא אות, \pN מספר, \pP פיסוק. \p{L} זהה ל־\pL.
פרק unicode מכסה את התכונות לפרטיהן, כולל הצורה המורכבת \p{Name=Value}, אשכול הגרפמות \X, ומתאמי ערכת התווים /a, /u, /l, /d.
מחלקות סוגריים מרובעים מורחבות — (?[ ])#
תחביר ה־[…] הסטנדרטי מטפל היטב באיחודים (״כל אחד מהתווים האלה״) אך אין לו פעולות קבוצתיות על מחלקות. הצורה המורחבת (?[ … ]) כן:
אופרטור | משמעות |
|---|---|
| איחוד (אותם תווים שיש לכל אחד מהאופרנדים) |
| חיתוך (בשניהם) |
| הפרש (בשמאלי, לא בימני) |
| הפרש סימטרי (באחד אך לא בשניהם) |
| משלים (כל דבר פרט) |
רווח לבן בתוך (?[…]) מתעלמים ממנו, ולכן האופרטורים נקראים כחשבון.
# Greek letters only:
/(?[ \p{Greek} & \p{Letter} ])/;
# Letters that are not Latin:
/(?[ \p{Letter} - \p{Latin} ])/;
# Hex digit, but not 'a' through 'f':
/(?[ [0-9A-Fa-f] - [a-f] ])/;
המבנה שימושי במיוחד בעת שילוב תכונות Unicode החופפות. בלעדיו, אותם ביטויים היו דורשים lookaround מילולי או לוגיקה מחוץ לתבנית.
(?[…]) הוא בעצמו מחלקת תווים — הוא צורך תו אחד וניתן לכמת אותו:
/(?[ \p{Letter} & \p{ASCII} ])+ /x; # ASCII letters
הסתייגות: (?[…]) הוא מנתח קטן משלו בתוך מנתח ה־regex. בתוכו, רק אופרטורים ואופרנדים ספציפיים מזוהים. טעויות מפיקות שגיאות הידור ספציפיות, ובתורן פירוש הדבר שמצב מחמיר (use re 'strict') תופס יותר ביטויי מחלקה שגויים בעת שימוש בצורה המורחבת.
מחלקה שלילית מנצחת את .*?#
תבנית נפוצה אצל מתחילים: שימוש ב־.*? (. לא־חמדן) להתאמת כל דבר עד למתחם, כמו <.*?>. התבנית עובדת על הקלטים שעליהם נבדקה; על קלט עוין אינה עובדת.
"<a> </a>" =~ /<.*?>/; # matches '<a>' — fine
"<a> <b>foo" =~ /<.+?>foo/; # matches '<a> <b>foo' — bad
במקרה השני המנוע התאים תחילה ל־<a>, אחר כך נדרש ל־foo אך מצא רווח. תחת לחץ החזרה לאחור, ה־.+? הלא־חמדן נאלץ להתרחב, וצרך בשמחה את ה־> של <a> ואת הרווח עד שה־foo הסתדר. מחלקת התווים השלילית אינה יכולה לוותר באופן זה:
"<a> <b>foo" =~ /<[^>]+>foo/; # matches '<b>foo' — [^>]+ refuses to cross '>'
שתי סיבות להעדיף [^>]+ על פני .+? בכל פעם שהמתחם הוא תו יחיד:
נכונות: המחלקה השלילית היא חסם קשיח; הצורה הלא־חמדנית היא העדפה.
ביצועים: מחלקה שלילית משתתפת באופטימיזציית החזרה הפשוטה;
.+?אינו (המנוע נאלץ לצאת מהלולאה הפנימית בכל איטרציה כדי לבדוק את מה שבא אחר כך). על קלטים ארוכים זה משמעותי.
דוגמה מעובדת: regex לכתובת IP#
תרגיל ה״ספציפיות מול מורכבות״ הקאנוני. חמש איטרציות, כל אחת מתקנת קטגוריה אחת של עמימות:
1. נאיבי. ״ארבע קבוצות ספרות מופרדות בנקודה.״
/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/;
מתאים ל־and then.....? בלי בעיה — כל קבוצה היא אופציונלית, והתבנית מסופקת על־ידי ארבע נקודות וכלום מעבר לזה.
2. דרישת ספרות. עיגון התבנית.
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/;
מתאים ל־1234.5678.9101112.131415. בכל קבוצה יש ספרות אך אין חסם עליון על מניינן.
3. הגבלת מניין הספרות, גרוע.
/^\d{3}\.\d{3}\.\d{3}\.\d{3}$/;
מתאים ל־192.168.001.001 אך דוחה את 1.2.3.4 — אפסים מובילים אינם תמיד נכתבים.
4. מתן אפשרות ל־1 עד 3 ספרות.
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
כעת מתאים ל־1.2.3.4 ודוחה את 1234.5.6.7. אך הוא גם מתאים ל־999.999.999.999 — מחוץ לטווח 0–255.
5. נכון מבחינת טווח.
/^(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.
(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.
(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.
(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/x;
כל קבוצה היא אחת מ־: 25[0-5] (250–255), 2[0-4]\d (200–249), או [01]?\d\d? (0–199 בצורות שונות). התבנית מתאימה כעת בדיוק למחרוזות המייצגות כתובת IPv4 תחבירית תקפה.
הלקח אינו ״לשנן את ה־regex הזה״. אלא התהליך: כל איטרציה הידקה סוג מסוים של עמימות. ה־regex הנכון לבעיה הוא זה שמכניס בדיוק את הקלטים הנכונים ודוחה את כל היתר, ומגיעים לכך רק על־ידי שאלה מה ה־regex הקודם באמת התיר.
השוואה בין מנועים: מחלקות מקוצרות#
הקיצורים \d, \w, \s אינם ניידים. בפרק cross-engine הטבלה המלאה; השורות הרלוונטיות:
קיצור | Perl 5.42 (ברירת מחדל) | PCRE2 | Emacs | POSIX BRE / ERE | RE2 / Go (ברירת מחדל) |
|---|---|---|---|---|---|
| ASCII (או Unicode תחת | ASCII | לא (להשתמש ב־ | לא | ASCII; Unicode תחת |
| ASCII או Unicode | ASCII | כן (מונחה־טבלת־תחביר) | לא | ASCII; Unicode תחת |
| ASCII או Unicode | ASCII | כן | לא | ASCII; Unicode תחת |
גבול מילה | כן | כן | כן ( | לא | כן |
| כן | כן | לא | לא | לא |
שני דברים להפנים:
ל־POSIX BRE ול־ERE חסרים
\d,\w,\sלחלוטין. סקריפטי מעטפת ניידים משתמשים ב־[0-9],[[:alnum:]_],[[:space:]].ל־Emacs יש
\wו־\sאך אין\d. תחביר ה־\sשל Emacs מלווה בתו מחלקת־תחביר (\s-לרווח לבן,\swלתו מילה) — ייחודי ל־Emacs.
מחלקות סוגריים מרובעים של POSIX ([[:digit:]], [[:alpha:]], …) הן הכתיב הנייד האוניברסלי: כל מנוע בהשוואה מזהה אותן.
הרגל שימושי#
מחלקות בעלות שם ומקוצרות כמעט תמיד ברורות יותר מטווחים מפורשים. \d{4}-\d{2}-\d{2} נקרא; [0-9]{4}-[0-9]{2}-[0-9]{2} דורש רגע. יש להשתמש בטווחים רק עם סיבה קונקרטית — בדרך כלל ביצועים בלולאה חמה, או הגבלה מכוונת ל־ASCII.
ראו גם#
פרק unicode —
\p{…},\P{…},\X, מתאמי ערכת התווים/a,/u,/l,/d.פרק anchors and assertions —
\bמחוץ למחלקה.פרק cross-engine — טבלה מלאה של תמיכת קיצורים בין מנועים.
m— אופרטור ההתאמה.qr— הידור תבנית לשימוש חוזר.