چه برنامهنویس باشید، چه متخصص سئو یا تحلیلگر داده، همه ما یک چالش مشترک داریم: سر و کله زدن با حجم زیادی از متن. پیدا کردن یک الگوی خاص، تمیز کردن دادههای کثیف یا اعتبارسنجی ورودیهای کاربر، کارهایی هستند که میتوانند ساعتها از وقت ما را بگیرند. اینجاست که «رجکس» (Regex) یا عبارات باقاعده، به عنوان یک ابزار فوقالعاده قدرتمند وارد میشود.
یادگیری رجکس در ابتدا شاید کمی ترسناک به نظر برسد (با آن سینتکس عجیب!)، اما قول میدهم یکی از کاربردیترین مهارتهایی است که یاد میگیرید. در این راهنمای جامع، ما قدم به قدم از صفر شروع میکنیم. ابتدا به سراغ مبانی و مفاهیم پایه رجکس میرویم و بعد با هم الگوهای پیچیدهتر و حرفهایتری را میسازیم. آمادهاید که استاد کار با متن شوید؟
جدول کاربردی: راهنمای تقلب (Cheatsheet) رجکس
| نماد (Symbol) | معنی و مفهوم | مثال کاربردی |
|---|---|---|
| . | هر کاراکتر تکی (به جز خط جدید) | h.t با hat و hot مطابقت دارد. |
| d | هر کاراکتر عدد (Digit) | معادل [0-9] |
| w | هر کاراکتر کلمه (Word) | معادل [a-zA-Z0-9_] |
| s | هر کاراکتر فاصله خالی (Whitespace) | شامل اسپیس، تب و خط جدید |
| [abc] | کلاس کاراکتری: فقط a یا b یا c | [aeiou]: تمام حروف صدادار |
| [^abc] | کلاس نفی: هر کاراکتری به جز a, b, c | [^0-9] : هر کاراکتر غیر عددی |
| * | صفر یا بیشتر از کاراکتر قبلی | a* با “”، a، aa مطابقت دارد. |
| + | یک یا بیشتر از کاراکتر قبلی | a+ با a، aa مطابقت دارد (اما با “” نه). |
| ? | صفر یا یکی (اختیاری کردن) | colou?r با color و colour مطابقت دارد. |
| {n,m} | تعداد دقیق: بین n تا m بار | d{2,4}: بین ۲ تا ۴ رقم عدد. |
| ^ | لنگر شروع: ابتدای دقیق رشته | ^Hello: فقط اگر رشته با Hello شروع شود. |
| $ | لنگر پایان: انتهای دقیق رشته | world$: فقط اگر رشته با world تمام شود. |
| ( ) | گروهبندی: چند کاراکتر را یک واحد میکند | (ha)+ با ha و haha مطابقت دارد. |
| | | “یا” منطقی (Alternation) | cat|dog با cat یا dog مطابقت دارد. |
تعریف عبارات باقاعده (Regular Expressions) به زبان ساده
عبارات باقاعده (که اغلب به آن Regex یا RegExp هم میگویند) در واقع یک «زبان الگو» (Pattern Language) هستند.
اگر بخواهم خیلی ساده بگویم، رجکس یکجور ابزار «پیدا کردن و جایگزینی» (Find & Replace) فوق پیشرفته است.
فرض کن در یک فایل متنی دنبال شماره تلفن میگردی. با (Ctrl+F) معمولی نمیتوانی «همه شماره تلفنها» را پیدا کنی، چون دقیقاً نمیدانی چه شمارهای آنجاست. اما با رجکس، میتوانی به ویرایشگر متن بگویی: «دنبال الگویی بگرد که با ۰۹ شروع میشود و ۹ رقم دیگر هم بعد از آن میآید.»
رجکس دقیقاً همین کار را میکند: به جای جستجوی یک کلمه ثابت، الگوی آن کلمه را جستجو میکند.
رجکس (Regex) دقیقاً چیست؟ (مفهوم و تعریف)
از نظر فنی، رجکس یک توالی از کاراکترها است که یک الگوی جستجو (Search Pattern) را تعریف میکند.
بگذار این تعریف را کمی باز کنم:
زبان برنامهنویسی نیست: رجکس خودش یک زبان برنامهنویسی کامل مثل پایتون یا جاوا اسکریپت نیست. بلکه یک «زبان کوچک» یا «زبان ویژه دامنه» (DSL) است که درون آن زبانهای بزرگتر استفاده میشود.
موتور جستجوی الگو: تقریباً تمام زبانهای برنامهنویسی مدرن، یک موتور رجکس داخلی دارند. تو الگوی خودت (مثلاً ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$ برای ایمیل) را به این موتور میدهی و موتور رجکس متن ورودی تو را بررسی میکند تا ببیند آیا بخشی از متن با آن الگو مطابقت دارد یا نه.
کاربردها: دو کاربرد اصلی دارد:
اعتبارسنجی (Validation): آیا ورودی کاربر (مثل ایمیل، پسورد، کد ملی) با الگوی مورد نظر ما مطابقت دارد؟
استخراج (Extraction): تمام بخشهایی از متن که با الگوی ما (مثل همه لینکها یا همه قیمتها) مطابقت دارند را بیرون بکش و به من بده.
چرا Regex یک مهارت حیاتی برای برنامهنویسان و تحلیلگران داده است؟
اگر بخواهم صادقانه بگویم، یادگیری رجکس در ابتدا کمی ترسناک به نظر میرسد (الگوهای آن شبیه کاراکترهای نامفهوم هستند)، اما تسلط بر آن یک مزیت رقابتی فوقالعاده است.
از دید من، رجکس در این حوزهها یک مهارت حیاتی و روزمره است:
۱. اعتبارسنجی فرمها (برای توسعهدهندگان وب):
تجربه عملی: تو نمیتوانی به کاربر اعتماد کنی که ایمیل یا شماره موبایل را درست وارد کند. قبل از ارسال اطلاعات به سرور، باید مطمئن شوی که فرمت ورودی کاربر حداقل شبیه یک ایمیل یا شماره تلفن معتبر است. رجکس بهترین و سریعترین ابزار برای این کار است.
۲. پاکسازی دادهها (برای تحلیلگران داده):
تجربه عملی: وقتی دادهها را از منابع مختلف جمعآوری میکنی (مثلاً خروجی اکسل، فایلهای CSV یا لاگهای سرور)، دادهها همیشه «کثیف» هستند. مثلاً ممکن است واحد پول ($ یا تومان) به اعداد چسبیده باشد، یا فاصلههای اضافه و کاراکترهای عجیب در متن وجود داشته باشد. رجکس به تو اجازه میدهد در کسری از ثانیه میلیونها ردیف داده را تمیز کنی.
۳. وب اسکرپینگ (Web Scraping):
وقتی میخواهی اطلاعات خاصی (مثل قیمت محصولات یا تیتر مقالات) را از یک صفحه وب استخراج کنی، رجکس به تو کمک میکند تا دقیقاً همان الگوی HTML مورد نظرت را پیدا کنی و محتوای درون آن را بیرون بکشی.
۴. جستجو و جایگزینی پیشرفته در کد (Refactoring):
برای برنامهنویسان، گاهی نیاز است یک الگوی تکراری در کد را تغییر دهند. مثلاً تمام متغیرهایی که با _temp_ شروع میشوند را پیدا کنند و نامشان را عوض کنند. ابزارهای جستجوی عادی در ویرایشگرهای کد (مثل VS Code) همگی از رجکس پشتیبانی میکنند.
به طور خلاصه، رجکس ابزاری است که برای هر کاری که با پردازش متن سروکار دارد، سرعت و دقت تو را به شدت بالا میبرد و به تو کمک میکند به هدفت برسی.
تاریخچه مختصر: رجکس از کجا آمد؟
شاید جالب باشد که بدانی ایده اولیه رجکس اصلاً برای کامپیوتر نبود، بلکه از علوم اعصاب و ریاضیات آمد!
دهه ۱۹۵۰ (تئوری): یک ریاضیدان و منطقدان به نام استیون کلینی (Stephen Cole Kleene)، مفهوم «مجموعههای منظم» (Regular Sets) را به عنوان روشی برای توصیف الگوها در ریاضیات و مدلسازی نورونهای عصبی معرفی کرد.
دهه ۱۹۶۰ (ورود به کامپیوتر): کن تامپسون (Ken Thompson)، که یکی از خالقین اصلی سیستمعامل یونیکس (Unix) و زبان برنامهنویسی B (جد C) است، این مفاهیم نظری را برداشت.
ابزار Grep: تامپسون اولین استفاده عملی از رجکس را در یک ویرایشگر متن به نام ed پیادهسازی کرد. بعدها، او ابزار معروف grep را در یونیکس ساخت (که مخفف “g/re/p” به معنی “globally search for a regular expression and print” است).
گسترش: از آنجا، رجکس به ابزار استاندارد پردازش متن در یونیکس تبدیل شد و بعداً تقریباً به تمام زبانهای برنامهنویسی مدرن راه پیدا کرد.
بنابراین، رجکس از یک تئوری ریاضی محض، به یکی از کاربردیترین ابزارهای روزمره برنامهنویسان تبدیل شد.
کاربردهای عملی و روزمره رجکس (چرا به آن نیاز داریم؟)
اگر بخواهم روراست بگویم، رجکس (Regex) یکی از آن ابزارهایی است که شاید در ابتدا یادگیریاش کمی سخت به نظر برسد، اما وقتی آن را یاد میگیری، مدام از خودت میپرسی: «چطور تا الان بدون این زندگی میکردم؟»
رجکس فقط یک تئوری جالب برنامهنویسی نیست؛ یک آچار فرانسهی واقعی برای کار با متن است. در دنیای سئو، برنامهنویسی و تحلیل داده، ما هر روز با حجم عظیمی از دادههای متنی سروکار داریم. رجکس به ما کمک میکند تا به جای صرف ساعتها زمان برای کارهای دستی، همان کارها را در چند ثانیه و با دقت صددرصد انجام دهیم.
بیایید چند سناریوی عملی و روزمره را با هم مرور کنیم که رجکس در آنها مثل یک قهرمان به ما کمک میکند.
اعتبارسنجی دادهها (Validation): از فرمت ایمیل تا قدرت رمز عبور
این یکی از پایهایترین و حیاتیترین کاربردهای رجکس، مخصوصاً برای ما توسعهدهندگان وب و متخصصان سئو است.
اعتبارسنجی (Validation) یعنی مطمئن شویم که ورودی کاربر با فرمتی که ما انتظار داریم مطابقت دارد.
مثال فرمت ایمیل: فرض کنید در یک فرم تماس، از کاربر ایمیل میخواهید. چطور مطمئن شویم چیزی که وارد کرده حداقل شبیه یک ایمیل است؟
راه حل ضعیف: چک کنیم که آیا ورودی شامل کاراکتر @ هست یا نه. (ضعیف است، چون abc@ هم قبول میشود!)
راه حل با Regex: ما یک الگو تعریف میکنیم که میگوید: «باید شامل (یک سری کاراکتر مجاز) + (علامت @) + (یک سری کاراکتر دیگر) + (یک نقطه) + (دو یا چند حرف برای دامنه) باشد». این الگو جلوی ورود دادههای بیمعنی و اشتباه به سیستم ما را میگیرد.
مثال قدرت رمز عبور: وقتی کاربر در حال ثبتنام است، میخواهیم مطمئن شویم رمز عبور او به اندازه کافی قوی است.
راه حل با Regex: با یک الگوی رجکس میتوانیم همزمان چند شرط را بررسی کنیم:
آیا حداقل ۸ کاراکتر دارد؟
آیا حداقل یک حرف بزرگ (A-Z) دارد؟
آیا حداقل یک عدد (0-9) دارد؟
آیا حداقل یک کاراکتر خاص (مثل !@#$%) دارد؟
تجربه من: استفاده از رجکس برای اعتبارسنجی، هم در سمت کاربر (Front-end) و هم در سمت سرور (Back-end)، جلوی بخش بزرگی از دادههای «کثیف» و خطاهای امنیتی را میگیرد و به شدت در زمان ما صرفهجویی میکند.
جستجو و جایگزینی (Find & Replace) هوشمند در متن و کد
همه ما با Ctrl+F (پیدا کردن) و Ctrl+H (جایگزینی) آشنا هستیم. اما این ابزارها فقط میتوانند یک کلمه یا عبارت ثابت را پیدا کنند. رجکس این قابلیت را به سطح «هوشمند» ارتقا میدهد.
مثال در متن: فرض کنید یک مقاله طولانی دارید و میخواهید تمام شماره تلفنها را پیدا کنید تا آنها را حذف یا ویرایش کنید. مشکل اینجاست که شمارهها به فرمتهای مختلفی نوشته شدهاند:
09121234567
0912-123-4567
+98 (912) 123 45 67
0912 123 4567
با Ctrl+F معمولی این کار غیرممکن است. اما با رجکس، ما الگویی مینویسیم که میگوید: «دنبال رشتهای بگرد که با ۰۹ یا +۹۸ شروع میشود و در ادامه، ۹ یا ۱۰ رقم دیگر دارد که ممکن است بینشان فاصله، خط تیره یا پرانتز هم باشد.»
مثال در کد (Refactoring): این کاربرد محبوب من به عنوان برنامهنویس است. فرض کنید در صدها فایل کد، متغیرهایی با نامهای user_id، post_id، comment_id دارید و حالا طبق استاندارد جدید تیم، باید همه آنها را به userId، postId و commentId (مدل camelCase) تغییر دهید.
با رجکس میتوانیم بگوییم: «هر عبارتی را که با _id تمام میشود پیدا کن، آن _ را حذف کن و حرف i را به I تبدیل کن.» این کاری است که در عرض چند ثانیه در ویرایشگرهایی مثل VS Code انجام میشود.
استخراج داده (Data Extraction/Scraping): بیرون کشیدن اطلاعات خاص از متون طولانی
این بخش برای متخصصان سئو و تحلیلگران داده فوقالعاده کاربردی است. استخراج داده (Extraction) یعنی از دل یک متن بزرگ و بههمریخته، فقط اطلاعاتی که لازم داریم را بیرون بکشیم.
مثال وب اسکرپینگ (Web Scraping): فرض کنید میخواهیم قیمت تمام محصولات را از یک صفحه دستهبندی فروشگاه رقیب استخراج کنیم. ما سورس HTML آن صفحه را داریم (که یک فایل متنی بسیار طولانی است).
قیمتها معمولاً یک الگوی مشخص دارند، مثلاً: <span>1,250,000 تومان</span>
ما با رجکس الگویی مینویسیم که میگوید: «دنبال عبارتی بگرد که بین <span> و تومان</span> قرار دارد.»
خروجی رجکس لیستی تمیز از تمام قیمتها خواهد بود: [1,250,000, 450,000, …]
مثال دیگر: بیرون کشیدن تمام آدرسهای ایمیل یا تمام لینکهای خارجی (External Links) از یک فایل متنی یا مقاله، کاری است که رجکس در یک چشم به هم زدن انجام میدهد.
تجزیه (Parsing) لاگها و فایلهای حجیم
اگر تا به حال با لاگهای سرور (Server Logs) کار کرده باشید، میدانید که با فایلهای متنی چندین گیگابایتی سروکار داریم که خواندن دستی آنها غیرممکن است. تجزیه (Parsing) به معنی تحلیل ساختار این فایلهاست.
هر خط در یک فایل لاگ (مثل لاگ وبسرور Nginx یا Apache) یک ساختار مشخص دارد: [IP Address] – [Timestamp] – “GET /page-url/ HTTP/1.1” [Status Code] [User Agent]
تجربه عملی: فرض کنید سایت دچار مشکل شده و میخواهیم تمام خطاهایی که منجر به «ارور ۵۰۰» شدهاند را پیدا کنیم.
من نمیتوانم فایل ۲ گیگابایتی لاگ را خط به خط بخوانم.
با یک دستور رجکس ساده (مثلاً در ترمینال با ابزار grep) میگویم: «فقط خطوطی را به من نشان بده که الگوی HTTP/… ” 500 را دارند.»
مثال پیشرفتهتر: «تمام آدرسهای IP که در ۲۴ ساعت گذشته بیش از ۱۰۰ بار به سایت دسترسی داشتهاند و کد وضعیت ۴۰۴ (صفحه یافت نشد) دریافت کردهاند را استخراج کن.»
این کار برای شناسایی حملات یا پیدا کردن لینکهای شکسته حیاتی است و بدون رجکس، تقریباً امکانپذیر نیست.
دیکشنری Regex: اجزای سازنده و سینتکس پایه (مبانی)
اگر رجکس را یک زبان در نظر بگیریم، این بخش مثل یاد گرفتن الفبا و کلمات پایه است. برای ساختن الگوهای پیچیده، اول باید این اجزای سازنده اساسی را بشناسیم. اینها بلوکهای ساختمانی رجکس هستند که در کنار هم قرار میگیرند تا الگوهای قدرتمند را بسازند.
۱. کاراکترهای تحتاللفظی (Literals) در مقابل متاکاراکترها (Metacharacters)
در رجکس، ما دو نوع کاراکتر داریم:
۱. کاراکترهای تحتاللفظی (Literals): اینها کاراکترهای معمولی هستند که معنای خاصی ندارند و دقیقاً خودشان را نمایندگی میکنند. وقتی در الگو مینویسید cat، موتور رجکس دقیقاً دنبال خود کلمه “cat” میگردد. حروف a تا z، A تا Z و اعداد 0 تا 9 (وقتی تنها استفاده شوند) معمولاً تحتاللفظی هستند.
۲. متاکاراکترها (Metacharacters): اینها کاراکترهای ستاره و قلب تپنده رجکس هستند. متاکاراکترها به جای خودشان، یک دستور یا یک مفهوم را نمایندگی میکنند.
مثال: کاراکتر نقطه (.) یک متاکاراکتر است. به معنای «هر کاراکتری» (به جز خط جدید در برخی موتورها) است.
بنابراین، الگوی c.t هم با cat، هم با cot، هم با c_t و هم با c5t مطابقت پیدا میکند.
مهمترین متاکاراکترها که در ادامه توضیح میدهیم عبارتند از: . , [ , ] , ( , ) , { , } , * , + , ? , ^ , $ ,
۲. کلاسهای کاراکتری (Character Classes): [ ], d, w, s
کلاس کاراکتری راهی برای گفتن «هر کدام از این کاراکترها» است.
[ ] (براکتها): این دستور اصلی برای ساختن یک کلاس سفارشی است. هر چیزی که داخل براکت بنویسید، به معنای «یکی از اینها» است.
[abc] یعنی: یا a یا b یا c.
[a-z] یعنی: هر حرف کوچک انگلیسی (این یک range یا بازه است).
[a-zA-Z0-9] یعنی: هر حرف کوچک، یا هر حرف بزرگ، یا هر عدد.
[^abc] (با ^ در داخل براکت): این حالت نفی (Negation) است. یعنی: «هر کاراکتری به جز a و b و c».
میانبرهای (Shorthands) پرکاربرد: رجکس برای کلاسهای خیلی رایج، میانبرهایی با استفاده از (بکاسلش) تعریف کرده که کار ما را راحتتر میکند:
d (Digit): «هر کاراکتر عدد». معادل دقیق [0-9] است.
w (Word Character): «هر کاراکتر کلمه». معادل [a-zA-Z0-9_] است (شامل حروف، اعداد و آندرلاین).
s (Whitespace): «هر کاراکتر فاصله خالی». شامل اسپیس، تب (t)، خط جدید (n) و غیره است.
نکته مهم: نسخههای با حرف بزرگ این میانبرها، دقیقاً متضاد آنها هستند:
D : هر کاراکتری که عدد نیست.
W : هر کاراکتری که «کلمه» نیست (مثل !, @, #, ).
S : هر کاراکتری که فاصله خالی نیست.
۳. لنگرها (Anchors): ^ (شروع خط) و $ (پایان خط)
لنگرها کاراکترهای خاصی هستند که با متن مطابقت پیدا نمیکنند، بلکه با یک موقعیت (Position) در متن مطابقت پیدا میکنند. آنها به موتور رجکس میگویند که الگو کجا باید پیدا شود.
^ (Caret): این لنگر، «شروع رشته» (یا شروع خط) را نشان میدهد.
الگوی ^Hello فقط کلمه “Hello” را پیدا میکند اگر دقیقاً در ابتدای متن باشد.
اگر متن ما “Well, Hello” باشد، این الگو مطابقت پیدا نمیکند.
$ (Dollar Sign): این لنگر، «پایان رشته» (یا پایان خط) را نشان میدهد.
الگوی bye$ فقط کلمه “bye” را پیدا میکند اگر دقیقاً در انتهای متن باشد.
اگر متن ما “bye bye” باشد، این الگو مطابقت پیدا نمیکند.
تجربه عملی: ما از ^ و $ با هم برای اعتبارسنجی کامل یک رشته استفاده میکنیم. مثلاً الگوی ^d{5}$ یعنی: «کل رشته باید فقط شامل ۵ عدد باشد، نه کمتر و نه بیشتر». این الگو با 12345 مطابقت دارد، اما با 123456 یا a12345 مطابقت ندارد.
۴. تعیینکنندههای تعداد (Quantifiers): *, +, ?, {n,m}
تعیینکنندهها متاکاراکترهایی هستند که بعد از یک کاراکتر (یا گروه) میآیند و میگویند: «از این قبلی، چند تا باید باشد؟»
* (Star): «صفر یا بیشتر»
a* یعنی: یا هیچ a نباشد، یا یک a باشد، یا aa، یا aaaaa و… .
مثال: ab*c هم با ac (صفر b)، هم با abc (یک b) و هم با abbbc (چند b) مطابقت دارد.
+ (Plus): «یک یا بیشتر»
a+ یعنی: باید حداقل یک a باشد (a, aa, aaa و…).
مثال: ab+c با abc و abbbc مطابقت دارد، اما با ac (چون b ندارد) مطابقت ندارد.
? (Question Mark): «صفر یا یکی»
این کاراکتر قبلی را اختیاری (Optional) میکند.
مثال: colou?r هم با color (صفر u) و هم با colour (یک u) مطابقت دارد. این برای پیدا کردن کلماتی با دو املای مختلف عالی است.
{ } (Curly Braces): «تعداد دقیق» این ابزار دقیقترین کنترل را به ما میدهد:
{n} : دقیقاً n بار. (مثال: d{10} یعنی دقیقاً ۱۰ رقم.)
{n,} : حداقل n بار. (مثال: d{3,} یعنی ۳ رقم یا بیشتر.)
{n,m} : بین n تا m بار (شامل خود n و m). (مثال: w{5,10} یعنی بین ۵ تا ۱۰ کاراکتر کلمه.)
۵. کاراکتر گریز (Escape Character): و کاربرد آن
کاراکتر (بکاسلش) احتمالاً مهمترین کاراکتر در رجکس است. این کاراکتر دو وظیفه کاملاً برعکس هم دارد:
وظیفه ۱: خنثی کردن متاکاراکترها (Escaping) اگر ما بخواهیم به جای «هر کاراکتری»، دنبال خود کاراکتر نقطه (.) بگردیم چه؟ یا اگر بخواهیم دنبال علامت + یا * بگردیم؟ اینجاست که وارد میشود. به کاراکتر بعد از خودش میگوید: «معنی خاص خودت را فراموش کن و یک کاراکتر تحتاللفظی باش.»
مثال: برای پیدا کردن site.com، الگوی site.com اشتباه است (چون . با هر کاراکتری مثل sitemcom هم مچ میشود).
الگوی درست: site.com . در اینجا . یعنی «خود کاراکتر نقطه».
مثال دیگر: برای پیدا کردن C++، باید بنویسیم C++ .
وظیفه ۲: معنیدار کردن کاراکترهای عادی (Shorthands) برعکس حالت قبل، میتواند به یک کاراکتر عادی، معنی خاص (متاکاراکتری) بدهد.
همانطور که دیدیم، d به تنهایی یعنی حرف “d”.
اما d تبدیل به متاکاراکتر «هر رقمی» میشود.
همینطور برای w (کلمه)، s (فاصله)، b (مرز کلمه) و… .
خلاصه: یا معنی خاص را میگیرد (مثل .) یا معنی خاص را میدهد (مثل d).
مفاهیم سطح متوسط Regex: گروهبندی و منطق
خب، تا اینجا الفبای رجکس را یاد گرفتیم. حالا وقت آن است که یاد بگیریم چطور با این الفبا «کلمه» و «جمله» بسازیم. مفاهیم این بخش به ما اجازه میدهند الگوهای پیچیدهتر، هوشمندتر و انعطافپذیرتری بنویسیم. اینجاست که قدرت واقعی رجکس خودش را نشان میدهد.
گروهبندی (Grouping) با پرانتز ( )
پرانتز ( ) یکی از پرکاربردترین ابزارها در رجکس است. کار اصلی آن این است که چندین کاراکتر را به عنوان یک واحد (Unit) در نظر میگیرد.
یادتان هست که تعیینکنندههای تعداد (Quantifiers) مثل *، + یا ? فقط روی یک کاراکتر قبل از خودشان اعمال میشدند؟
مثلاً ha+ یعنی h و به دنبال آن «یک یا چند a» (مثل ha, haaa, haaaaa).
حالا اگر بخواهیم کل کلمه ha تکرار شود چه؟ اینجاست که از پرانتز استفاده میکنیم:
الگوی (ha)+ یعنی: «گروه (ha) باید یک یا چند بار تکرار شود.»
این الگو با ha، haha، hahaha و… مطابقت دارد، اما با haaaa مطابقت ندارد.
تجربه عملی: این قابلیت برای اعمال کردن Quantifier ها روی یک بخش از الگو حیاتی است. مثلاً برای پیدا کردن آدرسهای وب که ممکن است http یا https باشند، میتوانیم از https? استفاده کنیم. اما اگر بخواهیم کل www. اختیاری باشد، باید آن را گروه بندی کنیم: (www.)?
کپچر کردن گروهها (Capturing Groups) و ارجاع به عقب (Backreferences)
پرانتز ( ) یک کار دوم و بسیار مهم هم انجام میدهد: به طور خودکار هر چیزی را که درون خود مطابقت دهد، “کپچر” (Capture) یا ذخیره میکند.
موتور رجکس این بخشهای کپچر شده را در حافظه موقت نگه میدارد. به این گروههای ذخیره شده (از چپ به راست) شمارههایی مثل ۱، ۲، ۳ و… اختصاص داده میشود.
کاربرد این چیست؟ ارجاع به عقب (Backreference)
ما میتوانیم درون خود الگو، با استفاده از 1، 2 و… به متنی که قبلاً توسط گروه شماره ۱ یا ۲ کپچر شده، ارجاع دهیم.
مثال کلاسیک: پیدا کردن کلمات تکراری
فرض کنید میخواهیم در متن “hello hello world” کلمه تکراری “hello hello” را پیدا کنیم.
الگوی ما این میشود: (w+)s+1
تحلیل الگو:
(w+): هر کلمهای را پیدا کن (یک یا چند کاراکتر کلمه) و آن را در گروه ۱ ذخیره کن (مثلاً “hello”).
s+: یک یا چند فاصله خالی پیدا کن.
1: دقیقاً همان متنی را پیدا کن که در گروه ۱ کپچر شده بود (یعنی خود “hello”).
این الگو با hello hello مطابقت دارد، اما با hello world مطابقت ندارد. به این کار «ارجاع به عقب» میگویند.
نکته پیشرفته (Non-Capturing Groups): گاهی اوقات ما فقط به پرانتز برای «گروهبندی» نیاز داریم و نمیخواهیم نتیجه را کپچر کنیم (چون حافظه مصرف میکند یا شمارهگذاری گروهها را به هم میریزد). در این حالت از (?:…) استفاده میکنیم.
(?:ha)+ : گروه (ha) را یک یا چند بار تکرار کن، اما نتیجه را در حافظه کپچر نکن.
عملگر “یا” (Alternation |): انتخاب بین چند الگو
کاراکتر پایپ | در رجکس دقیقاً معادل منطقی “یا” (OR) عمل میکند. این به ما اجازه میدهد بین دو یا چند الگوی مختلف، حق انتخاب قائل شویم.
مثال ساده: الگوی cat|dog
این الگو هم با “cat” و هم با “dog” مطابقت پیدا میکند. موتور رجکس هر کدام را که زودتر در متن پیدا کند، برمیگرداند.
ترکیب با گروهبندی: قدرت واقعی | زمانی است که با پرانتز ( ) ترکیب میشود تا “یا” را فقط به بخشی از الگو اعمال کند.
مثال: فرض کنید میخواهیم فایلهایی با پسوند jpg یا jpeg را پیدا کنیم.
الگوی (jpg|jpeg) این کار را انجام میدهد.
مثال پیچیدهتر: میخواهیم بگوییم “I love cats” یا “I love dogs”.
الگوی I love (cats|dogs)
در اینجا، بخش I love ثابت است و “یا” فقط بین cats و dogs اعمال میشود.
تفاوت Quantifierهای حریص (Greedy) و تنبل (Lazy)
این یکی از مهمترین و کلیدیترین مفاهیمی است که درک آن جلوی بسیاری از اشتباهات رایج در رجکس را میگیرد.
به طور پیشفرض، تمام تعیینکنندههای تعداد (*, +, {n,m}) در رجکس «حریص» (Greedy) هستند.
۱. حریص (Greedy): حریص یعنی موتور رجکس تلاش میکند تا بیشترین (طولانیترین) متن ممکن را که با الگو مطابقت دارد، پیدا کند.
مثال فاجعهبار: فرض کنید میخواهیم تگهای HTML را از متن <b>Hello</b> <i>World</i> استخراج کنیم.
اگر از الگوی حریص <.+> استفاده کنیم:
.+ یعنی «یک یا چند کاراکتر (هر چیزی)».
موتور رجکس از اولین < (یعنی < در <b>) شروع میکند و تا آخرین > (یعنی > در </i>) پیش میرود.
نتیجه: <b>Hello</b> <i>World</i> (کل رشته به عنوان یک تگ شناسایی میشود!)
دلیلش این است که .+ حریص است و تا جایی که میتواند (تا آخرین >) متن را میبلعد.
۲. تنبل (Lazy) یا (Non-Greedy): برای حل این مشکل، ما تعیینکننده را «تنبل» میکنیم. کافی است یک علامت سوال ? بلافاصله بعد از Quantifier اضافه کنیم (*?, +?, ??, {n,m}?).
تنبل یعنی موتور رجکس تلاش میکند تا کمترین (کوتاهترین) متن ممکن را که با الگو مطابقت دارد، پیدا کند.
مثال اصلاح شده: حالا از الگوی تنبل <.+?> روی همان متن <b>Hello</b> <i>World</i> استفاده میکنیم.
< : اولین < را پیدا میکند (در <b>).
.+? : حالا موتور به جای بلعیدن همهچیز، دنبال اولین > میگردد.
نتیجه اول: <b>
موتور به جستجو ادامه میدهد، < بعدی را در <i> پیدا میکند.
.+? دوباره دنبال اولین > بعدی میگردد.
نتیجه دوم: <i>
(و به همین ترتیب </b> و </i> هم پیدا میشوند).
تجربه من: به عنوان یک قانون کلی، هر زمان که با دادههای ساختاریافته مثل HTML, XML, یا JSON کار میکنید و از Quantifier هایی مثل .* یا .+ بین دو جداکننده (مثل < و >) استفاده میکنید، همیشه یادتان باشد که آنها را با ? تنبل کنید (.*? یا .+?) تا از نتایج غیرمنتظره جلوگیری کنید.
مثالهای واقعی: آموزش Regex با پروژههای کوچک
حالا بیایید تئوری را به عمل تبدیل کنیم. بهترین راه برای اینکه رجکس واقعاً در ذهنتان جا بیفتد، استفاده از آن در سناریوهای واقعی است. اینها مثالهایی هستند که من در پروژههای روزمره بارها با آنها سروکار داشتهام.
مثال ۱ (تجربه عملی): نوشتن رجکس برای اعتبارسNGی ایمیل استاندارد
سناریو: میخواهیم در فرم ثبتنام مطمئن شویم که کاربر یک آدرس ایمیل با فرمت قابل قبول وارد کرده است.
الگوی Regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$
تحلیل و شکستن الگو:
^
لنگر (Anchor): جستجو باید دقیقاً از ابتدای رشته شروع شود.
[a-zA-Z0-9._%+-]+
کلاس کاراکتری: مجموعهای از کاراکترهای مجاز برای «نام کاربری» (بخش قبل از @).
a-z: تمام حروف کوچک.
A-Z: تمام حروف بزرگ.
0-9: تمام اعداد.
._%+-: کاراکترهای خاص نقطه، آندرلاین، درصد، مثبت و خط تیره.
+ (Quantifier): از این کلاس کاراکتری، باید یک یا چند عدد وجود داشته باشد.
@
کاراکتر تحتاللفظی (Literal): دقیقاً باید کاراکتر @ وجود داشته باشد.
[a-zA-Z0-9.-]+
کلاس کاراکتری: مجموعهای از کاراکترهای مجاز برای «دامنه» (بخش بعد از @).
معمولاً دامنهها سادهتر هستند و فقط شامل حروف، اعداد، نقطه و خط تیره میشوند.
+ (Quantifier): باید یک یا چند عدد از این کاراکترها وجود داشته باشد.
.
کاراکتر گریز (Escape): ما دنبال خود کاراکتر نقطه (.) هستیم، نه متاکاراکتر نقطه (که یعنی «هر چیزی»).
[a-zA-Z]{2,}
کلاس کاراکتری: بخش TLD (دامنه سطح بالا) مثل .com, .ir, .org.
[a-zA-Z]: فقط باید از حروف تشکیل شده باشد.
{2,} (Quantifier): باید حداقل ۲ حرف داشته باشد (مثل .ir, .com, .info).
$
لنگر (Anchor): جستجو باید دقیقاً در انتهای رشته تمام شود.
نتیجه: این الگو کل رشته را بررسی میکند که از ابتدا تا انتها شبیه یک ایمیل استاندارد باشد.
مثال ۲ (تجربه عملی): استخراج تمام شماره تلفنهای ایران از یک متن
سناریو: یک متن طولانی داریم و میخواهیم تمام شماره موبایلها و تلفنهای ثابت (با پیششماره) را، با فرمتهای نوشتاری مختلف، استخراج کنیم.
الگوی Regex: (?:+98|0)?(9d{9}|[1-8]d{9})
تحلیل و شکستن الگو:
(?:+98|0)?
( … )? (گروه اختیاری): کل این بخش اختیاری است (صفر یا یکی).
?: (گروهNon-Capturing): ما این بخش را فقط برای گروهبندی میخواهیم و نیازی به کپچر کردن آن نداریم.
+98|0 (Alternation): این گروه یا با +98 (توجه به + برای گریز از متاکاراکتر) یا 0 مطابقت دارد.
معنی: این بخش پیششمارههای +98 یا 0 را پیدا میکند، اما اگر هم نبودند، مشکلی نیست.
(9d{9}|[1-8]d{9})
( … ) (گروه Capturing): این گروه اصلی شماره است که ما میخواهیم استخراج کنیم.
… | … (Alternation): این گروه به دو بخش تقسیم شده است:
بخش اول (موبایل): 9d{9}
9: باید با عدد 9 شروع شود (برای شمارههای موبایل مثل ۹۱۲، ۹۳۵، ۹۰۱ و…).
d{9}: دقیقاً 9 رقم عدد بعد از آن بیاید. (مجموعاً ۱۰ رقم موبایل).
بخش دوم (تلفن ثابت): [1-8]d{9}
[1-8]: باید با عددی بین ۱ تا ۸ شروع شود (پیششمارههای استانی مثل ۲۱، ۳۱، ۵۱ و… که با ۰ شروع نمیشوند چون ۰ در بخش اول مدیریت شد).
d{9}: دقیقاً 9 رقم عدد بعد از آن بیاید. (مثلاً ۲۱ برای تهران + ۸ رقم شماره).
مثال ۳ (تجربه عملی): بررسی یک رمز عبور قوی (حروف، اعداد، کاراکتر خاص)
سناریو: میخواهیم مطمئن شویم رمز عبور کاربر به اندازه کافی پیچیده است: حداقل ۸ کاراکتر، شامل حداقل یک حرف کوچک، یک حرف بزرگ، یک عدد و یک کاراکتر خاص.
الگوی Regex (با استفاده از Lookaheads): ^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]{8,}$
مفهوم جدید: Positive Lookahead (?=…) قبل از شکستن الGO، باید با “نگاه به جلو” آشنا شویم. (?=…) یک دستور خاص است. این دستور متن را مصرف نمیکند (یعنی کرسر جلو نمیرود)، فقط بررسی میکند که آیا الگوی داخل آن از همین نقطه به بعد وجود دارد یا نه. این به ما اجازه میدهد چندین شرط را همزمان روی کل رشته بررسی کنیم.
تحلیل و شکستن الگو:
^ : شروع رشته.
(?=.*[a-z]) : شرط اول: «نگاه کن به جلو، آیا (در ادامه) هر کاراکتری (.) به تعداد صفر یا بیشتر (*) و سپس یک حرف کوچک ([a-z]) وجود دارد؟»
(?=.*[A-Z]) : شرط دوم: «و همچنین، نگاه کن به جلو، آیا یک حرف بزرگ وجود دارد؟»
(?=.*d) : شرط سوم: «و همچنین، نگاه کن به جلو، آیا یک عدد وجود دارد؟»
(?=.*[@$!%*?&]) : شرط چهارم: «و همچنین، نگاه کن به جلو، آیا یکی از این کاراکترهای خاص وجود دارد؟»
بخش اصلی الگو: تا اینجا، ما فقط ۴ شرط را چک کردیم و کرسر ما هنوز اول رشته است. حالا باید خود رشته را مطابقت دهیم:
[A-Za-zd@$!%*?&]{8,}
کلاس کاراکتری: رشته فقط میتواند شامل این کاراکترهای مجاز باشد.
{8,} (Quantifier): طول کل رشته باید حداقل ۸ کاراکتر باشد.
$ : انتهای رشته.
نتیجه: این الگو فقط زمانی مطابقت دارد که هر چهار شرط Lookahead برقرار باشند و طول رشته حداقل ۸ کاراکتر باشد.
مثال ۴ (تخصصی): پیدا کردن تمام لینکها (URL) در یک صفحه HTML
سناریو: میخواهیم تمام URL های موجود در تگهای <a> (یعنی داخل href=”…”) را از یک فایل HTML استخراج کنیم.
الگوی Regex: <as+[^>]*hrefs*=s*[“‘](http[^”‘]+)[“‘]
تحلیل و شکستن الGO:
<a : شروع تگ <a>.
s+ : حداقل یک فاصله خالی (بین a و صفات دیگر مثل class یا href).
[^>]*
[^>]: کلاس نفی: هر کاراکتری به جز >.
*: صفر یا بیشتر.
معنی: این بخش تمام صفات دیگر (مثل, target=”…”) را «رد میکند» تا به href برسد، بدون اینکه تصادفاً از تگ خارج شود.
hrefs*=s*
href: خود کلمه href.
s*=s*: علامت مساوی، که ممکن است قبل یا بعدش فاصله باشد یا نباشد (* یعنی صفر یا بیشتر).
[“‘]
کلاس کاراکتری: یا یک دابل کوتیشن (“) یا یک سینگل کوتیشن (‘). (چون href میتواند هر دو حالت باشد).
(http[^”‘]+)
( … ) (گروه Capturing): این همان چیزی است که میخواهیم استخراج کنیم (گروه شماره ۱).
http: ما فقط لینکهای مطلق را میخواهیم که با http (یا https که در http گنجانده شده) شروع شوند.
[^”‘]+: هر کاراکتری به جز کوتیشن پایانی (دابل یا سینگل).
+: یک یا بیشتر از این کاراکترها.
اهمیت Lazy Quantifier (جایگزین):
تجربه من: الگوی بالا کمی سادهسازی شده. یک راه بهتر و امنتر استفاده از (http.*?) است:
hrefs*=s*[“‘](http.*?)[“‘]
در اینجا (.*?) یعنی: «هر کاراکتری (.) به تعداد صفر یا بیشتر (*)، اما به صورت تنبل (Lazy) (?)».
این الگو تا اولین کوتیشن بعدی (” یا ‘) که پیدا کند، متوقف میشود و حریصانه کل متن را نمیبلعد.
[“‘]
کوتیشن پایانی که باید با کوتیشن آغازی مطابقت داشته باشد (این الگو سادهشده است و این تطابق را دقیق چک نمیکند، اما در عمل خوب کار میکند).
نتیجه: این الگو تگهای <a> را پیدا کرده و محتوای href آنها (به شرطی که با http شروع شود) را در گروه ۱ کپچر میکند.
Regex در زبانهای برنامهنویسی و ابزارهای محبوب (پیادهسازی)
خب، حالا که با سینتکس و مفاهیم رجکس آشنا شدیم، بیایید ببینیم این ابزار قدرتمند چطور در دنیای واقعی و در زبانهای برنامهنویسی مختلف پیادهسازی میشود.
خبر خوب این است که مفاهیم و سینتکس اصلی رجکس (مثل d, [a-z], *, +, ?, ( ), |) تقریباً در همهجا یکسان هستند. چیزی که تغییر میکند، نحوه فراخوانی توابع و متدهایی است که این الگوها را اجرا میکنند.
استفاده از رجکس در پایتون (ماژول re)
در پایتون، تمام عملیات مربوط به رجکس از طریق ماژول داخلی re انجام میشود. کافی است آن را import کنید.
مهمترین توابع ماژول re:
re.search(pattern, string):
دنبال اولین مطابقت الگو (Pattern) در هر جای رشته (String) میگردد. اگر پیدا کند، یک «آبجکت مچ» (Match Object) برمیگرداند وگرنه None.
re.match(pattern, string):
فقط بررسی میکند که آیا الگو با ابتدای رشته مطابقت دارد یا نه. (کاربردش کمتر از search است).
re.findall(pattern, string):
این تابع فوقالعاده کاربردی است. تمام مطابقتهای بدون همپوشانی را پیدا کرده و به صورت یک لیست از رشتهها برمیگرداند.
re.sub(pattern, replacement, string):
همان «Find & Replace» است. تمام بخشهایی که با الگو مطابقت دارند را با رشته replacement جایگزین میکند.
تجربه عملی (Best Practice): اگر قرار است از یک الگوی رجکس بارها و بارها استفاده کنید (مثلاً داخل یک حلقه)، بهتر است ابتدا آن را با re.compile(pattern) کامپایل کنید. این کار سرعت اجرای کد را به شدت بالا میبرد.
مثال کد پایتون:
Python
import re
text = “آدرس ایمیل من test@example.com و info@gmail.com است.”
pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}’
# پیدا کردن تمام ایمیلها
emails = re.findall(pattern, text)
print(emails) # خروجی: [‘test@example.com’, ‘info@gmail.com’]
# جایگزینی (مثلاً برای مخفی کردن)
hidden_text = re.sub(pattern, “[EMAIL-HIDDEN]”, text)
print(hidden_text) # خروجی: “آدرس ایمیل من [EMAIL-HIDDEN] و [EMAIL-HIDDEN] است.”
نکته: استفاده از r قبل از رشته الگو (مثل r’…’) در پایتون یک عادت بسیار خوب است. این به پایتون میگوید که رشته را به صورت «خام» (Raw String) در نظر بگیرد و کاراکترهایی مثل را به عنوان دستورات خود پایتون پردازش نکند.
استفاده از رجکس در جاوا اسکریپت (شیء RegExp)
در جاوا اسکریپت (که در مرورگر و Node.js استفاده میشود)، رجکس بخشی از هسته زبان است. ما دو راه اصلی برای ساختن یک الگوی رجکس داریم:
۱. لیترال (Literal Syntax): (این روش ۹۹٪ مواقع ترجیح داده میشود) الگو مستقیماً بین دو اسلش / نوشته میشود: const regex = /pattern/flags;
Flags (پرچمها): پرچمها رفتار رجکس را تغییر میدهند. مهمترینها:
g (Global): جستجو را بعد از اولین مطابقت متوقف نکن و تمام مطابقتها را پیدا کن.
i (Case-Insensitive): به بزرگی و کوچکی حروف حساس نباش.
مثال: const regex = /hello/gi;
۲. سازنده (Constructor): const regex = new RegExp(‘pattern’, ‘flags’);
این روش زمانی مفید است که الگوی شما ثابت نیست و میخواهید آن را به صورت داینامیک از یک متغیر بسازید.
مهمترین متدها:
regex.test(string):
کاربردیترین متد برای اعتبارسنجی (Validation). چک میکند که آیا الگو حداقل یک بار در رشته پیدا میشود یا نه. خروجی آن true یا false است.
string.match(regex):
شبیه re.findall پایتون (اگر پرچم g را داشته باشد). تمام مطابقتها را به صورت یک آرایه برمیگرداند.
string.replace(regex, replacement):
شبیه re.sub پایتون. جستجو و جایگزینی انجام میدهد.
مثال کد جاوا اسکریپت:
JavaScript
const text = “قیمت محصول 15000 تومان و هزینه ارسال 2500 تومان است.”;
const regex = /d+/g; // الگو: یک یا چند عدد (g: همه را پیدا کن)
// اعتبارسنجی: آیا اصلاً عددی در متن هست؟
console.log(regex.test(text)); // خروجی: true
// استخراج: تمام اعداد را بیرون بکش
const numbers = text.match(regex);
console.log(numbers); // خروجی: [“15000”, “2500”]
// جایگزینی
const newText = text.replace(regex, “XXX”);
console.log(newText); // خروجی: “قیمت محصول XXX تومان و هزینه ارسال XXX تومان است.”
بهترین ابزارهای آنلاین برای تست رجکس (Regex101, Regexr)
صادقانه بگویم، هیچکس (حتی من) رجکسهای پیچیده را ذهنی و در تلاش اول بینقص نمینویسد. ما همیشه از ابزارهای تستر آنلاین استفاده میکنیم. این ابزارها پروسه نوشتن و دیباگ کردن رجکس را از یک کار عذابآور به یک کار لذتبخش تبدیل میکنند.
۱. Regex101.com:
انتخاب اول من و استاندارد صنعتی. این سایت یک ابزار بینظیر برای یادگیری و دیباگ است.
چرا عالی است؟
Explanation (توضیحات): در سمت راست، تکتک اجزای الگوی شما را به زبان ساده انگلیسی توضیح میدهد. (مثلاً میگوید: d+ یعنی “Matches one or more digits”).
Match Information: دقیقاً نشان میدهد چه بخشهایی مچ شدهاند و گروههای کپچر شده (Capture Groups) شما چه مقادیری دارند.
انتخاب طعم (Flavor): به شما اجازه میدهد موتور رجکس را انتخاب کنید (Python, JavaScript, PHP, Go, …) چون تفاوتهای خیلی جزئی بین موتورها وجود دارد.
دیباگر (Debugger): قدم به قدم نشان میدهد که موتور رجکس چطور الگو را روی متن شما اجرا میکند.
۲. Regexr.com:
یک ابزار عالی دیگر با رابط کاربری بسیار تمیز و جذاب.
چرا عالی است؟
Cheatsheet (تقلبنامه): یک راهنمای تقلب کامل و دمدستی دارد.
Visualizer: به صورت بصری الگو را نمایش میدهد.
بسیار سریع و سبک است و برای تستهای سریع عالی عمل میکند.
توصیه من: همیشه قبل از اینکه الگوی خود را در کد نهایی استفاده کنید، آن را در Regex101 با انواع دادههای تستی (Test Cases) امتحان کنید.
آشنایی با grep و sed در لینوکس
اگر با سرور یا خط فرمان لینوکس کار میکنید، رجکس بهترین دوست شما خواهد بود. دو ابزار اساسی در لینوکس که با رجکس زندگی میکنند:
۱. grep (Global Regular Expression Print):
کاربرد: ابزار اصلی برای جستجو (Find) در فایلها.
grep خط به خط یک فایل را میخواند و فقط خطوطی را چاپ میکند که با الگوی شما مطابقت دارند.
مثال کاربردی: فرض کنید میخواهیم در یک فایل لاگ (log) سرور به حجم ۲ گیگابایت، تمام خطوطی که حاوی کلمه “Error” یا “Warning” هستند را پیدا کنیم.
grep -E -i “(Error|Warning)” server.log
-E: به grep میگوید از سینتکس Extended Regex (ERE) استفاده کند که مدرنتر است و به ما اجازه استفاده از | (یا) را میدهد.
-i: جستجوی غیر حساس به حروف (Case-Insensitive).
(Error|Warning): الگوی رجکس ما (یا Error یا Warning).
server.log: فایلی که در آن جستجو میکنیم.
۲. sed (Stream Editor):
کاربرد: ابزار اصلی برای ویرایش (Find & Replace) در فایلها.
sed یک «ویرایشگر جریانی» است؛ یعنی فایل را میخواند، دستور شما را روی هر خط اجرا میکند و نتیجه را در خروجی استاندارد (معمولاً ترمینال) چاپ میکند.
سینتکس کلاسیک: sed ‘s/pattern/replacement/g’ filename
s: دستور Substitute (جایگزینی).
pattern: الگوی رجکس شما.
replacement: چیزی که میخواهید جایگزین شود.
g: (Global) یعنی تمام مطابقتها در همان خط را جایگزین کن (وگرنه فقط اولین مورد را جایگزین میکند).
مثال کاربردی: فرض کنید میخواهیم تمام آدرسهای IP را در یک فایل لاگ با [IP-REDACTED] جایگزین کنیم.
sed -E ‘s/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/[IP-REDACTED]/g’ access.log
اشتباهات رایج و نکات امنیتی (افزایش اعتماد و تخصص)
در مسیری که با رجکس کار میکنیم، همه ما (حتی افراد باتجربه) در تلههای مشابهی میافتیم. این بخش صرفاً تئوری نیست، بلکه مجموعهای از تجربیات و «زخمهایی» است که از پروژههای واقعی به دست آمده.
شناختن این اشتباهات رایج و (مهمتر از همه) خطرات امنیتی، تفاوت بین یک متخصص معمولی و یک متخصص درجه یک را مشخص میکند. بیایید با هم ببینیم چطور میتوانیم رجکسهایی بنویسیم که نه تنها کار میکنند، بلکه امن، خوانا و بهینه هم هستند.
اشتباه رایج ۱: پیچیدهنویسی بیش از حد (Overly Complex Regex)
این اولین و رایجترین تله است. گاهی اوقات ما آنقدر غرق در قدرت رجکس میشویم که سعی میکنیم «همهچیز» را در یک الگوی واحد و غولپیکر حل کنیم.
مشکل کجاست؟
خوانایی (Readability): شما الگویی مینویسید که یک هفته بعد، خودتان هم نمیتوانید آن را بفهمید.
نگهداری (Maintainability): اگر نیاز به تغییر کوچکی در منطق الگو داشته باشید، ویرایش آن هیولای تکخطی تقریباً غیرممکن است و ریسک ایجاد باگهای جدید را به شدت بالا میبرد.
تجربه من: یک بار ساعتها وقت گذاشتم تا یک رجکس بنویسم که تمام فرمتهای URL (شامل http, https, ftp, www., بدون www., با پارامترهای query و…) را در یک حرکت اعتبارسنجی کند. نتیجه، الگویی با ۵ خط طول شد که عملاً غیرقابل دیباگ بود.
راهحل (و توصیه من): ساده نگه دارید! به جای یک رجکس پیچیده، از چند رجکس سادهتر و پشت سر هم استفاده کنید. یا در کدنویسی، مسئله را به بخشهای کوچکتر بشکنید.
قانون طلایی: اگر نمیتوانید در عرض ۱۰ ثانیه بفهمید رجکس شما چه کار میکند، زیادی پیچیده است. خوانایی همیشه بر «باهوش به نظر رسیدن» ارجحیت دارد.
اشتباه رایج ۲: فراموش کردن Escape کردن متاکاراکترها
این اشتباه، شماره یک دلیل «چرا رجکس من کار نمیکند؟» در بین تازهکارهاست.
مشکل کجاست؟ فراموش میکنیم که کاراکترهایی مثل . + * ? [ ] ( ) $ ^ و… در رجکس معنی خاص دارند.
. به معنای «هر کاراکتری» است، نه «خود کاراکتر نقطه».
مثال کلاسیک: فرض کنید میخواهید آدرس site.com را پیدا کنید.
الگوی اشتباه: site.com
چرا اشتباه است؟ این الگو با site.com مطابقت دارد… اما با site-com، siteXcom و site_com هم مطابقت دارد! چون . یعنی «هر کاراکتری».
الگوی درست: site.com
در اینجا، . به موتور رجکس میگوید: «منظور من معنی خاص “نقطه” نیست، بلکه خود “کاراکتر نقطه” است.»
توصیه من: همیشه قبل از نوشتن الگو از خودتان بپرسید: «آیا این کاراکتر در رجکس معنی خاصی دارد؟ آیا من دنبال آن معنی هستم یا خود کاراکتر؟» اگر دنبال خود کاراکتر هستید، حتماً آن را با (بکاسلش) گریز (Escape) کنید.
خطر امنیتی: حملات ReDoS (Regex Denial of Service) چیست و چگونه از آن جلوگیری کنیم؟
این بخش فوقالعاده مهم است و ندانستن آن میتواند کل سرور یا اپلیکیشن شما را از کار بیندازد.
ReDoSچیست؟ حمله منع سرویس از طریق رجکس (ReDoS)، نوعی حمله است که در آن، یک مهاجم (Hacker) ورودی متنی خاص و مخربی را به سیستم شما ارسال میکند که باعث میشود موتور رجکس شما برای پردازش آن به زمان نمایی (Exponential Time) نیاز پیدا کند.
چطور کار میکند؟ (مفهوم Backtracking فاجعهبار) موتورهای رجکس برای پیدا کردن الگو، از فرآیندی به نام «عقبگرد» (Backtracking) استفاده میکنند. اگر الگوی شما بد نوشته شده باشد (مخصوصاً با تعیینکنندههای تعداد تودرتو مثل (a+)* یا (a|aa)*)، موتور رجکس در مواجهه با یک ورودی خاص، مجبور میشود میلیاردها مسیر مختلف را تست کند.
نتیجه؟ پردازش آن رشته ورودی ساده، CPU سرور شما را ۱۰۰٪ اشغال میکند، اپلیکیشن هنگ میکند و دیگر به هیچ درخواست دیگری پاسخ نمیدهد (Denial of Service).
چگونه ازReDoSجلوگیری کنیم؟ (نکات حیاتی)
دشمن شماره یک: Quantifier های تودرتو (Nested Quantifiers)
هرگز الگویی شبیه (a+)*، (a*)*، (a|a)* یا (a+)+ ننویسید. اینها کلاسیکترین الگوهای آسیبپذیر هستند.
خاص بنویسید، نه کلی (Be Specific):
بد: (.*)foo
خوب: ([^f]*)foo (یعنی: هر کاراکتری به جز f، تا زمانی که به foo برسی). این کار میزان عقبگرد را به شدت کاهش میدهد.
از Quantifier های تنبل (Lazy) استفاده کنید:
بد (Greedy): a.*b
بهتر (Lazy): a.*?b (تنبل بودن، اغلب مسیرهای عقبگرد را کم میکند).
استفاده از گروههای اتمی (Atomic Groups) (پیشرفته):
اگر موتور رجکس شما پشتیبانی میکند (مثل PHP, Java, .NET)، از گروههای اتمی (?>…) استفاده کنید. این گروهها به موتور میگویند: «وقتی از این گروه رد شدی، دیگر حق عقبگرد به داخل آن را نداری.»
تنظیم Timeout (مهمترین راهکار در عمل):
مهم نیست چقدر رجکس خود را امن مینویسید، همیشه این ریسک وجود دارد. بهترین کار این است که در کد اپلیکیشن خود (چه پایتون، چه Node.js یا C#)، یک محدودیت زمانی (Timeout) (مثلاً ۱ ثانیه) برای اجرای هر عملیات رجکس روی ورودی کاربر تنظیم کنید. اگر پردازش بیشتر طول کشید، آن را متوقف کنید.
بهترین روشها (Best Practices) برای نوشتن رجکس خوانا و بهینه
بیایید جمعبندی کنیم که چطور مثل یک متخصص واقعی رجکس بنویسیم:
۱. کامنتگذاری کنید (بله، در خود رجکس!) اکثر موتورهای رجکس مدرن از حالتی به نام «Verbose» یا «Free-Spacing» پشتیبانی میکنند (در پایتون با فلگ re.X، در جاوا اسکریپت با فلگ x، یا در Regex101 با فعال کردن گزینه مربوطه). این حالت به شما اجازه میدهد:
فاصلههای خالی و رفتن به خط بعد را نادیده بگیرید.
با استفاده از # کامنت بنویسید.
مثال (رجکس ایمیل که قبلاً دیدیم):
Code snippet
(?-x) # حالت عادی:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$
(?x) # حالت Verbose (خوانا):
^ # شروع رشته
[a-zA-Z0-9._%+-]+ # بخش نام کاربری (یک یا چند کاراکتر مجاز)
@ # علامت @
[a-zA-Z0-9.-]+ # بخش دامنه (مثل gmail)
. # خود کاراکتر نقطه
[a-zA-Z]{2,} # بخش TLD (مثل com) (حداقل 2 حرف)
$ # پایان رشته
نوشتن به این شکل، یک لطف بزرگ به همکارانتان (و خودتان در ۶ ماه آینده) است.
۲. از گروههای Non-Capturing استفاده کنید (?:…) اگر از پرانتز ( ) فقط برای گروهبندی (مثلاً اعمال + یا ? روی یک بخش) استفاده میکنید و نیازی به کپچر کردن محتوای آن ندارید، همیشه از (?:…) استفاده کنید.
چرا؟
بهینهتر است: موتور رجکس مجبور نیست نتیجه را در حافظه ذخیره کند.
خواناتر است: شمارهگذاری گروههای کپچر شما ( 1, 2 ) به هم نمیریزد و دقیقاً میدانید کدام گروه را میخواهید.
۳. اولویت با سادگی است (تکرار) دوباره تکرار میکنم: پیچیده ننویسید. دو رجکس ساده و خوانا، بهتر از یک رجکس پیچیده و غیرقابل نگهداری است.
۴. در Regex101 تست کنید (همیشه!) قانون شخصی من: هیچ رجکسی را قبل از تست کامل در Regex101 وارد کد اصلی نمیکنم.
نکته کلیدی تست: فقط ورودیهایی که باید مطابقت داشته باشند را تست نکنید. مهمتر از آن، ورودیهایی که نباید مطابقت داشته باشند (Edge Cases) را هم تست کنید تا مطمئن شوید رجکس شما بیش از حد حریص (Greedy) یا آزاد (Loose) نیست.
فراتر از اصول: مفاهیم پیشرفته Regex (برای حرفهایها)
خب، تا اینجا با مبانی و مفاهیم متوسط رجکس آشنا شدیم. ابزارهایی که تا الان یاد گرفتیم، ۹۵٪ نیازهای روزمره ما را پوشش میدهند.
اما گاهی اوقات، با چالشهایی روبرو میشویم که نیازمند ابزارهای دقیقتر و هوشمندتری هستند. مفاهیمی که در این بخش بررسی میکنیم، جزو پیشرفتهترین قابلیتهای موتورهای رجکس مدرن هستند. تسلط بر اینها به شما اجازه میدهد الگوهایی بنویسید که نه تنها مطابقت را پیدا میکنند، بلکه شرطهایی را هم بررسی میکنند.
نگاه به جلو و عقب (Lookaheads & Lookbehinds)
اینها «جواهرات پنهان» رجکس هستند و درک آنها بسیار حیاتی است. به این گروه از دستورات “Zero-Width Assertions” یا «تاییدهای با پهنای صفر» میگویند.
مفهوم کلیدی: برخلاف . یا d که کاراکترها را مصرف میکنند (یعنی کرسر جستجو را جلو میبرند)، Lookaround ها هیچ کاراکتری را مصرف نمیکنند. آنها فقط نگاه میکنند (به جلو یا عقب) تا ببینند آیا یک شرط برقرار است یا نه، و بعد کرسر به جای اولش برمیگردد.
آنها مثل ^ (شروع خط) یا $ (پایان خط) هستند؛ یک موقعیت یا شرط را بررسی میکنند، نه یک متن را.
ما چهار نوع Lookaround داریم:
۱. نگاه به جلوی مثبت (Positive Lookahead): (?=…)
معنی: «نگاه کن به جلو، آیا الگوی … بلافاصله بعد از این نقطه وجود دارد؟»
مثال: فرض کنید میخواهیم فقط کلمه «حسین» را پیدا کنیم که حتماً بعد از آن کلمه «محمودی» آمده باشد، اما نمیخواهیم «محمودی» جزو متن مچ شده باشد.
الگو: حسین(?=s+محمودی)
تحلیل: این الگو در متن «حسین محمودی» کلمه «حسین» را پیدا میکند، چون موتور رجکس به جلو نگاه میکند و میبیند که « محمودی» وجود دارد (شرط برقرار است)، پس «حسین» را به عنوان مچ برمیگرداند.
تجربه عملی: این همان دستوری است که در مثال «اعتبارسنجی رمز عبور قوی» استفاده کردیم ((?=.*d)) تا چک کنیم که آیا عددی در رشته وجود دارد یا نه، بدون اینکه خود عدد را مصرف کنیم.
۲. نگاه به جلوی منفی (Negative Lookahead): (?!…)
معنی: «نگاه کن به جلو، آیا الگوی … بلافاصله بعد از این نقطه وجود ندارد؟»
مثال: میخواهیم تمام کلمات «سئو» را پیدا کنیم که بعد از آنها کلمه «تکنیکال» نیامده باشد.
الگو: سئو(?!.*تکنیکال)
تحلیل: این الگو «سئو» را در «آموزش سئو» پیدا میکند، اما «سئو» را در «آموزش سئو تکنیکال» پیدا نمیکند، چون شرط (نبودن «تکنیکال» در ادامه) نقض شده است.
۳. نگاه به عقب مثبت (Positive Lookbehind): (?<=…)
معنی: «نگاه کن به عقب، آیا الگوی … بلافاصله قبل از این نقطه وجود دارد؟»
مثال: فرض کنید میخواهیم فقط اعدادی را استخراج کنیم که بلافاصله قبل از آنها علامت دلار ($) آمده باشد (مثلاً قیمتها).
الگو: (?<=$)d+
تحلیل: این الGO در متن price is $100، عدد «100» را مچ میکند (چون قبلش $ هست)، اما در متن price is 100، عدد «100» را مچ نمیکند.
نکته: اکثر موتورهای رجکس (مثل پایتون و جاوا اسکریپت) اجازه نمیدهند الگوی داخل Lookbehind طول متغیر داشته باشد (مثلاً * یا + در آن مجاز نیست).
۴. نگاه به عقب منفی (Negative Lookbehind): (?<!…)
معنی: «نگاه کن به عقب، آیا الگوی … بلافاصله قبل از این نقطه وجود ندارد؟»
مثال: میخواهیم اعدادی را پیدا کنیم که قبل از آنها علامت دلار ($) نیامده باشد.
الگو: (?<!$)d+
تحلیل: این الگو «100» را در price is 100 مچ میکند، اما «100» را در price is $100 مچ نمیکند.
گروههای بدون کپچر (Non-Capturing Groups) (?:…)
این یکی از بهترین روشها (Best Practices) برای نوشتن رجکس خوانا و بهینه است.
یادآوری: پرانتز عادی ( ) دو کار همزمان انجام میدهد:
گروهبندی (Grouping): چند کاراکتر را به عنوان یک واحد در نظر میگیرد (مثلاً برای اعمال + یا | روی آنها).
کپچر کردن (Capturing): متنی که مچ شده را در حافظه (در گروههای 1، 2 و…) ذخیره میکند.
مشکل: اغلب اوقات، ما فقط به کارکرد شماره ۱ (گروهبندی) نیاز داریم و اصلاً نمیخواهیم نتیجه را کپچر کنیم. مثلاً میخواهیم بگوییم (jpg|jpeg|png). ما به این گروه فقط برای استفاده از عملگر | (یا) نیاز داریم و نمیخواهیم حافظه مصرف کنیم یا شمارهگذاری گروههای دیگرمان را به هم بریزیم.
راهحل: (?:…)
این سینتکس به موتور رجکس میگوید: «این گروه را فقط برای گروهبندی استفاده کن و آن را کپچر نکن.»
چرا اینقدر مهم است؟
بهینهسازی (جزئی): موتور رجکس مجبور نیست متنی را در حافظه ذخیره کند. این در رجکسهای خیلی پیچیده و متون خیلی طولانی، تفاوت عملکردی کوچکی ایجاد میکند.
خوانایی و مدیریت گروهها (دلیل اصلی): این کار شمارهگذاری گروههای کپچر شما را «تمیز» نگه میدارد.
تجربه عملی: فرض کنید میخواهید تاریخ و نام فایل را از Report-2025.pdf استخراج کنید.
الگوی بد (با کپچر اضافی): (Report|Data)-(2025).pdf
گروه ۱: Report
گروه ۲: 2025 حالا اگر بخواهید به سال دسترسی پیدا کنید، باید از 2 استفاده کنید.
الگوی خوب (با Non-Capturing): (?:Report|Data)-(2025).pdf
?:Report|Data کپچر نمیشود.
گروه ۱: 2025 حالا سال همیشه در گروه 1 است. این باعث میشود مدیریت گروههایی که واقعاً به آنها نیاز دارید، بسیار سادهتر شود.
توصیه من: به عنوان یک عادت خوب، همیشه از (?:…) برای گروهبندی استفاده کنید، مگر اینکه دقیقاً بدانید که میخواهید آن بخش را کپچر کنید.
عبارات شرطی در رجکس
این یکی از پیشرفتهترین و شاید کماستفادهترین قابلیتهای رجکس است، چون خوانایی را به شدت کم میکند، اما به شدت قدرتمند است.
عبارات شرطی به شما اجازه میدهند یک منطق if-then-else را درون خود الگوی رجکس پیادهسازی کنید.
سینتکس کلی: (?(condition)then-pattern|else-pattern)
if (condition): شرط.
then-pattern: الگویی که در صورت درست بودن شرط اجرا میشود.
|else-pattern: (اختیاری) الگویی که در صورت غلط بودن شرط اجرا میشود.
شرط (Condition) چطور تعریف میشود؟ معمولاً به دو روش:
۱. شرط بر اساس وجود گروه کپچر (Backreference Condition):
سینتکس: (?(1) … | …)
معنی: «اگر گروه کپچر شماره ۱ (یا هر شمارهای) موفق به مچ شدن با چیزی شده بود، …»
مثال کلاسیک: فرض کنید میخواهید متنی را پیدا کنید که اختیاری داخل کوتیشن (” یا ‘) باشد، اما اگر با کوتیشن شروع شد، حتماً باید با همان کوتیشن هم تمام شود.
الگو: ([“‘])?(w+)(?(1)1)
تحلیل:
([“‘])?: (گروه ۱) یک کوتیشن (” یا ‘) را اختیاری (?) مچ کن.
(w+): (گروه ۲) خود کلمه را مچ کن.
(?(1)1): (شرط)
if (?(1)): اگر گروه ۱ (کوتیشن آغازی) مچ شده بود…
then 1: آنگاه، دقیقاً همان چیزی که گروه ۱ مچ کرده بود را (با 1) دوباره مچ کن (یعنی کوتیشن پایانی).
(else …): بخش else وجود ندارد.
نتیجه:
این الگو با “hello” مچ میشود (گروه ۱ ” بود، شرط درست بود، 1 هم ” را مچ کرد).
این الگو با ‘world’ مچ میشود (گروه ۱ ‘ بود، شرط درست بود، 1 هم ‘ را مچ کرد).
این الGO با test مچ میشود (گروه ۱ مچ نشد، شرط غلط بود، پس بخش then اجرا نشد و مچ تمام شد).
این الگو با “test’ مچ نمیشود (چون 1 دنبال ” میگشت نه ‘).
۲. شرط بر اساسLookaround:
سینتکس: (?(?=…) … | …)
معنی: «اگر الگوی Lookahead (?=…) درست بود، …»
مثال: (?(?=a)ab|cd)
تحلیل: «اگر چیزی که جلوتر میآید a است، آنگاه ab را مچ کن، در غیر این صورت cd را مچ کن.»
توصیه صادقانه من: عبارات شرطی بسیار قدرتمند هستند، اما خواندن و دیباگ کردن آنها کابوس است. در ۹۹٪ مواقع، بسیار عاقلانهتر است که این منطق if-then-else را در خود زبان برنامهنویسی (پایتون، جاوا اسکریپت و…) پیاده کنید و رجکس را ساده نگه دارید. از این قابلیت فقط زمانی استفاده کنید که مجبورید تمام منطق را در یک الگوی رجکس واحد بگنجانید (مثلاً در تنظیمات یک ابزار خاص یا ویرایشگر متن).
جمعبندی: رجکس یک ابزار است، نه یک هیولا!
پیمایش ما در دنیای رجکس (Regex) اینجا به پایان میرسد. ما از تعریف ساده و کاربردهای روزمره شروع کردیم، الفبای آن (مثل d, *, +) را یاد گرفتیم، با گروهبندی و منطق | (یا) آشنا شدیم و حتی به مفاهیم پیشرفتهای مثل Lookaround ها و خطرات امنیتی (ReDoS) سرک کشیدیم.
تجربه من میگوید که رجکس یک مهارت «یادگرفتنی» است، نه یک استعداد ذاتی. در ابتدا، سینتکس آن میتواند گیجکننده باشد، اما کلید تسلط بر آن فقط یک چیز است: تمرین عملی.
هرگز نترسید که الگوهای خود را در ابزارهایی مثل Regex101 تست کنید (همه ما این کار را میکنیم!). رجکس یک آچار فرانسه در جعبه ابزار شماست که وقتی به آن مسلط شوید، در زمان شما برای پردازش متن، اعتبارسنجی دادهها و پاکسازی لاگها به طرز شگفتانگیزی صرفهجویی میکند.