تعلم التعبيرات النمطية في حوالي 55 دقيقة

نقلها للعربية عبد الرحمن أبو رزمة.

التعبيرات النمطية (= Regular expressions أو اختصاراً "regexes") أدوات بحث واستبدال نصية قوية للغاية. تستعمل التعبيرات النمطية عند تحرير النصوص في محرر نصوص، لأغراض:

  • لاختبار ما إذا كان النص يحتوي على عيّنة نصية ما
  • اكتشاف هذه العيّنات المطابقة، إن وجدت
  • استخراج معلومات (نصوص فرعية) من النص
  • إجراء تعديلات على هذا النص.

وكما تعمل في محررات النصوص، فإنّ لغات البرمجة عالية المستوى جميعَها تقريباً تتضمّن دعماً للتعبيرات النمطية. وفي هذا السياق يكون "النص" متغيراً نصياً، ولكن العمليات تبقى ذاتها، بل إنّ بعضَ لغات البرمجة (Perl، JavaScript) تقدّم صيغة مضمّنة للعمليات على التعبيرات النمطية.

ولكن ما هي؟

التعبير النمطي مجرد نص. لا حدّ له من حيث الطول، ولكنه في الأعمّ نصٌ قصير. هذه بعض الأمثلة له:

  • I had a \S+ day today
  • [A-Za-z0-9\-_]{3,16}
  • \d\d\d\d-\d\d-\d\d
  • v(\d+)(\.\d+)*
  • TotalMessages="(.*?)"
  • <[^<>]>

النصّ في الواقع برنامجُ حاسبٍ صغيرٌ للغاية، وصيغة التعبير النمطي لغة برمجة محددة النطاق صغيرة، ومختصرة. فإذا أخذت ذلك في الحسبان، لن يصعب عليك أن تتعلم أن:

  • يمكن أن يقسّم كل تعبير نمطي إلى عدة تعليمات مسلسلة. "اعثر على هذا، ثم على ذاك، وبعدها على أحد هؤلاء..."
  • للتعبير النمطي دخل (النص) وخرج (العيّنات المطابقة، وأحياناً النص المعدّل).
  • الأخطاء القواعدية واردة - إذن ليس كل نص تعبيراً نمطياً صالحاً!
  • في صيغتها بعض الغرابة، وربّما، بعض الأهوال.
  • يمكن في بعض الأحيان ترجمة التعبير النمطي ليعمل أسرع.

وكذلك لدينا اختلافات كبيرة في مشغّلات التعبيرات النمطية. وفي هذه الوثيقة سأبقى مركزاً على الصيغة الجوهرية التي تتشارك بها المشغّلات جميعها تقريباً.

تمرين

احصل على محرر نصوص يدعم التعبيرات النمطية. أنا أفضّل Notepad++.

حمّل نص قصة نثرياً مثل Project Gutenberg's version of H. G. Wells' The Time Machine وافتحه.

حمّل معجم كلمات مثل هذا، فكّ ضغطه وافتحه.

وهذا كل شيء حتى الآن. وسيأتيك المزيد من التمرينات قريباً.

ملاحظة: صيغة التعبيرات النمطية غير متوافقة على الإطلاق مع صيغة سرد أسماء الملفات، مثل *.xml.

الصيغة الأساسية للتعبيرات النمطية

الحرفيات

تحتوي التعبيرات النمطية مزيجاً من المحارف التي تؤخذ حرفياً، والتي تمثل ذاتها، ومحارف خاصة تسمّى metacharacters، والتي تصنع أشياءَ خاصّة.

وهنا الأمثلة مجدّداً. سأخطط المحارف الخاصة.

  • I had a \S+ day today
  • [A-Za-z0-9\-_]{3,16}
  • \d\d\d\d-\d\d-\d\d
  • v(\d+)(\.\d+)*
  • TotalMessages="(.*?)"
  • <[^<>]*>

تعامل معظم المحارف، يتضمن ذلك جميع محارف الأبجدية معاملةً حرفية. وهذا يعني أنها تعثر على ذاتها في النص. مثلاً التعبير النمطي

cat

يعني "اعثر على c، متبوعاً بـ a متبوعاً بـ t".

هذا جيد حتى الآن. وهذا يعني يطابق عمليات

  • مربعات حوار البحث المألوفة
  • التابع String.indexOf() من لغة Java
  • التابع strpos() من لغة PHP
  • الخ.

ملاحظة: التعبيرات النمطية حساسة لحالة الأحرف، إلّا عندما يصرّح عن خلاف ذلك. على كل حال، جميع المشغّلات تقريباً تقدّم خياراً لتعطيل الحساسية لحالة الأحرف.

ملاحظة أخرى: من المهم أن تعرف ما إذا كان "النص" سلسلة من البايتات أو سلسلة من محارف اليونيكود Unicode. أما إذا اقتصر عملك على المحارف العادية المعرفة ASCII، فإن للحالتين نفس المؤدى، ويمكنك أن تمضي في عملك دون الحاجة لمعرفة الفرق...

النقطة

أول محارفنا الخاصة هو النقطة؛ .. تعثر النقطة . على أيّ محرفٍ وحيد. يعني التعبير النمطي

c.t

"اعثر على c، متبوعاً بأي محرفٍ، ثم متبوعاً بـ t".

وسيعثر هذا - عندما يطبق في قطعة نصية -، على cat،cot، czt وحتى على النص الحرفي c.t (c، نقطة، t)، ولكن ليس ct، أو coot.

وتؤخذ المسافة بالاعتبار في التعبيرات النمطية. يعني التعبير النمطي

c t

"اعثر على c، متبوعاً بفراغ، متبوعاً بـ t".

يمكن تهريب أي محرف خاص بالمائل الخلفي \. وسيعيد هذا معنى المحرف الخاص إلى مطابقته الحرفية. إذن يعني التعبير النمطي

c\.t

"اعثر على c، متبوعاً بنقطة، متبوعاً بـ t".

المائل الخلفي من المحارف الخاصة، مما يعني أنه يمكن تهريبه ليعني الخط ذاته حرفياً. إذن يعني التعبير النمطي

c\\t

"اعثر على c، متبوعاً بالمائل الخلفي، متبوعاً بـ t".

تحذير! تعني النقطة .، في بعض المشغّلات؛ اعثر على أي محرف باستثناء محرف فاصل الأسطر. وهذا يعني أن فاصل الأسطر يمكن أن يختلف من مشغّل لآخر أيضاً، راجع وثائق مشغّلك. لكنني سأفترض في هذه الوثيقة أن . ستعثر على أيّ محرف مهما كان.

في كلا الحالين، هناك عادةً خيار لتعديل هذا السلوك، والذي يسمّى عادةً DOTALL أو ما شابه.

تمرين

في ملف المعجم، باستخدام ما تعلمّته من التعبيرات النمطية حتى الآن؛ اعثر على الكلمات بحيث يكون هناك حرفي z أبعد ما يكونان عن بعضهما.

تمرين

في نص The Time Machine، استخدم التعبيرات النمطية لتعثر على جملة تنتهي بظرف.

فئات المحارف

فئة المحرف مجموعة من المحارف محصورة بقوسين مربعين. هذا يعني، "اعثر على أيٍّ من هذه المحارف".

  • يعني التعبير النمطي c[aeiou]t، "اعثر على c متبوعاً بحرفٍ صوتي متبوعاً بـ t". وسيجد هذا، عند استعماله في قطعة نصية cat، و cet، و cit، و cot، و cut.
  • يعني التعبير النمطي [0123456789] "اعثر على رقم".
  • يعني التعبير النمطي [a] نفس معنى a: "اعثر على a".
  • يعني التعبير النمطي [ ] "اعثر على فراغ".

بعض الأمثلة على التهريب:

  • \[a\] يعني "اعثر على قوس مربع أيسر متبوعاً بـ a متبوعاً بقوس مربع أيمن".
  • [\[\]ab] يعني "اعثر على قوس مربع أيسر أو قوس مربع أيمن أو a أو b".
  • [\\\[\]] يعني "اعثر على مائل خلفي أو قوس مربع أيسر أو قوس مربع أيمن" !

لا يهم الترتيب ولا التكرار في فئات المحارف. [dabaaabcc] يماثل تماماً [abcd].

ملاحظة هامة

تختلف "القواعد" داخل فئات المحارف عنها خارجها. فبعض المحارف تسلك سلوك المحارف الخاصة داخل فئات المحارف ولكنها محارف حرفية خارجها. وبعضها على العكس. ويسلك البعض سلوك محارفٍ خاصة في كلا الحالين، ولكننها تعني معانٍ مختلفة في كل حال!

تعني . في الواقع العملي "اعثر على أيّ محرف"، ولكن [.] يعني "اعثر على نقطة". ليسا الشيءَ ذاتَه!

تمرين

في ملف المعجم، باستخدام ما تعلّمته حتى الآن؛ استعمل التعبيرات النمطية للعثور على الكلمات التي تحوي أكبر عددٍ من الحروف الصوتية المتتالية، والكلمات التي تحوي أكبر عدد من الحرف الساكنة المتتالية.

مجالات فئات المحارف

يمكنك أن تستخدمَ داخل فئات المحارف، عن مجالٍ من الحروف أو الأرقام، باستخدام الشَرطة:

  • [b-f] نفسُ [bcdef] ويعني "اعثر على b أو c أو d أو e أو f".
  • [A-Z] نفسُ [ABCDEFGHIJKLMNOPQRSTUVWXYZ] ويعني "اعثر على حرفٍ بالكتابة الكبيرة".
  • [1-9] نفسُ [123456789] ويعني "اعثر على رقم باستثناء الصفر".

ليس للشَرطة أيَ معنىً خاص خارج فئات المحارف. ويعني التعبير النمطي a-z "اعثر على a متبوعاً بشَرطة متبوعاً z".

يمكن أن تتضامن المجالات والمحارف المفردة في فئة المحارف الواحدة ذاتها:

  • يعني [0-9.,] "اعثر على رقم أو نقطة أو فاصلة".
  • يعني [0-9a-fA-F] "اعثر على رقم بنظام العد الستة عشري".
  • يعني [a-zA-Z0-9\-] "اعثر على محرف رقمي حرفي أو شَرطة".

مع أنك يمكنك محاولة استخدام محارف غير رقمية ولا حرفية كنهاية المجال (مثال abc[!-/]def)، هذا ليس تعبيراً نمطياً صالحاً في جميع المشغّلات. وحتى حيث يكون مسموحاً، فلا يظهر جليّا للقارئ ما المحارف المضمَّنة في مجالٍ كهذا. استعمل بحذر (وأعني بهذا ألا تستعمله).

وبالمثل؛ فإنّ نهايات المجالات يجب أن تُختار من نفس المجال. وحتى إذا كان التعبير النمطي مثل [A-z] تعبيراً نمطياً صالحاً في مشغّلك، ربّما لا تنفّذ الأمر الذي تريده. (تلميح: توجد محارف بين Z و a...)

تحذير. المجالات مجالات محارف، لا مجالات أعداد. فيعني التعبير النمطي [1-31] "اعثر على 1 أو 2 أو 3وليس "اعثر على عدد صحيح بين 1 و 31".

تمرين

باستخدام ما تعلمته حتى الآن، اكتب تعبيراً نمطياً يعثر على تاريخ بصيغة YYYY-MM-DD.

استثناء فئة محارف

بإمكانك كذلك استثناء فئة محارف بوضع علامة ^ في البداءة.

  • يعني [^a] "اعثر على أي محرف عدا a".
  • يعني [^a-zA-Z0-9] "اعثر على محرف عدا الحروف أو الأرقام".
  • يعني [\^abc] "يعني اعثر على رمز ^ أو a أو b أو c".
  • يعني [^\^] "اعثر على حرف عدا رمز ^".
تمرين

في ملف المعجم، استخدم التعبيرات النمطية للعثور على أمثلة وعكسها بحث تطابق القاعدة "i قبل e إلا أن تكون بعدَ c".

فئات المحارف المختصرة

يعني التعبير النمطي \d نفس معنى [0-9]: "اعثر على رقم (digit)". (لتبحث عن مائل خلفي متبوعاً بـ d؛ استخدم التعبير النمطي \\d.)

يعني \w نفس معنى [0-9A-Za-z_]: "اعثر على محارف كلمة word".

يعني \s "اعثر على محرف فراغ space (مسافة, جدول tab, مفتاح الإدخال أو علامة سطر جديد)".

والمزيد،

  • يعني \D نفس معنى [^0-9]: "اعثر على ما ليس رقماً".
  • يعني \W نفس معنى [^0-9A-Za-z_]: "اعثر على محرفٍ من غير محارف الكلمة".
  • يعني \S "اعثر على محرف غير محارف المسافات".

إن هذه الاختصارات شائعة جداً ويجب عليك تعلمها.

إن النقطة أساساً - كما قد تكون لاحظت - .، فئة محارف تحتوي جميع المحارف المحتملة.

تقدّم الكثير من المشغّلات الكثير من فئات المحارف الإضافية، أو تقدّم خيارات توسّع بها الفئات الموجودة لتغطي محارفاً أخرى خارج نطاق المحارف النموذجية ASCII. تلميح: نظام يونيكود يحتوي المزيد من "المحارف الرقمية" زيادةً على مجرّد 0 إلى 9، وبالمثل يصحّ هذا على فئات "الكلمة" ومحارف "المسافة". راجع وثائق مشغّلك.

تمرين

اختصر التعبير النمطي [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].

المضاعفات

يمكنك استخدام الأقواس المزخرفة لتضع مضاعفاً أو تكراراً بعد حرفيٍّ أو فئة محارف.

  • التعبير النمطي a{1} هو ذاته a ويعني "اعثر على a".
  • يعني a{3} "اعثر على a متبوعاً بـ a متبوعاً بـ a".
  • يعني a{0} "اعثر على نص فارغ". يظهر هذا التعبير عند استخدامه لوحده أنه بغير فائدة. إذا استعملت هذا التعبير النمطي على أي قطعة من نص، ستحصل على مطابقة في الحال، مباشرةً عند أول نقطة يبدأ عندها البحث. يبقى هذا صحيحاً حتى لو كان النص الذي تبحث فيه نصاً فارغاً!
  • يعني a\{2\} "اعثر على a متبوعاً بقوس مزخرف أيسر متبوعاً بـ 2 متبوعاً بقوس مزخرف أيمن".
  • ليس للأقواس مزخرف أي معنىً خاص داخل فئات المحارف. يعني [{}] "اعثر على قوس مزخرف أيمن أو أيسر".

تحذير. ليس للمضاعفات ذاكرة. فيعني التعبير النمطي [abc]{2} "اعثر على a أو b أو c، متبوعاً بـ a أو b أو c". وهذا نفس معنى "اعثر على aa أو ab أو ac أو ba أو bb أو bc أو ca أو cb أو cc". ولا يعني "اعثر على aa أو bb أو cc"!

Exercises

اختصر هذه التعبيرات النمطية:

  • z.......z
  • \d\d\d\d-\d\d-\d\d
  • [aeiou][aeiou][aeiou][aeiou][aeiou][aeiou]
  • [bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]

مجالات المضاعفات

يمكن أن يكون للمضاعفات مجالات:

  • x{4,4} هو ذاته x{4}.
  • يعني colou{0,1}r "اعثر على colour أو color".
  • يعني a{3,5} "اعثر على aaaaa أو aaaa أو aaa".

لاحظ أن الأفضلية للاحتمال الأطول، لأن المضاعفات شرهة. إذا كان نصك I had an aaaaawful day فإن هذا التعبير النمطي سوف يعثر على الـ aaaaa في aaaaawful. ولن يتوقف عند ثلاثة الـ a.

المضاعفات شرهة، ولكنها لن تتجاهل مطابقة جيدة تامة. إذا كان نص الإدخال I had an aaawful daaaaay، فإن هذا التعبيرالنمطي سيعثر على الـ aaa في aaawful عند أول مطابقة. وفقط عندما تقول له "اعثر لي على مطابقة أخرى" فإنه سيستمر بالبحث وسيعثر على الـ aaaaa في daaaaay.

يمكن أن تكون المضاعفات مفتوحة النهاية:

  • يعني a{1,} "اعثر على واحد أو المزيد من الـ a المتتالية". سيبقى مضاعفك هنا شرهاً. بعد العثور على أول a، سيحاول العثول على أكبر كمية من الـ a قدر الإمكان.
  • يعني .{0,} "اعثر على أي شيء". مهما كان نص الإدخال لديك - حتى لو كان نصاً فارغاً - سينجح هذا التعبير النمطي بمطابقة النص بأكمله ويعيده لك.
تمارين

اكتب تعبيراً نمطياً للعثور على نص محاط بعلامة الاقتباس المزدوجة. يمكن لهذا النص أن يحوي أي عدد من المحارف.

عدّل تعبيرك النمطي حيث إن النص المحاط بعلامة الاقتباس المزدوجة الذي عثرت عليه لا يحتوي على المزيد من علامات الاقتباس المزدوجة بين العلامتين الأصليتين. ومجدّداً، استعمل فقط ما قد تعلمته.

المضاعفات المختصرة

يعني ? نفس معنى {0,1}. على سبيل المثال، يعني colou?r "اعثر على colour أو color".

يعني * نفس معنى {0,}. على سبيل المثال، يعني .* "اعثر على أي شيء"، تماماً كسابقه.

يعني + نفس معنى {1,}. على سبيل المثال، يعني \w+ "اعثر على كلمة". ونعني بالـ "كلمة" هنا سلسلة من تتكون من "محرف كلمة" أو أكثر، مثلاً _var أو AccountName1.

إنّ هذه الاختصارات شائعة إلى أبعد الحدود ويجب أن تتعلمها. وأيضاً:

  • يعني \?\*\+ "اعثر على علامة الاستفهام متبوعةً بعلامة النجمة متبوعةً بعلامة الزائد".
  • يعني [?*+] "اعثر على علامة الاستفهام أو علامة النجمة أو علامة الزائد".
تمارين

اختصر هذه التعبيرات النمطية:

  • ".{0,}" و "[^"]{0,}"
  • x?x?x?
  • y*y*
  • z+z+z+z+
تمرين

اكتب تعبيراً للعثور على كلمتين منفصلتين بمحارف ما ليس كلمة. ماذا عن ثلاث كلمات؟ وماذا عن ست؟

ما ليس شرهاً

يعني التعبير النمطي ".*" "اعثر على علامتة اقتباس مزدوجة، متبوعةً بأكثر من يمكن من المحارف، متبوعةً بعلامة اقتباس مزدوجة". لاحظ كيف يمكن للمحارف التي عثرنا عليها داخل العلامتين باستخدام .*، يمكن بسهولة أن تحوي المزيد من علامات القتباس المزدوجة. لا يكون هذا عادةً مفيداً جداً.

يمكن جعل المضاعفات غير شرهة بإلحاق علامة الاستفهام. وهذا يعكس ترتيب الأفضلية:

  • يعني \d{4,5}? "اعثر على \d\d\d\d أو \d\d\d\d\d". ولهذا نفس تأثير \d{4} تماماً.
  • يعني colou??r أن colou{0,1}?r والتي تعني "اعثر على color أو colour". ولهذا نفس تأثير colou?r.
  • يعني ".*?" "اعثر على علامة اقتباس مزدوجة، متبوعةً بأقل ما يمكن من المحارف ، متبوعةً بعلامة اقتباس مزدوجة". وهذا - بعكس المثالين السابقين أعلاه - مفيد في الواقع.

التناوب

يمكن مطابقة واحد من عدة خيارات باستخدام الأنبوب |:

  • يعني cat|dog "اعثر على cat أو dog".
  • red|blue| و red||blue و |red|blue جميعها تعني "اعثر على red أو blue أو نصاً فارغاً".
  • a|b|c ذاتها [abc].
  • يعني cat|dog|\| "اعثر على cat أو dog أو رمز الأنبوب".
  • يعني [cat|dog] "اعثر على a أو c أو d أو g أو o أو t أو رمز الأنبوب".
تمارين

اختصر هذه التعبيرات النمطية قدر الإمكان:

  • s|t|u|v|w
  • aa|ab|ba|bb
  • [abc]|[^abc]
  • [^ab]|[^bc]
  • [ab][ab][ab]?[ab]?
تمرين

اكتب تعبيراً نمطياً لمطابقة عدد صحيح يقع بين 1 و 31. تذكر، [1-31] ليس الجواب الصحيح.

التجميع

يمكنك تجميع التعبيرات باستخدام الأقواس:

  • للعثور على يوم من الأسبوع، استخدم (Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day.
  • (\w*)ility هو ذاته \w*ility. ويعني كلاهما "اعثر على كلمة تنتهي باللاحقة ility". تابع القراءة لتعرف لماذا قد يكون التعبير الأول مفيداً...
  • يعني \(\) "اعثر على قوس أيسر متبوعاً بقوس أيمن".
  • يعني [()] "اعثر على قوس أيسر أو قوس أيمن".
تمرين

استخدم التعبيرات النمطية في نص The Time Machine، لتعثر على نص محصور بين قوسين. ثم عدّل جوابك ليطابق النتائج التي لا تحوي أقواساً فيها.

يمكن أن تحتوي المجموعات نصاً فارغاً:

  • يعني (red|blue|) "اعثر على red أو blue أو نص فارغ".
  • يعني abc()def نفس معنى abcdef.

يمكنك استخدام المضاعفات على المجموعات:

  • يعني (red|blue)? نفس معنى (red|blue|).
  • يعني \w+(\s+\w+)* "اعثر على كلمة أو أكثر مفصولة بمسافة فارغة".
تمرين

اختصر \w+\W+\w+\W+\w+ و \w+\W+\w+\W+\w+\W+\w+\W+\w+\W+\w+.

فواصل الكلمات

فواصل الكلمة المكان بين محرف من محارف الكلمة ومحرف من غيرها. تذكر، محارف الكلمة \w والتي هي [0-9A-Za-z_]، ومحارف غير الكلمة \W والتي هي [^0-9A-Za-z_].

تعدّ بداءة النص ونهايته دائماً كفواصل كلمات أيضاً.

هنالك في نص الإدخال it's a cat، ثمانية فواصل كلمات. وإذا أضفنا مسافة لاحقة بعد cat، فسيكون هناك تسعة فواصل.

  • يعني التعبير النمطي \b "اعثر على فاصل كلمة".
  • يعني \b\w\w\w\b "اعثر على كلمة ثلاثية الأحرف".
  • يعني a\ba "اعثر على aمتبوعاً بفاصل كلمة، متبوعاً بـ a". لن ينجح التعبير النمطي هذا في العثور على أية مطابقة، مهما كان نص إدخالك.

فواصل الكلمات ليست محارفاً. وحجمها صفر. تتطابق التعبيرات النمطية التالية في نتائجها:

  • (\bcat)\b
  • (\bcat\b)
  • \b(cat)\b
  • \b(cat\b)
تمرين

اعثر على أطول كلمة في المعجم.

فواصل الأسطر

تنقسم كل قطعة نص إلى سطر واحد أو أكثر، مفصولة بـ فواصل الأسطر، كالتالي:

  • سطر
  • فاصل أسطر
  • سطر
  • فاصل أسطر
  • ...
  • فاصل أسطر
  • سطر

لاحظ كيف ينتهي النص دوماً بسطر، ولا ينتهي أبداً بفاصل أسطر. ولكن، يمكن لأي سطر ألا يحوي أية محارف، وكذلك السطر الأخير.

بداءة السطر المكان بين فاصل الأسطر وأول محرف في السطر التالي. كما هي فواصل الكلمات، بداءة النص تعد كذلك بداءة سطر.

نهاية السطر المكان بين آخر محرف من السطر وفاصل الأسطر. كما هي فواصل الكلمات، نهاية النص تعد كذلك نهاية سطر.

لذلك فإن تقسيمنا سيصير:

  • بداءة سطر، سطر، نهاية سطر
  • فاصل أسطر
  • بداءة سطر، سطر، نهاية سطر
  • فاصل أسطر
  • ...
  • فاصل أسطر
  • بداءة سطر، سطر، نهاية سطر

بناءً على هذا:

  • يعني التعبير النمطي ^ "اعثر على بداءة سطر".
  • يعني التعبير النمطي $ "اعثر على نهاية سطر".
  • يعني ^$ "اعثر على سطرٍ فارغ".
  • سيعثر ^.*$ على نصك كله، لأن فاصل الأسطر محرف و . ستعثر عليه. إذا أردت العثور على سطرٍ وحيد، استعمل النمط غير الشره ^.*?$.
  • يعني \^\$ "اعثر على علامة البداءة^ متبوعةً بعلامة الدولار".
  • يعني [$] "اعثر على علامة الدولار". ولكن، [^] ليست تعبيراً نمطياً صالحاً. تذكر أن لعلامة البداءة ^ معنىً مختلف خاص داخل الأقواس المربعة! ولتضعها داخل فئة محارف، استخدم [\^].

كما هي فواصل الكلمات، فواصل النصوص ليست محارف. وحجمها صفر. تتطابق التعبيرات النمطية التالية في المؤدى:

  • (^cat)$
  • (^cat$)
  • ^(cat)$
  • ^(cat$)
تمرين

استخدم التعبيرات النمطية للعثور على أطور سطر في نص The Time Machine.

فواصل النصوص

تقدّم الكثير من المشغلات خياراً لتغيّر معنى ^ و $ من "بداءة السطر" و "نهاية السطر" على التوالي إلى "بداءة النص" و "نهاية النص" على التوالي.

أما المشغّلات الأخرى فتقدّم محارفاً خاصة منفصلة \A و \z لهذا الغرض.

البحث والاستبدال

هنا حيث تبدأ التعبيرات النمطية تظهر قوتها.

استبدال المجموعات

قد علمت أن الأقواس تستخدم لحصر المجموعات. وهي تستخدم أيضاً لمعالجة النصوص الفرعية. إذا اعتبرنا التعبير النمطي برنامج حاسب صغيراً جداً، فإن مطابقة المجموعات (جزء من) خرج هذا البرنامج.

يعني التعبير النمطي (\w*)ility "اعثر على كلمة منتهية باللاحقة ility". مطابقة المجموعة 1 هي الجزء المطابَق بـ \w*. على سبيل المثال، إذا كان نصنا يحتوي على الكلمة accessibility، فإن مطابقة المجموعة 1 هو accessib. إذا كان النص كله ility فقط، فإن مطابقة المجموعة 1 هي نص فارغ.

يمكنك أن تعالج عدة مجموعات في الوقت ذاته، ويمكن أن تتداخل حتى. المجموعات المطابقة تعدّ من اليسار إلى اليمين . يكفيك أن تعتمد الأقواس اليسرى في العد.

افترض أن تعبيرك النمطي (\w+) had a ((\w+) \w+). وأن نص الإدخال لديك I had a nice day عندئذ تكون:

  • مطابقة المجموعة 1 I.
  • مطابقة المجموعة 2 nice day.
  • مطابقة المجموعة 3 nice.

وتعطيك أيضاً - بعض المشغّلات - مطابقة المجموعة 0، التي تعطيك المطابقة الكاملة: I had a nice day.

في بعض بعض المشغّلات إن لم تجد مطابقات المجموعات، فستملأ مطابقة المجموعة 1 تلقائياً من قيمة مطابقة المجموعة 0.

نعم، كل هذا يعني أن الأقواس نوعاً ما متعددة الاستعمالات. تعطي بعض المشغّلات صيغة مستقلة للتصريح عن مجموعات "غير قابلة للمطابقة"، لكن هذه الصيغة غير قياسية لذلك لن نتكلم عنها هنا.

يكون عدد المجموعات المطابقة العائد من مطابقة ناجحة مساوياً دوماً لعدد المجموعات المطابقة في التعبير النمطي الأصلي. تذكر هذا، إذ يمكن أن يساعدك في بعض الحالات المربكة.

يعني التعبير النمطي ((cat)|dog) "اعثر على cat أو dog". هناك في هذه الحال دائماً مجموعتين مطابقتين. إذا نص الإدخال لدينا dog، فإن مطابقة المجموعة 1 dog، ومطابقة المجموعة 2 نص فارغ، لأن هذا الخيار لم يستعمل.

يعني التعبير النمطي a(\w)* "اعثر على كلمة تبداً بـ a". هناك دائماً مجموعة مطابقة واحدة:

  • إذا كان نص الإدخال a، فمطابقة المجموعة 1 نص فارغ.
  • إذا كان نص الإدخال ad, فمطابقة المجموعة 1 d.
  • إذا كان نص الإدخال apricot, فمطابقة المجموعة 1 t. لكن، مطابقة المجموعة 0 ستكون الكلمة كلها apricot.

الاستبدال

حالما تستخدم تعبيراً نمطياً للعثور على نص، يمكنك تخصيص نصٍ آخر لاستبداله به. النص الثاني تعبير الاستبدال. في البدء، يبدو هذا تماماً كـ

  • مربع بحث واستبدال تقليدي
  • تابع Java String.replace()
  • تابع PHP str_replace()
  • الخ.
تمرين

استبدل جميع الأحرف الصوتية في نص رواية The Time Machine بالحرف r. تأكد من استخدام حالة الحرف الصحيحة!

يمكنك الإشارة إلى المجموعات المطابقة في تعبير الاستبدال. وهذا الشيء الخاص الوحيد الذي يمكنك فعله في تعبيرات الاستبدال، وهو مفيدٌ للغاية لأنه يعني أنك لا تحتاج إلى التدمير الكامل لكل ما عثرت عليه توّاً.

لنقل أنك تحاول استبدال صيغة التاريخ الأمريكية (MM/DD/YY) بصيغة التاريخ القياسية ISO 8601 (YYYY-MM-DD).

  • ابداً بالتعبير النمطي (\d\d)/(\d\d)/(\d\d). لاحظ أن هذه ثلاث مجموعات مطابقة: الشهر، واليوم، والسنة في رقمين.

  • تكون الإشارة للمجموعات المطابقة باستخدام المائل الخلفي ثم رقم المجموعة المطابقة. إذن، تعبير الاستبدال لديك صار 20\3-\1-\2.

  • إذا كان نص إدخالك يحتوي 03/04/05 (يمثّل آذار (مارس) 4، 2005)، إذن

    • مطابقة المجموعة 1 03.
    • مطابقة المجموعة 2 04.
    • مطابقة المجموعة 3 05.
    • سيكون نص الاستبدال 2005-03-04.

يمكنك الإشارة لمطابقة المجموعة أكثر من مرة في تعبير الاستبدال.

  • لتزاوج حرفين صوتيين، استخدم التعبير النمطي ([aeiou]) وتعبير الاستبدال \1\1.

يجب تهريب المائل الخلفي في تعبير الاستبدال. مثلاً، لنقل إنه لديك نص تريد استخدامه حرفياً في برنامج حاسوبي. هذا يعني أنك بحاجة إلى أن تضع مائلاً خلفياً قبل كل علامة اقتباس مزدوجة أو مائل خلفي في النص الأصلي.

  • تعبيرك النمطي سيكون ([\\"]). مطابقة المجموعة 1 علامة اقتباس مزدوجة أو مائل خلفي.

  • تعبير الاستبدال لديك سيكون \\\1؛ مائل خلفي حرفياً تلحقه علامة الاقتباس المزدوجة أو المائل الخلفي المطابقان.

تستخدم بعض المشغلات علامة الدولار $ عوضاً عن المائل الخلفي للإشارة إلى المجموعات المطابقة.

تمرين

اكتب تعبيراً نمطياً وتعبير استبدال يمكنه أخذ صيغة الوقت كـ 23h59 ويحولها إلى 23:59.

الإشارة الرجعية

يمكنك كذلك الإشارة إلى مجموعة مطابقة سابقة في ذات التعبير النمطي. يسمى هذا الإشارة الرجعية.

مثلاً، تذكر أن التعبير النمطي [abc]{2} يعني "اعثر على aa أو ab أو ac أو ba أو bb أو bc أو ca أو cb أو cc". ولكن التعبير النمطي ([abc])\1 يعني "اعثر على aa أو bb أو cc".

تمرين

اعثر في المعجم على أطول كلمة تتكون من نفس النص مكرراً مرتين (مثال papa، coco).

البرمجة باستخدام التعبيرات النمطية

بعض الملاحظات الخاصة بهذا الموضوع:

متلازمة زيادة المائل الخلفي

في بعض لغات البرمجة، كالجافا؛ ليس هناك دعم خاص لنصوص تحوي تعبيراتٍ نمطية. للنصوص قواعد خاصة بها لتهريب المحارف، وهي تضاف كطبقة أعلى قواعد تهريب التعبيرات النمطية، عادة ما يؤدي إلى تعدد استعمالات المائل الخلفي. مثلاً (ما زلنا في جافا):

  • للعثور على رقم، فإن التعبير النمطي \d يصير String re = "\\d"; في البرنامج المصدر.
  • للعثور على نص محصور بعلامة الاقتباس المزدوجة، "[^"]*" يصير String re = "\"[^\"]*\"";.
  • للعثور على المائل الخلفي أو قوس مربع أيسر أو قوس مربع أيمن، فإن التعبير النمطي [\\\[\]] يصير String re = "[\\\\\\[\\]]";.
  • String re = "\\s"; و String re = "[ \t\r\n]"; متساويان. لاحظ "المراحل" المختلفة من التهريب.

في لغات برمجة أخرى، تحاط التعبيرات النمطية بمعاملات خاصة، عادةً المائل الأمامي /. هنا كود جافاسكربت:

  • للعثور على رقم، \d تصبح var regExp = /\d/;.
  • للعثور على مائل خلفي أو قوس مربع أيسر أو قوس مربع أيمن، var regExp = /[\\\[\]]/;.
  • var regExp = /\s/; و var regExp = /[ \t\r\n]/; متساويان.
  • هذا يعني بالطبع أن المائل الأمامي يجب تهريبه عوضاً عن علامة الاقتباس المزدوجة. للعثور على الجزء الأول من عنوان URL: var regExp = /https?:\/\//;.

أتمنى أن ترى لماذا أحاول أن أحميك من المائل الخلفي حتى هذه المرحلة.

التعبيرات النمطية المولَّدة

كن حذراً عند بناء نص تعبير نمطي مولّد. إذا لم يكن النص الذي تستخدمه ثابتاً، إذن قد يحتوي محارف خاصة غير متوقعة. وهذا قد يؤدي إلى خطأ قواعدي. أو أسوأ، أن ينتج عنه تعبير نمطي صحيح قواعدياً، لكنه نتائجه غير متوقعة.

كود جافا فيه أخطاء:

String sep = System.getProperty("file.separator");
String[] directories = filePath.split(sep);

الخطأ البرمجي: String.split() تريد من sep أن تكون تعبيراً نمطياً. لكن على نظام ويندوز، sep ستكون نصاً يحتوي على المائل الخلفي، "\\". وهذا تعبيرٌ نمطي غير صالح قواعدياً! النتيجة: استثناء PatternSyntaxException.

تقدم أية لغة برمجية جيدة طريقةً ما لتهريب جميع المحارف الخاصة في أي نص. في جافا مثلاً، ستكتب ما يلي:

String sep = System.getProperty("file.separator");
String[] directories = filePath.split(Pattern.quote(sep));

التعبيرات النمطية في الحلقات

ترجمة نص تعبير نمطي في "برنامج" قيد التشغيل عملية مكلفة نسبياً. ربما تجد تحسناً في الأداء إذا تمكنت من تجنب هذا داخل الحلقات.

نصائح متنوعة

التحقق من الإدخال

يمكن استعمال التعبيرات النمطية للتحقق من دخل المستخدم. لكن يمكن لإجراءات التحقق مفرطة الدقة أن تجعل حياة المستخدم بالغة الصعوبة. الأمثلة أدناه:

أرقام بطاقات الدفع

على أحد المواقع، أدخلت رقم بطاقتي كـ 1234 5678 8765 4321. رفضها الموقع. كان يتحقق من حقل الإدخال باستخدام \d{16}.

يجب أن يسمح التعبير النمطي بالفراغات، والشرطات.

في الواقع، لم لا تجرد أولاً جميع المحارف غير الرقمية ثم تقوم بالتحقق؟ ولعمل هذا، استخدم التعبير النمطي \D، ونصاً فارغاً لتعبير الاستبدال.

تمرين

اكتب تعبيراً نمطياً يمكنه التحقق من رقم بطاقتي من دون تجريده من المحارف غير الرقمية أولاً.

الأسماء

لا تستعمل التعبيرات النمطية للتحقق من أسماء الأشخاص. في الواقع، لا تتحقق من الأسماء على الإطلاق إن أمكنك.

أفكار خاطئة للمبرمجين عن الأسماء:

  • الأسماء لا تحتوي فراغات.
  • الأسماء لا تحتوي علامات ترقيم.
  • الأسماء مكونة فقط من محارف ASCII.
  • الأسماء مقصورة على أية مجموعة محارف معيّنة.
  • الأسماء تحتوي دوماً على ن محرفاً على الأقل.
  • الأسماء لا تحتوي أبداً على أكثر من ك محرفاً.
  • لدى الأشخاص دوماً اسم علم واحد بالضبط.
  • لدى الأشخاص دوماً اسم أوسط واحد بالضبط.
  • لدى الأشخاص دوماً لقب واحد بالضبط.
  • ...
عناوين البريد الإلكتروني

لا تستعمل التعبيرات النمطية للتحقق من عناوين البريد الإلكتروني.

أولاً، هذه مهمة غاية في الصعوبة للقيام بها بشكل صحيح. عناوين البريد الإلكتروني تخضع بالفعل للتعبيرات النمطية، ولكن التعبير سيكون طويل ومعقد بشكل مذهل. أي شكلٍ أخصر من ذلك سيؤدي في الغالب إلى رفض عناوين بشكل خاطئ. (هل علمت أن عناوين البريد الإلكتروني يمكنها أن تحتوي تعليقات؟!)

ثانياً، حتى لو كان عنوان البريد الإلكتروني يخضع للتعبير النمطي، فهذا لا يثبت وجوده. السبيل الوحيد للتحقق من بريد إلكتروني، أن ترسل رسالةً إليه.

التوصيف والتنسيق

لا تستخدم التعبيرات النمطية لتحليل HTML أو XML في التطبيقات الحقيقية. تحليل HTML/XML:

  1. مستحيلة باستخدام تعبيرات نمطية سهلة
  2. صعبة للغاية حتى من دون تفاصيلها
  3. مشكلة محلولة.

اعثر على مكتبة موجودة يمكنك استخدامها لعمل ذلك.

وكانت هذه 55 دقيقة

بالخلاصة:

  • الحرفيات: a b c d 1 2 3 4 الخ.

  • فئات المحارف: . [abc] [a-z] \d \w \s

    • . يعني "أي محرف"
    • \d يعني "رقم"
    • \w يعني "محرف كلمة", [0-9A-Za-z_]
    • \s يعني "فراغ، أو محرف الجدولة، أو محرف الإدخال، أو محرف السطر الجديد"
    • استثناءات فئات المحارف: [^abc] \D \W \S
  • المضاعفات: {4} {3,16} {1,} ? * +

    • ? يعني "صفر أو واحد"
    • * يعني "صفر أو أكثر"
    • + يعني "واحد أو أكثر"
    • المضاعفات شرهة إلا عند إلحاق ? بها
  • التناوب والمجموعات: (Septem|Octo|Novem|Decem)ber

  • فواصل الكلمات، والفئات، والنصوص: \b ^ $ \A \z

  • للإشارة الرجعية لمجموعة مطابقة: \1 \2 \3 الخ. (تعمل في كلا تعبيري المطابقة والاستبدال)

  • قائمة بالمحارف الخاصة: . \ [ ] { } ? * + | ( ) ^ $

  • قائمة بالمحارف الخاصة عندما تكون في فئة محارف: [ ] \ - ^

  • يمكنك دائماً تهريب محرف باستخدام المائل الخلفي: \

شكراً للقراءة

إن التعبيرات النمطية شائعة ومفيدة. وينبغي على كل من يمضي وقتاً في تحرير النصوص أو كتابة برامج الحاسب أن يعرف كيفية استخدامها.

وحتى هذه الكلمات ما فعلنا سوى أننا حصلنا معرفةً سطحيةً عنها...

تمرين

انطق واقرأ الوثائق المتاحة لمشغل التعبيرات النمطية الذي تختاره. أضمن لك أنك ستجد مزيداً من الميزات والمواضيع عما ذكرناه هنا.

اصطلاحات الترجمة

العربيةEnglish
الأقواس المربعة [ ]Square Brackets
الأقواس المزخرفة { }Braces
الأنبوب |Pipe
إشارة مرجعيةBack-Reference
التعبيرات النمطية المولَّدةDynamic regular expressions
شرهGreedy
عيّنةPattern
لغة برمجة محددة النطاقDomain-Specific Programming Language
فئة محارفCharacter Class
المائل /Slash
المائل الخلفي \Backslash
مجموعةGroup
مطابقة مجموعةCapture Group