سلام! من نگینم. بذار یه اعترافی بکنم. اوایل که با Regex (عبارات باقاعده) کار میکردم، تقریباً همهش از ستاره (*) و پلاس (+) استفاده میکردم. کارم راه میافتاد، ولی همیشه یه حس «لَق بودن» داشت. انگار کنترلی روی کارم نداشتم و داشتم با حدس و گمان جلو میرفتم.
تا اینکه یه روز گیر یه پروژه ریدایرکت خیلی پیچیده افتادم و مجبور شدم URLهایی رو پیدا کنم که دقیقاً ۶ رقم پشت سر هم داشتن. اینجا بود که * و + کم آوردن و من رسماً به بنبست خوردم.
اونجا بود که فهمیدم چرا درک عمیق مبانی و مفاهیم کلیدی آموزش Regex انقدر مهمه و فقط با دونستن کلیات نمیشه کار حرفهای کرد. امروز میخوام بریم سراغ همون قهرمان گمنام Regex که اون روز مشکل من رو حل کرد: آکولادها {}.
اگه تو هم میخوای یاد بگیری چطور با دقت میلیمتری به Regex بگی دقیقاً چندتا تکرار از یه الگو رو میخوای، جای درستی اومدی. قراره با هم از «حدس زدن» به «کنترل کامل» برسیم.
قبل از اینکه شیرجه بزنیم توی جزئیات {}، بیا توی یه جدول ساده ببینیم این ابزارهای دقیق چه فرقی با رفیقای کلیگوی خودشون دارن:
| ابزار تکرار (Quantifier) | ظاهر | معنی ساده | کِی استفاده کنیم؟ |
|---|---|---|---|
| عمومی (کلیگو) | * | «صفر یا بیشتر» | وقتی تعداد اصلاً مهم نیست |
| عمومی (کلیگو) | + | «یکی یا بیشتر» | وقتی فقط میخوای «حتماً باشه» |
| عمومی (اختیاری) | ? | «صفر یا یکی» | وقتی یه چیزی اختیاریه (Optional) |
| دقیق (مهندسی) | {} | دقیقاً N، حداقل N، یا بین N تا M | وقتی کنترل دقیق روی تعداد میخوایم |
چرا به آکولاد {} نیاز داریم؟ (فراتر از *, + و ؟)
ببین، ستاره (*)، پلاس (+) و علامت سوال (?) ابزارهای تکرار (Quantifiers) عمومی ما هستن. کارشون هم خوبه، اما یه «اما»ی بزرگ دارن: دقت ندارن.
محدودیتهای تکرارکنندههای عمومی (*, +, ?)
بیا خیلی خودمونی ببینیم مشکلشون چیه:
ستاره (*): معنیش اینه: «یا هیچی (صفر بار)، یا هر چند تا که دلت خواست (بینهایت بار)». مثلاً اگه الگوی go*gle رو بدی، هم با ggle (صفر بار ‘o’) مچ میشه، هم با google (دو بار ‘o’) و هم با goooooogle (کلی ‘o’). خیلی بازه!
پلاس (+): این یکی میگه: «حداقل یکی، تا بینهایت». اگه بگی go+gle، دیگه با ggle مچ نمیشه (چون حداقل یه ‘o’ میخواد)، ولی باز هم با google و goooooogle مچ میشه. بازم دقیق نیست.
علامت سوال (?): این از همه محدودتره. میگه: «یا هیچی (صفر بار) یا فقط یکی». مثلاً colou?r هم با color (بدون ‘u’) مچ میشه و هم با colour (با یه ‘u’). به درد اختیاری بودن یه حرف میخوره، ولی اگه تو ۲ تا ‘u’ بخوای چی؟
میبینی؟ مشکل اینجاست که هیچکدوم از اینا به من اجازه نمیدادن بگم «دقیقاً ۶ تا».
معرفی Quantifier-های دقیق: آکولاد {} برای کنترل کامل تکرار
اینجاست که آکولاد {}، که بهش میگن «تکرارکننده دقیق»، وارد میشه و بازی رو عوض میکنه. آکولاد به ما ۳ حالت کنترل مطلق میده:
۱. تکرار دقیق: {n}
این یعنی: «دقیقاً n بار».
مثلاً الگوی d{6} یعنی دقیقاً ۶ رقم (digit) پشت سر هم. این همون چیزی بود که من برای اون URLها لازم داشتم!
۲. تکرار حداقل: {n,}
این یعنی: «حداقل n بار، و هرچقدر بیشتر هم بود، عیبی نداره».
مثلاً d{4,} یعنی دنبال عددهایی بگرد که حداقل ۴ رقمی هستن (مثل ۱۹۹۵ یا ۲۰۲۴ یا ۱۲۳۴۵۶).
۳. تکرار در یک بازه مشخص: {n,m}
این دیگه آخرشه! یعنی: «حداقل n بار و حداکثر m بار».
مثلاً w{3,5} یعنی دنبال کلماتی (word characters) بگرد که بین ۳ تا ۵ حرف دارن.
بذار همهش رو توی یه جدول ساده برات بذارم:
| تکرارکننده (Quantifier) | معنی | مثال (a) |
|---|---|---|
| a* | صفر یا بیشتر | ”, ‘a’, ‘aa’, ‘aaaa’ |
| a+ | یکی یا بیشتر | ‘a’, ‘aa’, ‘aaaa’ |
| a? | صفر یا یکی | ”, ‘a’ |
| a{3} | دقیقاً ۳ بار | ‘aaa’ |
| a{3,} | حداقل ۳ بار | ‘aaa’, ‘aaaa’, ‘aaaaa’ |
| a{3,5} | بین ۳ تا ۵ بار | ‘aaa’, ‘aaaa’, ‘aaaaa’ |
پس دفعه بعدی که خواستی توی گوگل آنالیتیکس، سرچ کنسول، یا کدهای برنامهنویسی یه الگوی خیلی دقیق پیدا کنی، یادت باشه که * و + دوستای خوبی هستن، ولی آکولاد {} اون رفیق متخصصه که کارت رو تمیز و دقیق درمیاره.
بخش ۱: تطابق دقیق با {n} (Exactly n times)
این اولین و سادهترین کاربرد آکولاده. وقتی میدونی دقیقاً دنبال چندتا تکرار هستی، این ابزار کارت رو راه میندازه.
فرمول و کاربرد: پیدا کردن یک الگو دقیقاً n بار
فرمولش خیلی سادهست: {n}.
این n همون عددیه که تو دنبالشی. خیلی رک و پوستکنده به موتور Regex میگی: «من دقیقاً n تا از الگویی که قبل از این آکولاد اومده رو میخوام. نه n-1، نه n+1. دقیقاً n تا!»
مثال عملی: اعتبارسنجی کد پستی ۵ رقمی (مثال: d{5})
این یه مثال کلاسیک برای بچههای وبه. فرض کن یه فرم داری و میخوای مطمئن بشی کاربر کد پستی (Zip Code) رو درست وارد میکنه. کدهای پستی استاندارد آمریکا ۵ رقمی هستن.
الگوی تو این میشه: d{5}
بیا بازش کنیم:
d: این الگو یعنی دنبال یه «رقم» (Digit) بگرد (هر عددی از 0 تا 9).
{5}: اینم بلافاصله بهش میگه: «من دقیقاً ۵ تا از اون رقمها رو پشت سر هم میخوام.»
نتیجه: این الگو با “12345” یا “90210” جور درمیاد (مچ میشه)، ولی با “1234” (چون ۴ تاست) یا “123456” (چون ۶ تاست) اصلاً مچ نمیشه. به همین تمیزی!
مثال عملی: یافتن کدهای محصول ۶ کاراکتری (مثال: [A-Z]{2}d{4})
حالا بیا یه کم حرفهایترش کنیم. فرض کن مدیر یه سایت فروشگاهی بزرگی و کدهای محصول (SKU) شما یه فرمت خیلی خاص دارن: ۲ تا حرف بزرگ انگلیسی، و بلافاصله بعدش ۴ تا عدد.
الگوش میشه: [A-Z]{2}d{4}
ببین چقدر قشنگه:
[A-Z]: اول میگیم دنبال یه کاراکتر باش که هر حرف بزرگ انگلیسی (از A تا Z) باشه.
{2}: بلافاصله بهش میگیم دقیقاً ۲ تا از این حروف بزرگ پشت سر هم.
d: بعدش میگیم دنبال یه رقم باش.
{4}: و در آخر میگیم دقیقاً ۴ تا از این رقمها پشت سر هم.
نتیجه: این الگو با “NG1399” یا “RT2024” مچ میشه. ولی با “N1399” (چون یه حرف داره) یا “NG123” (چون سه تا عدد داره) یا “ng1399” (چون حروفش کوچیکه) مچ نمیشه.
این همون دقتیه که ستاره (*) و پلاس (+) هیچوقت نمیتونستن به ما بِدن. حالا فهمیدی چرا میگم این آکولادها قهرمان دقت هستن؟
میخوای حالا بریم سراغ اینکه چطور بهش «حداقل» یا «بازه» بدیم؟
بخش ۲: حداقل تکرار با {n,} (At least n times)
اینجا ما یه «کف» یا یه «حداقل» تعیین میکنیم، ولی «سقف» رو باز میذاریم.
فرمول و کاربرد: پیدا کردن یک الگو حداقل n بار
فرمول اینه: {n,}
اون ویرگول (,) آخرش، جادوی کاره. یعنی: «حداقل n بار، و هر چقدر بیشتر هم بود، تا بینهایت، قبوله!»
این برای وقتایی عالیه که میخوای از یه حدی کمتر نباشه، ولی بیشتر بودنش مشکلی نداره.
مثال عملی: بررسی قدرت رمز عبور (حداقل ۸ کاراکتر) (مثال: .{8,})
این مثال رو هر روز میبینیم! وقتی یه جا ثبتنام میکنی و بهت میگه: «رمز عبور باید حداقل ۸ کاراکتر باشد.» اونها دقیقاً دارن از این الگو استفاده میکنن.
الگو اینه: .{8,}
بشکافیمش:
. (نقطه): توی Regex، نقطه یه «وایلدکارت» (Wildcard) خیلی معروفه. معنیش اینه: «هر کاراکتری». فرقی نداره حرف باشه، عدد باشه، علامت (!@#) باشه، یا حتی فاصله (Space).
{8,}: اینم که یاد گرفتیم. یعنی: «حداقل ۸ تا، تا بینهایت.»
نتیجه: این الگو با “Negin123” (۸ کاراکتر) مچ میشه، با “MyP@ssword!2025” (۱۷ کاراکتر) هم مچ میشه. اما با “seo123” (۶ کاراکتر) مچ نمیشه.
اینطوری سایتها بلافاصله میفهمن رمزت به اندازه کافی قوی هست یا نه.
مقایسه {1,} با + (یک یا بیشتر)
اینجا یه نکته خیلی جالب هست که میخوام بدونی. یادت میاد قبلاً در مورد + (علامت پلاس) حرف زدیم؟ گفتیم معنیش «یک یا بیشتر» هست.
خب، یه راز بهت بگم: + در واقع یه میانبُر (Shortcut) هست!
a+ دقیقاً، مو به مو، معادل a{1,} هست.
جفتشون میگن: «حداقل یکی، تا بینهایت.»
پس چرا از + استفاده میکنیم؟ فقط چون کوتاهتر و سریعتره. برنامهنویسها و متخصصهای سئو عاشق میانبر هستن!
دونستن این موضوع بهت کمک میکنه بفهمی این علامتهای + و * از کجا اومدن؛ همهشون زیرمجموعهی منطق این آکولادها هستن.
خب، حالا که «دقیق» و «حداقل» رو بلدیم، فقط یه حالت مونده: چطور یه «بازه» مشخص کنیم؟ مثلاً بگیم «بین ۵ تا ۱۰ کاراکتر». آمادهای بریم سراغش؟
خب، تا اینجا یاد گرفتیم چطور «دقیقاً» یه عددی رو مشخص کنیم (مثل {5}) و چطور یه «کف» یا «حداقل» بذاریم (مثل {8,}). حالا میرسیم به قشنگترین و منعطفترین حالتش: حالتی که هم «کف» داریم و هم «سقف»!
این دقیقاً مثل همون داستان «گلدیلاکس» (Goldilocks) و سه تا خرسه. ما دنبال گزینهای هستیم که نه خیلی کم باشه، نه خیلی زیاد. دقیقاً «درست و بهاندازه» باشه. این همون جاییه که {n,m} میدرخشه.
بخش ۳: تعیین محدوده تکرار با {n,m} (Between n and m times)
این حالت به ما کنترل کامل روی یه بازه مشخص رو میده.
فرمول و کاربرد: پیدا کردن الگو بین n و m بار (شامل خودشان)
فرمول اینه: {n,m}
خیلی سادهست:
n میشه حداقل (Minimum).
m میشه حداکثر (Maximum).
مهمترین نکته اینه که خود n و m هم قابل قبولن. یعنی میگیم: «ببین، از n تا m بار، هر چندتا بود، قبوله.»
مثال عملی: اعتبارسنجی نام کاربری (بین ۳ تا ۱۶ کاراکتر) (مثال: w{3,16})
اینم یه مثال روزمره دیگه که توی همه فرمهای ثبتنام دیدیم: «نام کاربری باید بین ۳ تا ۱۶ کاراکتر باشد.»
الگوی این قانون معمولاً این شکلیه: w{3,16}
بیا بازش کنیم:
w: این w یه میانبر باحاله. یعنی «هر کاراکتر کلمهای» (Word Character). شامل حروف انگلیسی (a-z, A-Z)، اعداد (0-9) و علامت آندرلاین (_) میشه. کلاً چیزاییه که برای نام کاربری مجازه.
{3,16}: اینم که قانون ماست: «حداقل ۳ تا، حداکثر ۱۶ تا.»
نتیجه:
با “Negin” (۵ تا) مچ میشه.
با “seo_vazir_1” (۱۰ تا) مچ میشه.
با “me” (۲ تا) مچ نمیشه (چون از حداقل ۳ تا کمتره).
با “this_is_a_very_long_username” (۲۹ تا) هم مچ نمیشه (چون از حداکثر ۱۶ تا بیشتره).
اینطوری سایتها مطمئن میشن که نام کاربری تو نه خیلی کوتاهه که بیمعنی باشه، و نه اونقدر طولانیه که دیتابیس رو به هم بریزه.
اشتباه رایج: ترتیب n و m (بزرگتر و کوچکتر)
اینجا یه سوتی خیلی رایج هست که خودم اوایل چند باری دادم! یادت باشه، توی فرمول {n,m}، همیشه n باید کوچکتر (یا مساوی) m باشه.
{3,16} درسته. (حداقل ۳، حداکثر ۱۶)
{16,3} غلطه!
اگه بنویسی {16,3}، موتور Regex گیج میشه و اصلاً کار نمیکنه یا خطا میده. منطقیه دیگه! نمیتونی بگی «حداقل ۱۶ تا، حداکثر ۳ تا». مثل اینه که بگی یه چیزی بین طبقه ۱۶ تا ۳ پیدا کن!
پس یادت باشه: اول عدد کوچیکه، بعد بزرگه.
خب، تبریک میگم! تو الان تمام حالتهای کنترل تکرار با آکولاد رو بلدی: دقیق ({n}), حداقل ({n,}) و بازه ({n,m}).
حالا که همهشون رو یاد گرفتی، به نظرت کدوم یکی از این سه حالت توی کار روزمره یه متخصص سئو یا محتوا بیشتر به درد میخوره؟
خب، رسیدیم به یکی از اون بخشهایی که اگه ندونی، ممکنه ساعتها وقتت رو تلف کنه. بذار یه خاطره واقعی برات بگم. یه بار میخواستم با Regex تمام تگهای <strong> رو از یه متن HTML بکشم بیرون.
متن من این بود: این <strong>مهم</strong> است و آن <strong>خیلی مهم</strong> است.
منم خیلی ساده نوشتم: <strong>.*</strong>
(که . یعنی هر کاراکتری و * یعنی صفر یا بیشتر)
فکر میکنی چی شد؟ به جای اینکه دو تا تگ رو جدا برگردونه (<strong>مهم</strong> و <strong>خیلی مهم</strong>)، یه تیکه غولپیکر برگردوند:
<strong>مهم</strong> است و آن <strong>خیلی مهم</strong>
یعنی از اولین <strong> شروع کرد و تا آخرین </strong> رفت! چرا؟ چون Regex ذاتاً «حریص» (Greedy) ئه.
موضوع پیشرفته: آکولادها و تطابق حریصانه (Greedy) در مقابل تنبل (Lazy)
این مفهوم «حریص بودن» دقیقاً برای آکولادهای ما هم صدق میکنه.
چرا {n,} و {n,m} به طور پیشفرض «حریصانه» (Greedy) عمل میکنند؟
موتور Regex طوری طراحی شده که همیشه دنبال طولانیترین تطابق ممکن بگرده. مثل آدمی که سر میز سلفسرویس، بشقابش رو تا جایی که میتونه پُر میکنه.
وقتی تو از الگوی {n,m} (مثلاً d{2,5} یعنی بین ۲ تا ۵ رقم) استفاده میکنی، موتور Regex اینجوری فکر میکنه:
«خب، من باید بین ۲ تا ۵ رقم پیدا کنم. قانون من حریصانهست، پس اول سعی میکنم حداکثر رو بردارم (یعنی ۵ تا).»
قدم ۱: آیا میتونم ۵ تا رقم پیدا کنم؟ (اگه متن 123456 باشه، بله 12345 رو برمیداره).
قدم ۲ (اگه ۱ نشد): آیا میتونم ۴ تا رقم پیدا کنم؟
قدم ۳ (اگه ۲ نشد): آیا میتونم ۳ تا رقم پیدا کنم؟
قدم ۴ (اگه ۳ نشد): آیا میتونم ۲ تا رقم پیدا کنم؟
موتور Regex همیشه از سقف (m) شروع میکنه و به سمت کف (n) میاد. به همین دلیل بهش میگن Greedy (حریص)؛ چون اول طولانیترین حالت ممکن رو امتحان میکنه. همین داستان برای {n,} هم هست (اول سعی میکنه تا بینهایت برداره!).
چگونه با افزودن ? تطابق را «تنبل» (Lazy) کنیم؟ (مثال: .*? در مقابل {n,m}?)
حالا میرسیم به راهحل اون مشکل HTML من. چطور به Regex بگیم: «عزیز من، حریص نباش! قانع باش. همون اولین جایی که کارت راه افتاد، وایسا»؟
جواب، اضافه کردن یه علامت سوال (?) بلافاصله بعد از تکرارکنندهست.
نکته مهم: این ? با اون ? که به تنهایی میاد (به معنی صفر یا یکی) فرق داره. وقتی «بعد» از یه تکرارکننده (مثل * یا {}) میاد، داره رفتارش رو عوض میکنه.
این ? حالت رو از «حریصانه» به «تنبل» (Lazy) تغییر میده.
حالا موتور Regex برعکس فکر میکنه:
«خب، من باید بین ۲ تا ۵ رقم پیدا کنم (d{2,5}?). قانون من تنبله، پس اول سعی میکنم حداقل رو بردارم (یعنی ۲ تا).»
قدم ۱: آیا میتونم ۲ تا رقم پیدا کنم؟ (اگه متن 123456 باشه، بله 12 رو برمیداره و متوقف میشه!).
بیایید الگوهای «حریص» و «تنبل» رو کنار هم ببینیم:
| الگو | نوع | معنی | مثال روی متن 1234567 |
|---|---|---|---|
| d{2,5} | Greedy (حریص) | از ۵ شروع کن به پایین | 12345 (حداکثر ممکن رو برداشت) |
| d{2,5}? | Lazy (تنبل) | از ۲ شروع کن به بالا | 12 (حداقل ممکن رو برداشت و وایساد) |
پس برای حل اون مشکل HTML، من باید مینوشتم:
<strong>.*?</strong>
این ? اضافه شده به * میگه: «تنبل باش! از اولین <strong> شروع کن و به محض رسیدن به اولین </strong>، کارت رو تموم کن.»
این تفاوت Greedy و Lazy یکی از اون فوتهای کوزهگری Regexئه که دونستنش کلی جلوی خطاهای عجیبوغریب رو میگیره.
رسیدیم به آخر داستان و وقت جمعبندیه. این سوالی که پرسیدی، دقیقاً کلید ماجراست.
بذار یه تشبیه ساده برات بزنم:
*، + و ? مثل اینه که به دوستت بگی: «برو برام چندتا سیب بخر.» (چندتا؟ یکی؟ ده تا؟ هیچی؟ معلوم نیست!)
آکولاد {} مثل اینه که دقیقاً بگی: «برو برام دقیقاً ۵ تا سیب بخر» ({5})، یا «حداقل ۳ تا بخر» ({3,})، یا «بین ۳ تا ۵ تا بخر» ({3,5}).
دیدی چقدر فرقشونه؟ همهش سر دقت و کنترل ئه.
جمعبندی: چه زمانی از {} بجای *, + یا ? استفاده کنیم؟
قانونش خیلی سادهست:
۱. کی از *, + یا ? استفاده کنیم؟ (وقتی کلیگویی میکنیم)
از اینا وقتی استفاده کن که تعداد دقیق برات مهم نیست و فقط یه حالت کلی رو میخوای بررسی کنی:
* (ستاره): وقتی میگی «صفر یا بیشتر». یعنی «بود بود، نبود هم نبود، بیشتر هم بود عیبی نداره».
مثال: go*gle (با ggle و google و goooogle مچ میشه).
+ (پلاس): وقتی میگی «یکی یا بیشتر». یعنی «حداقل یکی باید باشه، بقیش مهم نیست».
مثال: go+gle (با google و goooogle مچ میشه، ولی با ggle نه).
? (علامت سوال): وقتی میگی «صفر یا یکی». یعنی یه چیزی «اختیاری» (Optional) هست.
مثال: colou?r (با color و colour مچ میشه، ولی با colouur نه).
۲. کی از {} (آکولاد) استفاده کنیم؟ (وقتی دقیق و مهندسی عمل میکنیم)
از آکولاد وقتی استفاده کن که دقیقاً میدونی دنبال چه تعدادی هستی و کنترل کامل میخوای.
{n} (دقیقاً n بار): وقتی یه عدد ثابت و مشخص میخوای.
مثال: کد پستی ۵ رقمی: d{5}
{n,} (حداقل n بار): وقتی یه «کف» یا حداقل مشخص داری.
مثال: رمز عبور حداقل ۸ کاراکتری: .{8,}
{n,m} (بین n تا m بار): وقتی یه بازه دقیق با «کف» و «سقف» داری.
مثال: نام کاربری بین ۳ تا ۱۶ کاراکتر: w{3,16}
یه قانون سرانگشتی برای خودمون:
اگه توی صورت مسئلهات کلمههای «دقیقاً»، «حداقل»، «حداکثر» یا «بین…تا…» وجود داشت، بدون معطلی باید بری سراغ آکولاد {}.
در غیر این صورت، همون * و + و ? کارتو راه میندازن.
به همین سادگی! حالا تو بگو، با این تعریفها، فکر میکنی توی کار روزمره خودت (مثلاً توی سرچ کنسول یا آنالیتیکس) کدوم گروه بیشتر به کارت میاد؟ اون عمومیها یا این دقیقها؟