הידור פעם אחת, ואז התאמה
כתבו תבניות כ-raw string literals בתוך backticks. במחרוזת רגילה במירכאות כפולות צריך להכפיל כל לוכסן הפוך ("\\d+"), וזה הופך מהר לבלתי קריא.
regexp.MustCompile גורמת ל-panic אם התבנית לא תקינה, וזה בדיוק מה שרוצים לתבנית שכתובה בקוד המקור: שגיאת הקלדה נכשלת מיד בהפעלה, ולא בבקשה הראשונה. לתבניות שמגיעות ממשתמשים או מהגדרות, השתמשו ב-regexp.Compile וטפלו בשגיאה:
re, err := regexp.Compile(userPattern)
if err != nil {
return fmt.Errorf("bad pattern: %w", err)
}
הדרו פעם אחת. הידור יקר בהרבה מהתאמה. regexp.MustCompile בתוך פונקציה שרצה בכל בקשה או בכל שורה חוזר על העבודה הזאת בכל פעם. שמרו את ה-*regexp.Regexp המהודר ב-var ברמת החבילה או בשדה של struct. regexp מהודר בטוח לשימוש מהרבה goroutines בו זמנית.
regexp.MatchString(pattern, s) מהדרת ומתאימה בקריאה אחת. היא מתאימה לבדיקה חד פעמית ובזבזנית בתוך לולאה.
חיפוש התאמות
שמות המתודות בנויים לפי תבנית: Find + All? + String? + Submatch? + Index?.
| חלק | משמעות |
|---|---|
All | כל ההתאמות שלא חופפות; מקבלת מגבלה n (-1 לכולן) |
String | עובדת על string; בלעדיו, המתודה מקבלת ומחזירה []byte |
Submatch | מחזירה גם את קבוצות הלכידה |
Index | מחזירה היסטים בבתים במקום הטקסט |
FindString מחזירה "" כשאין התאמה, ואי אפשר להבדיל את זה מהתאמה למחרוזת ריקה. כשהתבנית יכולה להתאים לטקסט ריק, השתמשו ב-FindStringIndex (מחזירה nil כשאין התאמה) או קודם ב-MatchString. גרסאות ה-All מחזירות nil כששום דבר לא מתאים.
קבוצות לכידה
סוגריים לוכדים. FindStringSubmatch מחזירה את ההתאמה המלאה באינדקס 0, ואחריה רשומה אחת לכל קבוצה:
תמיד בדקו nil לפני גישה לפי אינדקס לתוצאה; כשאין התאמה, m[1] גורם ל-panic.
(?:...) מקבץ בלי ללכוד, לחלופות או לחזרות: (?:ab)+. קבוצות עם שם נכתבות (?P<name>...), ומאז Go 1.22 גם (?<name>...).
החלפה
מלכודת קלאסית בהפניות לקבוצות: "$1x" נקרא כקבוצה בשם 1x, שלא קיימת, ומתפרש כמחרוזת ריקה. כתבו "${1}x".
למחרוזת קבועה, strings.ReplaceAll, strings.Contains ו-strings.Split פשוטות ומהירות יותר מ-regexp. פנו ל-regexp כשלטקסט שאתם מחפשים יש צורה ולא ערך קבוע.
תקציר תחביר
Go משתמשת בתחביר RE2, שמכסה את היכולות המוכרות בסגנון Perl:
| תחביר | מתאים ל |
|---|---|
. | כל תו חוץ משורה חדשה (עם (?s), גם שורה חדשה) |
\d \w \s | ספרה, תו מילה [0-9A-Za-z_], רווח לבן (ASCII בלבד) |
\D \W \S | השלילות שלהם |
[abc] [^abc] [a-z] | מחלקות תווים |
\pL \p{Greek} | מחלקות Unicode: כל אות, כל תו יווני |
* + ? {n,m} | חזרה, חמדנית |
*? +? ?? | חזרה, עצלה |
^ $ | תחילת הטקסט וסופו (של השורה, עם (?m)) |
\b | גבול מילה (ASCII) |
a|b | חלופה |
(?i) | בלי הבחנה בין אותיות גדולות לקטנות מאותה נקודה ועד סוף הקבוצה העוטפת; בדרך כלל נכתב בתחילת התבנית |
\d ו-\w מתאימים רק ל-ASCII. ל"כל אות בכל שפה", השתמשו ב-\pL, ולכל ספרת Unicode, ב-\p{Nd}. regexp.QuoteMeta(s) מבצעת escape לכל תו מיוחד ב-s, ואת זה צריך כשבונים תבנית מקלט של משתמש.
מה ש-RE2 לא יודע לעשות
regexp של Go מבטיח שההתאמה לוקחת זמן ליניארי ביחס לאורך הקלט. יכולות שהיו שוברות את ההבטחה הזאת לא נתמכות:
- אין lookahead או lookbehind:
(?=...),(?!...),(?<=...),(?<!...)הן שגיאות הידור. מכיוון ש-Go 1.22 התחילה לקבל(?<name>...)לקבוצות עם שם, lookbehind נכשל עכשיו עם ההודעה המבלבלתinvalid named capture, כפי שהתוכנית הבאה מראה. - אין backreferences: אי אפשר לכתוב
(\w)\1כדי למצוא אות כפולה. - אין כמתים רכושניים או קבוצות אטומיות.
הפתרון כמעט תמיד זהה: ללכוד קצת יותר ממה שצריך, ואז לסנן או לחתוך ב-Go. התמורה היא ש-regexp של Go לא יכול להידחף ל-backtracking קטסטרופלי. תבנית כמו (a+)+$, שיכולה להקפיא מנוע PCRE על קלט קצר, רצה כאן בזמן ליניארי, וזה חשוב כשהתבנית או הקלט מגיעים מבחוץ.
טעויות נפוצות
- הידור בתוך לולאה או handler. הדרו פעם אחת לתוך משתנה ברמת החבילה.
- תבניות במירכאות כפולות.
"\d"הוא אפילו לא מחרוזת Go תקינה; השתמשו ב-backticks. - שכחת עוגנים באימות.
\d{5}מתאים בתוך"abc123456xyz". השתמשו ב-^\d{5}$. - גישה לפי אינדקס לתוצאת submatch בלי בדיקת
nil. - ציפייה ש-
\wאו\bיבינו טקסט שאינו ASCII. השתמשו במחלקות Unicode כמו\pL. - שימוש ב-regexp למחרוזות קבועות. החבילה
stringsברורה ומהירה יותר.
שאלות נפוצות
איך בודקים אם מחרוזת מתאימה ל-regex ב-Go?
הדרו את התבנית פעם אחת עם regexp.MustCompile, וכתבו אותה כ-raw string בתוך backticks כדי שלא יהיה צורך לבצע escape ללוכסנים ההפוכים, ואז קראו ל-re.MatchString(s). בלי עוגנים (^ ו-$), MatchString מחזירה true אם התבנית מתאימה בכל מקום במחרוזת.
האם regexp של Go תומך ב-lookahead וב-lookbehind?
לא. regexp של Go משתמש בתחביר RE2, שאין בו lookahead, lookbehind או backreferences. בתמורה, ההתאמה רצה בזמן ליניארי ביחס לגודל הקלט, כך שתבנית או קלט זדוניים לא יכולים לתקוע את התוכנית. השתמשו במקום זה בקבוצות לכידה ובקצת קוד Go, או פצלו את הבדיקה לשני ביטויים רגולריים.
מה ההבדל בין regexp.Compile לבין regexp.MustCompile?
Compile מחזירה את התבנית המהודרת ושגיאה. MustCompile גורמת ל-panic כשהתבנית לא תקינה. השתמשו ב-MustCompile לתבניות שכתובות בקוד המקור, בדרך כלל במשתנה ברמת החבילה, כך ששגיאת הקלדה תיכשל כבר בהפעלה. השתמשו ב-Compile לתבניות שמגיעות ממשתמשים או מקובצי הגדרות.
איך מקבלים את קבוצות הלכידה של regex ב-Go?
השתמשו ב-FindStringSubmatch, שמחזירה slice שבו אינדקס 0 הוא ההתאמה המלאה ואינדקסים 1, 2, ... הם הקבוצות, או nil אם אין התאמה. לכל ההתאמות השתמשו ב-FindAllStringSubmatch(s, -1). את הקבוצות עם שם (?P<year>\d{4}) אפשר למצוא עם re.SubexpIndex("year").