משתני התאמת regex#

לאחר כל התאמת תבנית מוצלחת, Perl מאכלס קבוצה קבועה של משתנים במידע על מה התאים והיכן. אלה לקריאה בלבד - אתם צופים בהם, אינכם כותבים אליהם. הסיוג ”מוצלחת“ קריטי: התאמה לא־מוצלחת משאירה את המשתנים מחזיקים את כל מה שההתאמה המוצלחת הקודמת באותו תחום דינמי קבעה אותם. תמיד שערו את הגישה שלכם עם התוצאה הבוליאנית של ההתאמה עצמה.

משתנה

מחזיק

$1..$N

טקסט שנלכד על ידי קבוצת הלכידה הN

$&

תת־המחרוזת המותאמת כולה

$`

המחרוזת שלפני ההתאמה

$'

המחרוזת שאחרי ההתאמה

$+

טקסט שנלכד על ידי הקבוצה בעלת המספר הגבוה ביותר

$^N

טקסט שנלכד על ידי הקבוצה שנסגרה לאחרונה

@-

היסטי התחלה: $-[0] = התאמה, $-[N] = התחלת $N

@+

היסטי סוף: $+[0] = סוף התאמה, $+[N] = סוף $N

%+

האש של לכידות בעלות שם: $+{name} = (?<name>...)

%-

האש של כל הלכידות לפי שם (ערכי arrayref)

@{^CAPTURE}

מערך של לכידות: ${^CAPTURE}[0] = $1, וכו«

${^MATCH}

זהה ל־$&, מאוכלס רק עם הדגל /p

${^PREMATCH}

זהה ל־$`, רק עם /p

${^POSTMATCH}

זהה ל־$', רק עם /p

התבנית הבסיסית#

if ("Mr. Smith, age 47" =~ /(\w+)\s+(\w+),\s+age\s+(\d+)/) {
    print "title: $1\n";          # Mr
    print "name:  $2\n";          # Smith
    print "age:   $3\n";          # 47
    print "match: $&\n";          # the full match
}

ה־if הוא מה שהופך את הגישה לבטוחה. בלעדיו, על מחרוזת שאינה מתאימה, $1/$& עדיין היו מחזיקים ערכים מהתאמה מוצלחת קודמת כלשהי - אין כלל ”התאמה נכשלה ← ניקוי“.

לכידות ממוספרות - $1..$N#

כל קבוצת לכידה (...) ממלאת משתנה אחד. המספור הוא משמאל־לימין לפי הסוגר הפותח:

"alpha-beta=42" =~ /^(\w+)-(\w+)=(\d+)$/;
print "$1 / $2 / $3\n";           # alpha / beta / 42

קבוצות שאינן לוכדות (?:...) ו־lookarounds (?=...)/(?!...) אינן צורכות מספר; הן בלתי־נראות למניין.

עבור החלפות s///, $1..$N נראים בתוך ההחלפה (יחד עם ההפניות לאחור בסגנון $1 הייחודיות־להחלפה בהחלפות במרכאות בודדות):

my $s = "John Smith";
$s =~ s/(\w+)\s+(\w+)/$2, $1/;    # "Smith, John"

לכידות בעלות שם - (?<name>...) ו־%+#

לכידות בעלות שם ברורות יותר מספירת סוגריים, במיוחד בתבניות עם קבוצות רבות:

my $log = "2024-03-15 14:32:01 ERROR connection refused";
if ($log =~ /^(?<date>\d{4}-\d{2}-\d{2})
              \s+
              (?<time>\d{2}:\d{2}:\d{2})
              \s+
              (?<level>\w+)
              \s+
              (?<msg>.*)$/x) {
    print "[$+{level}] $+{date} $+{time}: $+{msg}\n";
}

%+ הוא האש הלכידות בעלות השם. הצורות הממוספרות עדיין עובדות (ללכידות בעלות שם מוקצים גם מספרים בסדר הופעתן), כך ש־$1 היה $+{date} כאן.

%- דומה אך ערכיו הם הפניות מערך, המחזיקות כל לכידה תחת אותו שם (רלוונטי בשימוש בקבוצות איפוס ענף (?|...|...) או כאשר שם משמש מחדש על פני ענפי חלופה):

"abc" =~ /(?|(?<x>a)|(?<x>b)|(?<x>c))/;
print "%-{x} has @{$-{x}}\n";    # captured value(s) under name 'x'

רוב הקוד אי פעם קורא רק את %+. %- הוא למקרי הקצה.

גבולות התאמה - @- ו־@+#

היסטי ההתחלה והסוף של ההתאמה במחרוזת המותאמת:

"hello world" =~ /(\w+)\s+(\w+)/;
print "match started at $-[0], ended at $+[0]\n";   # 0 .. 11
print "group 1: $-[1] .. $+[1]\n";                  # 0 .. 5
print "group 2: $-[2] .. $+[2]\n";                  # 6 .. 11

$-[N] ו־$+[N] נותנים את אותו מידע ש־substr($var, $-[N], $+[N] - $-[N]) היה מחלץ - הם הדרך לשחזר מיקומים, לא ערכים, לאחר התאמה.

השימוש הקלאסי: החלפת תת־מחרוזות מותאמות תוך שמירת המקור (ללא האופרטור s///):

my $s = "the quick brown fox";
$s =~ /quick (brown)/;
my $before = substr($s, 0, $-[0]);
my $after  = substr($s, $+[0]);
my $g1     = substr($s, $-[1], $+[1] - $-[1]);
print "$before|FOUND $g1|$after\n";

$&, $`, $' - התאמה, קדם־התאמה, פוסט־התאמה#

"hello world" =~ /wo\w+/;
print "before: '$`'\n";          # 'hello '
print "match:  '$&'\n";          # 'world'
print "after:  '$\''\n";         # ''

שלושת אלה הם משתני ההתאמה הוותיקים ביותר של Perl והיקרים ביותר היסטורית - ראו ביצועים: סיפור ה־$& להלן.

$+ הוא הטקסט שנלכד על ידי הקבוצה בעלת המספר הגבוה ביותר שהשתתפה בהתאמה:

"abc" =~ /(a)(b)?(c)?/;          # $1='a', $2='b', $3='c', $+='c'
"a"   =~ /(a)(b)?(c)?/;          # $1='a', $2=undef, $3=undef, $+='a'

$^N דומה אך מחזיק את הטקסט של הקבוצה שנסגרה לאחרונה - שימושי בתוך תבניות מורכבות הזקוקות לערך של ”הקבוצה שזה עתה הסתיימה“:

my $s = "tag:value";
$s =~ /(\w+):(\w+) (?{ $cb = $^N })/;
# $cb is the text most recently captured (here: "value")

ביצועים: סיפור ה־$&#

אזהרה היסטורית שתמצאו בקוד ישן ובספרים:

אל תזכירו את $&, $`, או $' בשום מקום בקוד שלכם, כולל במודולים שאתם require - הם גורמים לכל התאמה מוצלחת להעתיק את המחרוזת המותאמת כולה, ומאיטים את התוכנית.

זה היה נכון עד Perl 5.10. מ־Perl 5.18 ואילך, זמן הריצה עוקב אילו משלושת המשתנים הקוד שלכם באמת מזכיר ומעתיק רק את הנדרש. מ־Perl 5.20 סכמת copy-on-write הופכת אותם לחינמיים למעשה.

PetaPerl עוקב אחר ההתנהגות המודרנית: $& וחבריו בטוחים לשימוש בכל מקום. הדגל /p ו־${^MATCH} / ${^PREMATCH} / ${^POSTMATCH} קיימים עבור התקופה שבין 5.10 ל־5.20 כאשר העתקה משוערת על ידי /p הייתה אופטימיזציה שימושית. אין סיבה לכתוב קוד חדש איתם.

תיחום - הם בתיחום דינמי#

משתני ההתאמה אינם לקסיקליים; הם מתנהגים כמשתנים בתיחום דינמי. כל התאמה מוצלחת מבצעת local למצב התאמה גלובלי לתחום הדינמי הנוכחי. חשוב מכל:

  • התאמה לא־מוצלחת אינה מנקה את המשתנים.

  • התאמה מוצלחת בתוך בלוק פנימי עוקפת אותם, אך רק בתוך אותו בלוק - כאשר הבלוק הפנימי יוצא, מצב ההתאמה של התחום החיצוני משוחזר.

"alpha" =~ /(\w+)/;              # $1 = 'alpha'
{
    "1234" =~ /(\d+)/;           # $1 = '1234' (inside this block)
    print "inner: $1\n";          # 1234
}
print "outer: $1\n";              # alpha - restored

זה מפתיע לעיתים: פונקציה שאתם קוראים מתוך בלוק טיפול־regex אינה מזהמת את ה־$1 שלכם אלא אם אותה פונקציה מבצעת התאמה מוצלחת משלה באותו תחום דינמי (דבר נדיר אלא אם הן רצות באותו בלוק לקסיקלי).

${^LAST_SUCCESSFUL_PATTERN}#

הפניה לקריאה בלבד אל ה־regex שהפיק את מצב ההתאמה הנוכחי - שימושי לאבחון:

"hello" =~ /(\w+)/;
print "last pattern was: ${^LAST_SUCCESSFUL_PATTERN}\n";

כאשר התאמות נכשלות - pos#

המיקום על מחרוזת לאחר התאמה מעוגנת־/g מוחזק לא באחד מהמשתנים בעמוד זה, אלא ב־pos:

my $s = "1 2 3 4";
while ($s =~ /(\d+)/g) {
    print "matched $1 at ", pos($s) - length($1), "\n";
}
# After the loop, pos($s) is undef.

pos הוא לכל־מחרוזת, ניתן לקביעה, והוא מה ש־\G מעגן אליו.

@{^CAPTURE} - לכידות כמערך#

הלכידות הממוספרות, נחשפות גם כמערך מאופס־אינדקס:

"alpha=42" =~ /(\w+)=(\d+)/;
print "name = ${^CAPTURE}[0]\n"; # 'alpha' (same as $1)
print "val  = ${^CAPTURE}[1]\n"; # '42'    (same as $2)
print "n    = scalar @{^CAPTURE}\n";  # 2

לעיתים קל יותר לבצע איטרציה על זה מאשר על $1, $2, , אך רוב הקוד משתמש בצורות הממוספרות או בעלות השם ישירות.

ראו גם#

  • m//, s/// - האופרטורים המאכלסים כל משתנה בעמוד זה.

  • qr// - מהדר תבנית; ניתן מאוחר יותר להתאים מולה את עצם ה־regex המתקבל ולהפיק את אותן לכידות.

  • pos - ההיסט לכל־מחרוזת עבור /g ו־\G.

  • איגוד regex - =~, האופרטור שמחליט על איזו מחרוזת ההתאמה רצה.

  • Regular expressions guide

    • the regex language itself.

  • Groups and captures

    • the chapter on the capture variables.