תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

ביטויים רגולריים ו-PHP

מחבר:
בתאריך:

To match or not to match with regular expressions in PHP

 

אחד הכלים החשובים ביותר בכל שפת תכנות הוא הכלי של ביטויים רגולריים, מפני שהוא מזהה דפוסים במחרוזות. היכן משתמשים בזה? בכל מקום כמעט. לזיהוי URL, לזיהוי דפדפנים, לסינון ספאם או קוד מזיק שהזינו הגולשים, לבדיקת החוזק של ססמאות, ועוד. במדריך זה תלמדו את הבסיס לשימוש בביטויים רגולריים בשפת ה-PHP.

 

preg_match מוצא התאמה במחרוזות

preg_match היא פונקציה מובנה של PHP, שמחפשת התאמה לביטוי רגולרי. אם היא מוצאת התאמה, היא מחזירה true, ואם היא לא מוצאת היא מחזירה false.

התחביר:

preg_match($regex, $string, $match)
  • $regex הוא הביטוי הרגולרי, ואותו צריך להקיף בקו נטוי ובמירכאות כפולות, לדוגמה: "/exp/"
  • $string המחרוזת שבתוכה מחפשים את הביטוי.
  • $match מערך שמאחסן את ההתאמה הראשונה שהוא מוצא (preg_match מפסיק לחפש ברגע שהוא מוצא את ההתאמה הראשונה).

לדוגמה, נבדוק האם הביטוי "ביטויים" נמצא במחרוזת $string:

<?php
$regex  = "/ביטויים/";
$string = "ביטויים רגולריים";
if(preg_match($regex, $string, $match)) 
{
    echo "מצאנו התאמה ל- " . $match[0];
} 
else 
{
    echo " לא מצאנו התאמה ";
}
?>

התוצאה:

מצאנו התאמה ל-ביטויים

preg_match מחפש את הביטוי הרגולרי ($regex) בתוך המחרוזת ($string), ואת ההתאמה הראשונה, ורק אותה, הוא שומר במערך $match. שימו לב שהביטוי הרגולרי אנחנו כותבים בין שני קווים נטויים.

 

תווים מיוחדים

ישנם מספר מטה-תגיות (Meta caharacter) שצריך להכיר היטב:

Meta characterמשמעות
.כל תו מלבד שורה חדשה
\wאותיות אנגליות קטנות וגדולות, מספרים וקו תחתון
\dמתאים לכל ספרה 0-9
\sדוגמת רווח, טאב ושורה חדשה white space
\bרווח בין מילים
$סוף מחרוזת
^תחילת מחרוזת

לדוגמה, הביטוי:

$regex = "/.\s\d$/";
$string = "Flex 3";
if (preg_match($regex, $string, $match)) 
{
    echo "מצאנו התאמה ל- " . $match[0];
}

מחפש התאמה לביטוי שהתו הראשון בו הוא כל תו שהוא (.) התו השני הוא רווח (\s), והתו האחרון הוא ספרה (\d), ובתנאי שהביטוי יימצא בסוף המחרוזת ($).

התוצאה:

מצאנו התאמה ל- x 3

מה קורה כשאנחנו רוצים למצוא התאמה לנקודה או ל-$ (למרות שיש להם משמעות מיוחדת)? במקרים אילו, נשתמש בקו נטוי (\), שגורם לאיבוד המשמעות המיוחדת של המטה תגיות, ולהתייחסות אליהם כפשוטם.

הדפוסמוצא התאמה ל
\.פשוט נקודה
\$$ פשוט
\^^ פשוט
\\פשוט קו נטוי

לדוגמה, הביטוי הרגולרי "/\.\$.\^/" יתאים ל- .$M^ וגם ל- .$5^

 

המודיפייר i

ביטויים רגולריים מבחינים בין אות קטנה וגדולה באנגלית כברירת מחדל. לכן, מבחינת הביטויים הרגולריים, chrome הוא ביטוי אחר מ-Chrome. כדי למנוע את המצב הזה נשתמש במודיפייר i (case insensitive) . כך כותבים זאת:

$regex = "/Chrome/i";

כשה-i מציין שצריך להתעלם מהאם האותיות קטנות או גדולות.

בדוגמה הבאה, נרצה למצוא התאמה ל-Chrome, בין אם הוא כתוב באותיות קטנות או גדולות.

$regex = "/Chrome/i";
$string = "The browser with the best developer's tools is chrome.";
if(preg_match($regex, $string, $match)) 
{
    echo "מצאנו התאמה ל- " . $match[0];
}

והתוצאה:

מצאנו התאמה ל - chrome

 

preg_match_all()

עד עכשיו עבדנו עם preg_match, שמפסיק למצוא התאמות אחרי המקרה הראשון של התאמה. אולם, כשנרצה למצוא את כל הביטויים התואמים (מכונה התאמה גלובלית), נשתמש ב-preg_match_all.

בדוגמה, אנחנו מחפשים את הביטוי $regex בתוך המחרוזת $string, ואם נמצאת התאמה היא מאוחסנת במערך הרב-ממדי $match.

$regex = "/ביטוי/";
$string = "ביטויים רגולריים הם ביטוי לדפוס";
if(preg_match_all($regex, $string, $match)
{
    print_r($match[0]);
}

והתוצאה:

Array
(
    [0] => ביטוי
    [1] => ביטוי
)

 

טווח של תווים

לא חייבים לציין כל תו שרוצים לזהות, ובמקום זה אפשר לציין את הטווח הרצוי בין סוגריים מרובעים. אילו הטווחים השימושיים ביותר:

הטווחמוצא התאמה ל
[ab]a או b
[abc]a, b או c
[A-Z]אותיות גדולות בלבד
[a-z]אותיות קטנות בלבד
[A-Za-z]אותיות קטנות או גדולות בלבד
[0-9]ספרות בלבד
[A-Za-z0-9]אותיות או ספרות בלבד
[א-ת]כל האותיות בעברית
[a-d]האותיות a-d בלבד

ראינו כבר את תו המטה ^, כשהוא מורה לחפש את ההתאמה בתחילתו של הביטוי, אבל כשהוא נמצא בין סוגריים מרובעים הוא מקבל משמעות של הסט המשלים. ראו את הדוגמאות הבאות:

הסטמוצא התאמה ל
[^A-Z]כל מה שאינו אותיות גדולות
[^a-z]כל מה שאינו אותיות קטנות
[^A-Za-z]כל מה שאינו אותיות אנגליות
[^0-9]כל מה שאינו ספרות
[^A-Za-z0-9]אותיות או ספרות בלבד
[^א-ת]כל מה שאינו אות בעברית
[^a-d]כל מה שאינו בטווח האותיות a-d

 

כמתים (quantifiers) של ביטויים רגולריים

כדי לציין את כמות החזרות על הביטוי שבו אנחנו מעוניינים, נשתמש בכמתים, כפי שניתן לראות בטבלה המצורפת:

הכמתמשמעותו
*אפס פעמים או יותר
+1 או יותר פעמים
?0 פעמים או פעם אחת
{3}בדיוק 3 פעמים
{3,8}בין 3 ל-8 פעמים
{3, }לפחות 3 פעמים
{ ,8}לכל היותר 8 פעמים

לדוגמה:

$regex = "/[א-ת]ביטוי*/";
$string = "ביטויים רגולריים";
if (preg_match($regex, $string, $match)) 
{
    echo "מצאנו התאמה ל- " . $match[0];
}

והתוצאה:

מצאנו התאמה ל- ביטויים רגולריים

הסבר: הביטוי [א-ת]*  מציין שאחרי הביטוי יכול לבוא כל טווח האותיות בעברית ורווח, כשלחיצה אחת על מקש הרווח מציינת רווח.

הביטוי הבא, המספר 20 ואחריו 2 ספרות, יתאים לכל שנה במאה ה-21:

$regex = "/$20[0-9]{2}^/";

 

ביטויים עצלים

כמתים נוטים להיות חמדנים, ולמצוא את ההתאמה הרחבה ביותר האפשרית, וכך אנו עלולים לכתוב ביטוי שמתאים להרבה יותר ממה שהתכוונו. לדוגמה, אני מעוניין להחליף את צבע החתול ב-' M@#! ' בכל הספאנים. אם אכתוב את הביטוי באופן רגיל:

$string = "<span>השחור</span> לחתול <span>הכתום</span>אמר החתול ";
//ביטוי רגיל וחמדן
$regex = "/<span>.+<\/span>/";
echo preg_replace($regex ,' M@#! ', $string );

אקבל את התוצאה הבאה:

אמר החתול M@#!

בניגוד לרצון להחליף כל ספאן בנפרד, החליף הביטוי את כל מה שהתחיל עם התג הפותח של הספאן הראשון ועד לתג הסוגר של הספאן האחרון. כדי למנוע את הבעיה הזו, נוסיף ? אחרי הכמת +, ועל ידי זה נגרום לו לעצור מיד אחרי שהוא מוצא התאמה.

הביטוי המשוכתב יכלול עכשיו את סימן השאלה, שהופך את הביטוי מחמדן לעצל:

$regex = "/<span>.+?<\/span>/";

והתוצאה, בהתאם:

אמר החתול M@#! לחתול M@#!

כל ספאן מוחלף בנפרד.

 

בחירה בין אפשרויות (א|ב)

כדי לבחור בין אפשרויות, מפרידים ביניהן באמצעות פייפ (|). לדוגמה, הביטוי הבא מאפשר לבחור אך ורק קבצי תמונות מסוג png, gif או jpeg.

$regex = "/^[A-Za-z0-9-_]{3,50}.(jpg|gif|png)$/";

הסבר: הביטוי מתחיל ב3-50 תווים שכוללים אותיות אנגליות, ספרות, קו תחתון או קו מפריד, אחר-כך נקודה ואז אחת האפשרויות jpg, gif או png.

 

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

לפעמים, אנו מעוניינים רק במחרוזות שלא מתאימות לביטוי, ובמקרים אילה נשתמש ב- ! (not operator) , כש-! הופך את המשמעות של הביטוי הבוליאני. לדוגמה, אנו רוצים התאמה למחרוזת שאינה כוללת בתוכה את הביטוי "chrome".

$regex = "/Chrome/i";
$string = "Internet Explorer 10 is a fine browser.";
if(!preg_match($regex, $string, $match))
{
    echo "אין התאמה";
} 
else 
{ 
    echo "יש התאמה";
}

והתוצאה:

אין התאמה

 

החלפה של תווים

כדי להחליף תווים במחרוזת משתמשים ב-preg_replace(), ובתחביר הבא:

preg_replace($regex, $replace, $string);
  • $regex – הביטוי הרגולרי
  • $replace – במה צריך להחליף את הביטוי
  • $string – המחרוזת שבתוכה מחפשים את הביטוי

בדוגמה הבאה, מחליפים את כל הצורות השגויות של רלוונטי באיות הנכון.

$regex = "/(רלבנט|רלונט)/";
$replace = "רלוונט";
$string = "רצוי לכתוב משהו יותר רלבנטי, כדי שלא תאבד הרלונטיות שלו מהר מאוד.";
echo preg_replace($regex, $replace, $string);

והתוצאה:

רצוי לכתוב משהו יותר רלוונטי, כדי שלא תאבד הרלוונטיות שלו מהר מאוד.

 

קבוצות של ביטויים

כשמקיפים ביטוי בסוגריים, ניתן להתייחס אליו אחר כך באמצעות reference, שמציינים באמצעות $. אל הסוגריים הראשונים משמאל נתייחס כ-$1, אל השניים משמאל כ-$2, וכיו"ב. בדוגמה הבאה, אנחנו לוקחים את התאריך בפורמט האמריקאי (09-16-2013) וממירים אותו לפורמט הישראלי:

$string = "09-16-2013";
$regex = "/([0-9]{1,2})-([0-9]{1,2})-([0-9]{4})/";
//הסוגריים הראשונים מתייחסים לחודש
//הסוגריים השניים ליום
//והשלישיים לשנה
$replace = "$2.$1.$3";
//מחליפים בין הסוגריים הראשונים והשניים
echo preg_replace($regex ,$replace ,$string );

והתוצאה:

16.09.2013

---

במידה ולא מעוניינים לתפוס את אחת הקבוצות מוסיפים ?: בתחילת הסוגריים. לדוגמה, אם איננו מעוניינים לתפוס את אחת הקבוצות בביטוי ניתן לכתוב את הביטוי מחדש באופן הבא:

$regex = "/([0-9]{1,2})-([0-9]{1,2})-(?:[0-9]{4})/";

והתוצאה, בהתאם, אינה כוללת את הקבוצה השלישית:

16.09.

---

ניתן לקרוא להתאמות שמתקבלות בשם כשמעבירים פרמטר שלישי ל- preg_match שיאחסן את ההתאמות.
בדוגמה להלן $matches מאחסן את המערך שמכיל את ההתאמות.

$str = "09-16-2013";
preg_match('/(?P<month>[0-9]{1,2})-(?P<day>[0-9]{1,2})-(?P<year>[0-9]{4})/', $str, $matches);
// The matches array is stored in the $matches variable
var_dump($matches);

והתוצאה:

array (size=7)
 0 => string '09-16-2013' (length=10)
 'month' => string '09' (length=2)
 1 => string '09' (length=2)
 'day' => string '16' (length=2)
 2 => string '16' (length=2)
 'year' => string '2013' (length=4)
 3 => string '2013' (length=4)

 

פיצול מחרוזות בביטוי

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

התחביר הוא הבא:

preg_split($regex, $string);
  • $regex הוא הביטוי שאליו מחפשים התאמה
  • $string זו המחרוזת שבתוכה מחפשים את ההתאמה

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

$regex = "/,\s+/";
$string = "html, css,  javascript,   php";
$keywords = preg_split($regex, $string);
print_r($keywords);

והתוצאה:

Array
(
    [0] => html
    [1] => css
    [2] => javascript
    [3] => php
)

 

מציאת התאמות במערכים

preg_grep מטפל במערכים, ומחזיר מערך ובו הפריטים התואמים בלבד.

זה התחביר:

preg_grep($pattern, $array);
  • $pattern הוא הביטוי שאליו מחפשים התאמה
  • $array הוא המערך שבתוכו מחפשים את ההתאמה

בדוגמה הבאה, אנו מחפשים בתוך המערך $food, את המזון שמתחיל ב-פ.

$foods = array(
	"פירות",
	"תפוח אדמה",
	"דגים",
	"פלאפל",
	"קרמבו");
$output = preg_grep('/^[א-ת]פ+/', $foods);
print_r( $output );

והתוצאה:

Array
(
    [0] => פירות
    [3] => פלאפל
)

 

כיצד להמשיך הלאה?

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

מדריך מקיף ל- regular expressions

שליף cheat sheet, שמסכם את נושא ה-regex

כלי לבדיקה און-ליין של ביטויים רגולריים

 

וכמובן, אתם מוזמנים לקרוא את יתר מדריכי ה-PHP באתר:
לכל מדריכי ה-PHP

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

המדריכים באתר עוסקים בנושאי תכנות ופיתוח אישי. הקוד שמוצג משמש להדגמה ולצרכי לימוד. התוכן והקוד המוצגים באתר נבדקו בקפידה ונמצאו תקינים. אבל ייתכן ששימוש במערכות שונות, דוגמת דפדפן או מערכת הפעלה שונה ולאור השינויים הטכנולוגיים התכופים בעולם שבו אנו חיים יגרום לתוצאות שונות מהמצופה. בכל מקרה, אין בעל האתר נושא באחריות לכל שיבוש או שימוש לא אחראי בתכנים הלימודיים באתר.

למרות האמור לעיל, ומתוך רצון טוב, אם נתקלת בקשיים ביישום הקוד באתר מפאת מה שנראה לך כשגיאה או כחוסר עקביות נא להשאיר תגובה עם פירוט הבעיה באזור התגובות בתחתית המדריכים. זה יכול לעזור למשתמשים אחרים שנתקלו באותה בעיה ואם אני רואה שהבעיה עקרונית אני עשוי לערוך התאמה במדריך או להסיר אותו כדי להימנע מהטעיית הציבור.

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

המשתמש באתר צריך להיות מודע לכך שאם וכאשר הוא מפתח קוד בשביל פרויקט הוא חייב לשים לב ולהשתמש בסביבת הפיתוח המתאימה ביותר, הבטוחה ביותר, היעילה ביותר וכמובן שהוא צריך לבדוק את הקוד בהיבטים של יעילות ואבטחה. מי אמר שלהיות מפתח זו עבודה קלה ?

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

מתי הוקמה המדינה?

 

תמונת המגיב

אלכס בתאריך: 10.05.2014

המדריך הטוב ביותר שקראתי עד היום. תודה רבה!

תמונת המגיב

ישראל בתאריך: 04.04.2016

לא אהבתי.
א. לא משתמשים בביטויים רגולריים כדי לעשות פרסינג של הטמל.
ב. אנשים נתקלים בבעיה והם חושבים שהתשובה היא ב-regex. עכשיו יש להם שתי בעיות.

תמונת המגיב

יוסי בן הרוש בתאריך: 05.04.2016

תודה ישראל.
א. אני מסכים שזה לא best practice, אבל אפשר להשתמש בביטויים רגולריים כדי לעשות פרסינג של html אם יודעים למה לצפות.
ב. אהבתי את השנינות.

תמונת המגיב

דור בתאריך: 08.10.2017

מאוד אהבתי. כמתחיל ללמוד ביטויים רגולריים php רוצה לומר שמאוד עזרת לי לעשות בראש סוג של סדר בנושא למרות שהדוגמאות הן על קצה המזלג (אגב, אני חושב שמצאתי טעות הקלדה כנראה, באחת הדוגמאות), בכל אופן תודה רבה לך. לא הבנתי את התגובה של ישראל הבחור מעליי - אבל כנראה זה כבר בתחום של php מתקדמים.

תמונת המגיב

יוסי בן הרוש בתאריך: 09.10.2017

היי דור, תודה על דברי העידוד. התגובה של ישראל התייחסה לדוגמה שנתתי בביטויים עצלים ואכן מוטב שלא להשתמש בביטויים רגולריים על הטמל, אלא אם המבנה ידוע. כלי חלופי הוא DOMDocument.