השוואה בין־מנועית#
ביטויים רגולריים של Perl אינם הסוג היחיד. קורא שמכיר Perl היטב יתקל, במוקדם או במאוחר, בביטוי רגולרי שנכתב עבור כלי אחר — sed, awk, חיפוש מאגר Emacs Lisp, שירות Go, ספריית PCRE2 מוטמעת — ויצופה ממנו לקרוא אותו. ההבדלים התחביריים בדרך כלל קטנים, ההבדלים הסמנטיים לפעמים משמעותיים, וההבדלים הארכיטקטוניים (אילו קלטים הופכים את המנוע לאיטי קטסטרופלית) יכולים להיות מכריעים.
פרק זה הוא העמוד היחיד שמשיב על השאלה ״כיצד נראה X במקום אחר?״ עבור חמש משפחות המנועים שקורא מאומן ב־Perl ככל הנראה יפגוש. אין זה מדריך להעברת קוד; זוהי הפניית ההשוואה.
המנועים המכוסים#
חמש משפחות, נבחרו מכיוון שכל אחת נפוצה בנישה שלה וגם שונה משמעותית מ־Perl. מנועים שנפוצים אך מעוצבים ברובם בסגנון Perl (java.util.regex של Java, re של Python, ECMAScript, .NET) מחוץ לתחום; ההבדלים מינוריים וקורא Perl יכול לקרוא אותם במבט.
מנוע | היכן פוגשים אותו | משפחה |
|---|---|---|
PCRE2 | nginx, | Perl־מורחב |
Emacs | חיפוש ביטוי רגולרי ב־Emacs Lisp, | משפחה משלו |
POSIX BRE |
| POSIX Basic |
POSIX ERE |
| POSIX Extended |
RE2 / | ספריית התקן של Go, Cloud Bigtable, חיפוש קוד | היברידי בזמן ליניארי |
גרסאות שעוגנו מולן בפרק זה: PCRE2 10.44, Emacs 30.x, POSIX 1003.1-2024, Go 1.22 (RE2 עוקב אחר מנוע הביטויים הרגולריים של Go). הערכים בטבלאות שלמטה אומתו מול גרסאות אלה; הם יסטו עם הזמן, ועדכונים ספציפיים למנוע צריכים לאמת מחדש לפני הסתמכות על שורה.
מה אינו בתחום#
המנועים הבאים הושמטו במכוון מהטבלאות. כל אחד נפוץ דיו כדי שתפגשו בו; בכל מקרה, ההבדלים מ־Perl קטנים דיים כך שקורא Perl יכול לקרוא את הביטוי הרגולרי עם התאמה מועטה.
java.util.regexשל Java — נגזר מ־Perl; ההבדלים הם איותי דגלים ומעט מוסכמות בריחה.reשל Python — נגזר מ־Perl; ההפרש הגדול ביותר הוא שתמיכה בהצצה לאחור באורך משתנה הגיעה מאוחר יותר מאשר ב־Perl.ECMAScript (JavaScript) — נגזר מ־Perl; הצצה לאחור רק מאז ES2018, ללא כמתים רכושניים, דגל דביק
yבמקום\G. V8 / SpiderMonkey מודרניים מתכנסים לקבוצת התכונות של Perl.ביטוי רגולרי של .NET — מעוצב כ־Perl, ובנוסף קבוצות מאוזנות (
(?<name-pop>…)), שהן ייחודיות. מספיק נישה כדי לדלג.regexשל Rust — שושלת RE2; הסיפור בעיקרו של Go עם משטח תכונות שונה במקצת (יש בו הצצה לאחור ברוחב קבוע, ב־RE2 אין).Vim — סמנטיקת מצב קסם שונה; פנים־Vim.
Boost.Regex — נישת C++; עוקב מקרוב אחר PCRE2.
חמש המשפחות, פסקה לכל אחת#
PCRE2#
Perl-Compatible Regular Expressions, גרסה 2 (PCRE2 החליף את ספריית PCRE המקורית ב־2015). המימוש הסטנדרטי ל״ביטוי רגולרי של Perl מחוץ ל־Perl״. נקרא כמעט זהה לתבניות Perl 5.42 ותומך בכמעט אותה קבוצת תכונות: הפניות לאחור, הצצות סביב, קבוצות אטומיות, כמתים רכושניים, תת־תבניות רקורסיביות, לכידות בשם, תבניות מותנות, callouts. ל־PCRE2 יש גם מהדר JIT שלעיתים מהיר משל perl.
היכן ש־PCRE2 שונה מ־Perl 5.42:
(?{...})ו־(??{...})— Perl מריצה קוד Perl שרירותי; ל־PCRE2 יש callouts ((?C)/(?Cn)) שקוראים בחזרה ליישום המארח. אין זה הבדל אבטחה; פשוט ממשק שונה.חופן איותי דגלים שונים (
PCRE2_CASELESSמול/i, וכדומה) ברמת ה־API, לא בתחביר התבנית.אטומיות של תת־תבנית רקורסיבית:
(?R)הוא אטומי ב־PCRE2 כברירת מחדל; Perl מאפשרת חזרה לאחור לתוך קבוצה שעברה רקורסיה.קבוצת
(*VERB)זהה ברובה, כאשר PCRE2 מוסיף(*UTF),(*UCP), ואפשרויות אתחול נוספות.
עבור רוב התבניות, ״נראה אותו דבר״ נכון.
Emacs#
מנוע הביטויים הרגולריים של GNU Emacs הוא משפחה משלו. על פני השטח הוא נראה בסגנון BRE — \(...\) לקיבוץ, \{m,n\} לספירות — אך בפנים יש בו תכונות בסגנון Perl־קל (כמתים לא־חמדנים, גבולות מילה) ותכונות ספציפיות ל־Emacs (מחלקות תחביר \sw, מחלקות קטגוריה \cC) שאין באף מנוע אחר.
מבנים בולטים ספציפיים ל־Emacs:
עוגני מאגר:
\`(גרש הפוך) הוא תחילת המאגר,\'הוא סוף המאגר. קרוב יותר ל־\A/\zשל Perl מאשר ל־^/$.מחלקות תחביר:
\swמתאים לתו ״תחביר מילה״ כפי שמוגדר על ידי טבלת התחביר של המאגר הנוכחי;\s-מתאים לתחביר־רווח לבן. זה תלוי במצב — אותו ביטוי רגולרי מתאים לתווים שונים ב־C-mode מול Lisp-mode.מחלקות קטגוריה:
\cCמתאים לתו בקטגוריה C. בשימוש כבד לסיווג טקסט CJK.אין קיצור
\d— Emacs דורש[0-9]או[[:digit:]]. (\w,\s,\bקיימים, אך\dלא.)אין הצצה קדימה, הצצה לאחור, או
\K.
אם תקראו Emacs Lisp ב־2026 תראו תבניות \\(\\) ללא הרף — זוהי הבריחה הכפולה הנדרשת מכיוון שמילוליי מחרוזות של Emacs Lisp צורכים רמה אחת של לוכסן הפוך לפני שמפענח הביטוי הרגולרי רואה את השאר.
POSIX BRE — sed, grep, ed#
POSIX Basic Regular Expressions. המנוע שמתקבל מ־sed, grep, ו־ed ללא דגלים. הדבר המפתיע ביותר עבור קורא Perl: קבוצת התווים המטא קטנה יותר. +, ?, {m,n}, (, ), ו־| חשופים הם תווים מילוליים. צורות התווים המטא הן הוריאנטים עם לוכסן הפוך: \+, \?, \{m,n\}, \(, \), \| (האחרון הוא הרחבה של GNU; ל־POSIX BRE קפדני אין ברירה כלל).
# POSIX BRE, finds the literal text "a+b":
echo 'a+b' | grep 'a+b'
# To match "one or more a", under BRE:
echo 'aaab' | grep 'a\+b'
הפניות לאחור (\1–\9) עובדות ב־BRE (זה ישן מכלל ה־ללא־הפניות־לאחור של POSIX ERE). מחלקות POSIX [[:digit:]] וכדומה עובדות. \< ו־\> (הרחבת GNU) מספקים גבולות מילה. רצפי בריחה מקוצרים של תווים כמו \d, \s, \w אינם נתמכים.
grep ו־sed מספקים את אותו מנוע אך ברירת המחדל של הדגלים שונה למשפחות שונות: grep -P של GNU מפעיל את PCRE2, grep -E הוא ERE, ברירת המחדל grep היא BRE. sed -E הוא ERE, ברירת המחדל sed היא BRE.
POSIX ERE — awk, egrep, sed -E#
POSIX Extended Regular Expressions, המנוע ש־awk מוגדר אליו כברירת מחדל וזה שנבחר על ידי egrep (עכשיו grep -E) ו־sed -E / sed -r. +, ?, {m,n}, (...), ו־| חשופים הם תווים מטא כאן; זוהי המשפחה הקרובה ביותר ל״מה שמתכנת מאומן ב־Perl מצפה לו״ בין הקבוצה תואמת־POSIX.
מה ש־POSIX ERE קפדני אינו כולל, על פי המפרט:
הפניות לאחור בתבנית (
\1,\2, …). הן חוקיות בהחלפות החלפה שלsedאך לא בתבניות התאמה. הרחבות GNU מוסיפות אותן; סקריפטים ניידים לא צריכים להסתמך על זה.הצצות סביב.
כמתים רכושניים, קבוצות אטומיות.
\d,\s,\w,\b(יש להשתמש ב־[0-9],[[:space:]], וכו«).
ההפתעה הנפוצה ביותר: \( ב־ERE הוא סוגריים פותחים מילוליים. הוספת לוכסן הפוך לפיסוק היא לעיתים תו מטא ולעיתים ללא־פעולה, כשהכלל הפוך מ־BRE. יש לזכור: לוכסן הפוך ב־BRE מאפשר תווים מטא; לוכסן הפוך ב־ERE משבית אותם.
RE2 / regexp של Go#
RE2 של Google היא ארכיטקטורה שונה ביסודה. RE2 בונה NFA בזמן הידור ומשתמש ב־DFA עצלן (בונה מצבי DFA לפי דרישה ושומר אותם במטמון) בזמן התאמה. התוצאה היא זמן ליניארי מובטח באורך הקלט, ללא קשר לתבנית.
הפשרה: RE2 אינה יכולה לתמוך בתכונות שמוציאות שפות רגולריות ממשפחת השפות הרגולריות. באופן ספציפי:
אין הפניות לאחור בתבניות.
\1,\g{1},\k<name>— אין. תבנית כמו(.)\1פשוט אינה ניתנת להידור.אין הצצה סביב כללית. RE2 תומך בעוגנים וב־
\b(טענות ברוחב־אפס), אך לא ב־(?=...),(?<=...),(?!...), או(?<!...).אין קבוצות אטומיות, אין כמתים רכושניים, אין תת־תבניות רקורסיביות, אין קוד מוטמע.
מה ש־RE2 כן תומך בו, ומה שהופך אותה לנעימה למקרה הנפוץ:
לכידות בשם עם תחביר בסגנון Python
(?P<name>...).ASCII כברירת מחדל עבור
\d,\w,\s; מצב יוניקוד מאופשר עם(?u).תחבירי המתאם
(?flags)ו־(?flags:...).דגל החלפת ברירת המחדל של החמדנות
(?U)— תחתיו,*הוא לא־חמדן ו־*?הוא חמדן. ההפך מ־Perl.
אם אי פעם כתבתם ביטוי רגולרי של Perl שחזר לאחור באופן קטסטרופלי, RE2 הוא מה שכותבים אחר כך. העלות היא התכונות החסרות שלמעלה; היתרון הוא שאי אפשר לבצע התקפת DoS על המנוע באמצעות קלט זדוני.
חבילת regexp הסטנדרטית של Go עוטפת את RE2 ומוסיפה כמה ייחודיויות של Go, שאף אחד מהן אינו משנה את היסודות.
טבלאות השוואת תכונות#
שאר הפרק הזה הוא מסודר־לפי־שורות: השורות הן תכונות, העמודות הן מנועים. הסידור הזה מותאם למקרה השימוש ״אני מכיר מנוע אחד, איך נראה X באחר?״. ערכי התאים תמציתיים ועשויים לפשט מקרי קצה — יש לאמת מול ההפניה של המנוע עצמו עבור קוד ייצור.
כמתים, קיבוץ, ברירה#
תכונה | Perl 5.42 | PCRE2 | Emacs | POSIX BRE | POSIX ERE | RE2 / Go |
|---|---|---|---|---|---|---|
| מטא | מטא | מטא | מטא | מטא | מטא |
| מטא | מטא | מטא | מילולי (יש להשתמש ב־ | מטא | מטא |
| מטא | מטא | מטא | מילולי (יש להשתמש ב־ | מטא | מטא |
| מטא | מטא |
|
| מטא | מטא |
| מטא | מטא | מטא |
| מטא | מטא |
| מטא | מטא |
|
| מטא | מטא |
| כן | כן | לא | לא | לא | כן |
רכושני | כן | כן | לא | לא | לא | לא |
לא־חמדן | כן | כן | כן | לא | לא | כן |
קבוצה אטומית | כן | כן | לא | לא | לא | לא |
השורה היחידה בעלת הערך הגבוה ביותר עבור קורא Perl שפוגש ב־BRE: + ו־? הם מילוליים. ביטוי רגולרי כמו colou?r נקרא כ״מתאים colou?r בדיוק״ תחת BRE. הכלל עקבי — קבוצת התווים המטא של BRE קטנה; ERE הרחיב אותה; Perl הרחיבה אותה עוד יותר.
מחלקות תווים מקוצרות#
למה \d מתאים? תלוי. העמודות שלמטה מראות לאיזו קבוצת תווים כל מנוע מתייחס כספרות, תווי מילה, ורווח לבן תחת הגדרות ברירת המחדל.
קיצור | Perl 5.42 (ברירת מחדל) | Perl 5.42 + | PCRE2 (ברירת מחדל) | Emacs | POSIX BRE | POSIX ERE | RE2 / Go (ברירת מחדל) |
|---|---|---|---|---|---|---|---|
| ASCII (או יוניקוד תחת | יוניקוד | ASCII | לא | לא | לא | ASCII; יוניקוד תחת |
| ASCII או יוניקוד | יוניקוד | ASCII | כן (מונע על ידי טבלת תחביר) | לא | לא | ASCII; יוניקוד תחת |
| ASCII או יוניקוד | יוניקוד | ASCII | כן | לא | לא | ASCII; יוניקוד תחת |
| כן | כן | כן | כן ( | לא | לא | כן |
| כן | כן | כן | לא | לא | לא | לא |
| כן | כן | כן | לא | לא | לא | לא |
שתי הפתעות כאן עבור קורא Perl:
POSIX BRE ו־ERE שניהם חסרים
\d,\w,\s. הצורות הניידות הן[0-9],[[:alnum:]_],[[:space:]]. כלים המקבלים\d(grepשל GNU,awkבחלק מהמימושים) עושים זאת כהרחבה לא־ניידת.ל־Emacs יש
\wו־\sאך אין\d. אחרי התו\sבא תו מחלקת תחביר —\s-עבור רווח לבן,\swעבור מילה — שייחודי ל־Emacs.
מחלקות סוגריים של POSIX כמו [[:digit:]] ו־[[:alpha:]] עובדות בכל מנוע בטבלה; הן האיות הנייד.
עוגנים וטיפול במעבר שורה#
תכונה | Perl 5.42 | PCRE2 | Emacs | POSIX BRE / ERE | RE2 / Go |
|---|---|---|---|---|---|
| כן | כן | תחילת מאגר | כן | כן |
|
|
| תמיד (מוכוון־שורות) | לא מוגדר |
|
| כן | כן | סוף מאגר | כן | כן |
| כן | כן | לא | משתנה | כן |
|
|
| לא | לא |
|
| כן | כן |
| לא | כן ( |
| כן | כן | לא | לא | לא (יש להשתמש ב־ |
| כן | כן | לא | לא | לא |
Emacs משתמשת ב־\` וב־\' עבור עוגני המאגר, היכן שרוב המנועים המודרניים משתמשים ב־\A וב־\z. האיות של Emacs ישן יותר; הוא קודם ל־POSIX. בלבול בין ^ ו־\A חשוב בקוד Emacs מוכוון־שורות יותר מאשר ב־Perl, מכיוון שהתאמות Emacs לעיתים מופעלות בהקשרים שסורקים מאגר שלם.
\G ספציפי ל־Perl (ול־PCRE2). RE2 / Go משתמשים ב־API שונה על עצם ההתאמה (מתודת Loc שמחזירה את המיקום הבא) במקום להטמיע את המיקום בתבנית.
מבני הצצה־סביב ואטומיים#
תכונה | Perl 5.42 | PCRE2 | Emacs | POSIX | RE2 / Go |
|---|---|---|---|---|---|
הצצה קדימה | כן | כן | לא | לא | לא |
הצצה לאחור ברוחב קבוע | כן | כן | לא | לא | לא |
הצצה לאחור באורך משתנה | כן (ניסיוני) | כן | לא | לא | לא |
| כן | כן | לא | לא | לא |
קבוצה אטומית | כן | כן | לא | לא | לא |
כמתים רכושניים | כן | כן | לא | לא | לא |
למעשה: רק PCRE2 (ומנועים אחרים שנגזרו מ־Perl מחוץ לטבלה זו — Java, Python, .NET) תומך בהצצה־סביב. כלי POSIX ו־RE2 / Go פשוט אין להם. תבניות הזקוקות להצצה־סביב אינן ניתנות להעברה על פני המחיצה הזו.
לכידות והפניות לאחור#
תכונה | Perl 5.42 | PCRE2 | Emacs | POSIX BRE | POSIX ERE | RE2 / Go |
|---|---|---|---|---|---|---|
לכידות ממוספרות | כן | כן | כן | כן | קפדני לא, GNU כן | כן |
הפניות לאחור בתבנית ( | כן (1–99) | כן | כן | כן ( | קפדני לא, GNU כן | לא |
לכידות בשם | כן | כן | לא | לא | לא | כן ( |
איפוס ענפים | כן | כן | לא | לא | לא | לא |
תת־תבניות רקורסיביות | כן | כן | לא | לא | לא | לא |
תבניות מותנות | כן | כן | לא | לא | לא | לא |
| כן | callouts | לא | לא | לא | לא |
מערך | כן | לא | לא | לא | לא | לא (API שונה) |
המסקנה החשובה ביותר: RE2 / regexp של Go אינו תומך בהפניות לאחור. תבניות כמו /(.)\1/ אינן ניתנות להידור. אין זה באג של המנוע; זהו המחיר ש־RE2 משלם עבור זמן ליניארי מובטח. תבניות המכילות הפניות לאחור אינן שפות רגולריות ו־DFA אמיתי אינו יכול להתאים אותן בזמן ליניארי.
אם אתם מעבירים ביטויים רגולריים של Perl לשירות Go, שאלת הביקורת היא ״האם התבנית הזו משתמשת בהפניות לאחור?״ — אם כן, ההמרה אינה מכנית.
סיכום ארכיטקטורת המנוע#
תכונה | PCRE2 / Perl 5.42 | Emacs | POSIX BRE / ERE (מימוש טיפוסי) | RE2 / Go |
|---|---|---|---|---|
אלגוריתם | NFA מסורתי + JIT (PCRE2) | NFA מסורתי | DFA (GNU) / NFA (אחרים) | היברידי NFA → DFA עצלן |
זמן במקרה הגרוע | מעריכי | מעריכי | ליניארי (DFA) | ליניארי (מובטח) |
מודל חזרה לאחור | כן | כן | DFA: לא | לא |
סיכון לחזרה לאחור קטסטרופלית | כן | כן | לא (DFA) | לא |
הפניות לאחור נתמכות | כן | כן | משתנה | לא |
POSIX BRE/ERE תלוי במימוש: grep ו־awk של GNU משתמשים ב־DFA היברידי עם נפילה ל־NFA שהוא מהיר ואינו נתון לחזרה לאחור קטסטרופלית; מימושים מסורתיים ישנים יותר (awk היסטורי, grep מסורתי בכמה BSD) משתמשים ב־NFA עם חזרה לאחור. ההנחה הניידת היא ״זמן ליניארי, אך לאמת אם הקלט עלול להיות עוין״.
בחירת מנוע כאשר יש לכם את הבחירה#
אם אתם כותבים את התוכנית (לא קוראים קוד שכבר בחר), המנוע שאתם בוחרים נקבע ברובו על ידי השפה שבה אתם מתכנתים. החריגים הם כאשר אותה ספרייה מספקת מספר מנועים, וכאשר כלי ביטוי רגולרי חיצוני הוא בחירתכם:
תוכנית Perl: המובנה של Perl. אין סיבה לפנות החוצה.
תוכנית C המטמיעה ביטוי רגולרי: PCRE2. ה־
regex.hשל ספריית התקן הוא POSIX BRE/ERE; PCRE2 הוא מה שרוצים עבור התנהגות בסגנון Perl.שירות Go המטפל בקלט לא־אמין:
regexp(RE2). הבטחת הזמן הליניארי היא התכונה המרכזית; ההפניות לאחור והצצות־סביב החסרות לרוב מקובלות.תוכנית Rust: ה־crate של
regex. שושלת RE2; אותה הבטחת זמן ליניארי.סקריפטים של מעטפת: יש להעדיף
grep -E(ERE) ו־sed -E(ERE) על פני ברירות המחדל של BRE לקריאוּת בסגנון Perl. יש להשתמש ב־grep -P(PCRE2) רק כאשר ERE באמת אינו מספיק.פונקציית Emacs Lisp: של Emacs. אחרת יש לעזוב את Emacs.
ראו גם#
פרק character classes — שורת הקיצורים־לפי־מנוע מופיעה בסרגל הצד L3 שלו.
פרק anchors and assertions — שורות ההצצה־סביב והעוגנים מופיעות בסרגל הצד L3 שלו.
פרק groups and captures — שורת הלכידות־וההפניות־לאחור מופיעה בסרגל הצד L3 שלו.
פרק performance — מודל NFA מסורתי וחזרה לאחור קטסטרופלית, הסיבה הארכיטקטונית לקיומה של RE2.