مقالات

راهنمای جامع علامت سوال (؟) در Regex از «اختیاری» تا «غیرحریصانه»

علامت سوال در رجکس (؟) فقط به معنی «اختیاری» بودن نیست! در این آموزش، با کاربرد دوم و حیاتی آن یعنی «Lazy» (تنبل) آشنا شوید و جلوی خطاهای Greedy را بگیرید.

لام! نگین هستم، از وزیر سئو. بیاین یه گپ خودمونی بزنیم در مورد یکی از ترسناک‌ترین و در عین حال جذاب‌ترین بخش‌های دنیای سئو و برنامه‌نویسی: رجکس (Regex)!

می‌دونم، اسمش که میاد خیلی‌ها یاد کدهای درهم‌پیچیده می‌افتن و ترجیح می‌دن از کنارش رد بشن. اما امروز می‌خوام یکی از مرموزترین و پرکاربردترین کاراکترهاش، یعنی علامت سوال (؟) رو با هم باز کنیم.

این کاراکتر کوچولو، دو تا کاربرد کاملاً متفاوت و حیاتی داره که دونستنشون جزو <a href=”/your-link/”>مبانی و مفاهیم کلیدی آموزش</a> رجکس به حساب میاد. این دو تا کاربرد اونقدر مهمن که می‌تونن فرق بین یه الگوی دقیق و یه الگوی فاجعه‌بار رو رقم بزنن.

حاضری این غول کوچیک و مرموز رو با هم رام کنیم و ببینیم چطور تو سرچ کنسول، فایل htaccess. و حتی اعتبارسنجی فرم‌ها بهمون کمک می‌کنه؟ بزن بریم!

قبل از اینکه شیرجه بزنیم تو عمق ماجرا، بذار تو این جدول ساده، دو تا کاربرد اصلی (؟) رو برات خلاصه کنم تا تصویر کلی دستت بیاد:

کاربرد (؟) نماد الگو (Pattern) اسم فنی معنی ساده و خودمونی
۱. اختیاری کردن https? Optional Quantifier «این ‘s’ می‌تونه باشه، می‌تونه هم نباشه!» (یعنی صفر یا یکی)
۲. تنبل کردن <b>.*?</b> Lazy (Non-Greedy) «حریص نباش! تا اولین </b> که دیدی، وایسا و ادامه نده.»

 معنای اول و رایج (؟): تطابق «صفر یا یک» (Optional Quantifier)

آخ، رجکس! می‌دونم، بعضی‌ها تا اسمش رو می‌شنون یاد کدهای پیچیده و درهم‌تنیده می‌افتن. اما بذار یه تجربه شخصی بهت بگم: برای ماهایی که کار سئو می‌کنیم، رجکس مثل یه چاقوی سوئیسی همه‌کاره‌ است. مخصوصاً وقتی می‌خوای توی سرچ کنسول یا گوگل آنالیتیکس، یه گزارش رو دقیقاً اونجوری که می‌خوای فیلتر کنی و داده‌های الکی رو حذف کنی.

حالا یکی از بامزه‌ترین و پرکاربردترین ابزارهای این چاقو، همین علامت سوال (؟) هست.

کارش چیه؟ خیلی ساده: «اختیاری» می‌کنه.

یعنی به موتور رجکس می‌گه: «ببین رفیق، این چیزی که دقیقاً قبل از من اومده (چه یه کاراکتر باشه چه یه گروه)، می‌تونه باشه (یعنی ۱ بار) یا اصلاً نباشه (یعنی صفر بار). در هر دو صورت، من قبول می‌کنم و برات پیداش می‌کنم.»

تعریف Quantifier (تکرارکننده) در Regex چیست؟

ببین، Quantifierها (یا تکرارکننده‌ها) تو رجکس، حکم «تعداد» رو دارن. اون‌ها به موتور رجکس دستور می‌دن که از الگو، کلمه یا کاراکتر قبلی، چند تا باید پیدا کنه.

انگار داری به یه دستیار خیلی دقیق می‌گی:

«برو دنبال کلمه‌ی ‘سلام’ بگرد، ولی فقط اگه دقیقاً ۳ بار پشت هم اومده بود.»

«برو دنبال حرف ‘ا’ بگرد، اگه حداقل ۱ بار اومده بود.»

«برو دنبال ‘سایت’ بگرد، چه باشه چه نباشه.» (این کار همون علامت سواله!)

چگونه (؟) یک کاراکتر را اختیاری می‌کند؟ (مثال: colou?r)

این مثال colou?r عالیه. فرض کن یه سایت داری که هم برای مخاطب انگلیسی آمریکایی می‌نویسی و هم بریتانیایی.

اینجا علامت سوال (؟) دقیقاً به کاراکتر قبل از خودش (یعنی ‘u’) می‌چسبه و می‌گه: «این ‘u’ می‌تونه باشه، یا می‌تونه نباشه».

نتیجه چی می‌شه؟

این الگو (Pattern) هم کلمه‌ی color (املای آمریکایی، بدون ‘u’) رو پیدا می‌کنه و هم colour (املای بریتانیایی، با ‘u’) رو. هر دوتاش براش ‘match’ حساب می‌شه.

چگونه (؟) یک گروه (Group) را اختیاری می‌کند؟ (مثال: (https:)?//www)

آها! این دقیقاً جاییه که من تو سرچ کنسول و آنالیتیکس عاشقش می‌شم.

فرض کن می‌خوای تمام URLهای یه بخش خاص از سایتت رو فیلتر کنی، ولی بعضی‌ها با http ایندکس شدن، بعضی‌ها با https. یا مثلاً می‌خوای ببینی چند تا از آدرس‌هات www دارن یا نه.

اینجا (؟) رو روی یه «گروه» اعمال می‌کنیم. گروه یعنی هر چیزی که تو پرانتز ( ) باشه.

تو مثالی که گفتی (https:)?//www، اون (https:) یه گروهه. علامت سوال (؟) که بعدش اومده، کل این گروه رو اختیاری می‌کنه. یعنی آدرس چه https://www باشه و چه فقط //www (البته در عمل ما معمولاً یه الگوی کامل‌تر مثل (https?:)? می‌نویسیم که خود ‘s’ رو هم اختیاری کنه)، جفتش رو پیدا می‌کنه.

این یعنی به جای نوشتن دوتا فیلتر جدا، با یه تیر دو نشون می‌زنی.

تفاوت کلیدی (؟) با * (صفر یا بیشتر) و + (یک یا بیشتر)

این سه‌تا، سه‌قلوهای معروف رجکس هستن و قاطی کردنشون تو روزهای اول یادگیری خیلی رایجه. بذار یه بار برای همیشه تو یه جدول ساده برات بازش کنم که دیگه یادت نره. تفاوتشون فقط تو «تعداد» تکراره.

علامت (Quantifier) اسمش چیه؟ معنیش چیه؟ مثال ساده (الگو: ba?)
? اختیاری (Optional) صفر یا یک بار b رو پیدا می‌کنه (صفر ‘a’). ba رو پیدا می‌کنه (یک ‘a’). baa رو پیدا نمی‌کنه.
* ستاره (Star) صفر یا بیشتر b رو پیدا می‌کنه (صفر ‘a’). ba رو پیدا می‌کنه (یک ‘a’). baaaaa رو هم پیدا می‌کنه (چندین ‘a’).
+ پلاس (Plus) یک یا بیشتر b رو پیدا نمی‌کنه (چون حداقل یک ‘a’ می‌خواد). ba رو پیدا می‌کنه (یک ‘a’). baaaaa رو هم پیدا می‌کنه.

خلاصه و خودمونی:

? (اختیاری): می‌گه «یا هست یا نیست». (حداکثر یکی)

* (ستاره): می‌گه «هر چی بود، حتی هیچی!». (از صفر تا بی‌نهایت)

+ (پلاس): می‌گه «حداقل یکی باید باشه». (از یکی تا بی‌نهایت)

امیدوارم این گپ کوتاه تونسته باشه ترس از رجکس رو برات کمتر کنه. دونستن همین سه تا، می‌تونه کلی از فیلترهای آنالیتیکس و سرچ کنسولت رو دقیق‌تر و حرفه‌ای‌تر کنه.

 معنای دوم و پیشرفته (؟): تبدیل تطابق «حریصانه» (Greedy) به «تنبل» (Lazy)

بذار یه داستانی که اوایل کارم باهاش درگیر بودم رو بگم. می‌خواستم از یه متن، یه تیکه کد HTML رو بکشم بیرون. الگویی که نوشته بودم به جای اینکه فقط همون یه تیکه رو برداره، تقریباً کل صفحه رو برام مچ (Match) می‌کرد! دیوونم کرده بود. مشکل کجا بود؟ مشکل از «حریص» بودن (Greedy) موتور رجکس بود.

درک تفاوت Greedy vs. Lazy: چرا Regex به طور پیش‌فرض حریصانه است؟

موتورهای رجکس (Regex Engines) ذاتاً «حریص» (Greedy) هستن.

«حریص» یعنی چی؟ یعنی وقتی بهشون یه الگو می‌دی (مخصوصاً الگویی مثل .* که یعنی «هر چیزی، هر چند تا»)، اون‌ها سعی می‌کنن طولانی‌ترین، بزرگ‌ترین و کامل‌ترین رشته‌ای که می‌تونن رو پیدا کنن.

تصور کن یه جاروبرقی خیلی قوی داری که می‌فرستیش فقط یه تیکه آشغال کوچیک رو برداره، اما اون تا ته کابلش می‌ره و هر چی سر راهشه می‌بلعه! این حالت پیش‌فرض رجکسه. اون همیشه دنبال «بیشترین» تطابق ممکنه.

(؟) چگونه جلوی «بلعیدن» کل رشته توسط .* را می‌گیرد؟ (مقایسه .* و .*?)

اینجا همون‌جاییه که اون (؟) کوچولوی ما نقش قهرمان رو بازی می‌کنه.

وقتی علامت سوال (؟) رو بلافاصله بعد از یک تکرارکننده (مثل * یا +) میاری، داری به موتور رجکس دستور می‌دی: «هی رفیق، حریص نباش! تنبل (Lazy) باش!»

حالت حریصانه (Greedy): .*

معنی: «هر کاراکتری رو پیدا کن (.)، صفر بار یا بیشتر (*)، و تا جایی که می‌تونی ادامه بده (یعنی تا ته رشته برو)!»

اون جاروبرقی قویه‌ است.

حالت تنبل (Lazy): .*?

معنی: «هر کاراکتری رو پیدا کن (.)، صفر بار یا بیشتر (*)، اما تنبل باش (?)! یعنی به محض اینکه تونستی بقیه‌ی الگو رو پیدا کنی، وایسا.»

این یعنی به جاروبرقی می‌گی: «فقط تا جایی برو که اون تیکه آشغال رو برداشتی، دیگه ادامه نده.»

مثال عملی: استخراج صحیح تگ‌های HTML با تطابق Lazy

بریم سراغ همون مشکلی که خودم داشتم. فرض کن این رشته (String) رو داریم:

این <b>متن اول</b> است و این <b>متن دوم</b> است.

سناریوی ۱: تطابق حریصانه (Greedy

الگو (Pattern): <b>.*</b>

اتفاقی که می‌افته: موتور رجکس <b> اول رو پیدا می‌کنه. بعد .* (حریص) شروع می‌کنه به بلعیدن همه‌چیز: متن اول</b> است و این <b>متن دوم. می‌رسه به </b> آخر. چون «حریص» بوده، تا آخرین جایی که تونسته رفته.

نتیجه: <b>متن اول</b> است و این <b>متن دوم</b> (یک نتیجه‌ی اشتباه و به‌دردنخور)

سناریوی ۲: تطابق تنبل (Lazy

الگو (Pattern): <b>.*?</b>

اتفاقی که می‌افته: موتور رجکس <b> اول رو پیدا می‌کنه. بعد .*? (تنبل) شروع می‌کنه به جلو رفتن، فقط تا زمانی که به اولین </b> برسه. به محض اینکه بهش می‌رسه، کارش رو تموم می‌کنه و می‌گه «پیدا کردم!».

نتیجه (Match 1): <b>متن اول</b>

نتیجه (Match 2): <b>متن دوم</b> (اگه دوباره جستجو رو ادامه بده)

این دقیقاً همون چیزیه که ما می‌خوایم! این ترفند «تنبل‌سازی» تو کارهای روزمره سئو، به‌خصوص وقتی با ابزارهایی مثل Screaming Frog یا Google Sheets برای تحلیل محتوا کار می‌کنی، واقعاً نجات‌دهنده‌ است.

اشتباه رایج: چه زمانی از ? به اشتباه استفاده می‌کنیم؟

بزرگ‌ترین اشتباه، قاطی کردن این دوتا کاربرد علامت سواله.

یادآوری کاربرد ۱ (اختیاری):

الگو: colou?r

? بعد از یه کاراکتر (u) اومده.

معنی: خود کاراکتر ‘u’ «اختیاریه» (صفر یا یک بار).

یادآوری کاربرد ۲ (تنبل‌سازی):

الگو: .*?

? بعد از یه تکرارکننده (*) اومده.

معنی: خود تکرارکننده‌ی * رو «تنبل» می‌کنه.

اشتباه اینه که فکر کنیم a? یعنی «’a’ رو تنبل پیدا کن». نه! a? فقط یعنی «یه ‘a’ اختیاری». اگه می‌خوای «چندین ‘a’ رو تنبل پیدا کنی»، باید بگی a*? یا a+?.

این دو تا کاربرد، با اینکه یه شکل نوشته می‌شن، اما دنیای کارهاشون کاملاً متفاوته.

 کاربردهای عملی (؟) در سئو و برنامه‌نویسی (نمایش تجربه – Experience)

استفاده در Google Search Console برای فیلتر کردن URLهای با پارامتر

این یکی دیگه عصای دست منه! می‌دونی که سرچ کنسول تو بخش Performance، فیلتر رجکس (Regex) داره.

تجربه من: فرض کن می‌خوام عملکرد یه بخش خاص از سایتم، مثلاً example.com/blog/ رو بررسی کنم. ولی یه مشکلی هست:

بعضی URLها با اسلش (/) تموم می‌شن: /blog/my-post/

بعضی‌ها بدون اسلش: /blog/my-post

بعضی‌ها پارامتر UTM کمپین دارن: /blog/my-post?utm_source=…

بعضی‌ها پارامترهای فیلتر داخلی دارن: /blog/my-post?filter=…

اگه بخوام همه‌ی این‌ها رو با هم ببینم یا برعکس، فقط پارامتردارها رو حذف کنم، (؟) به دادم می‌رسه.

مثال برای «اختیاری» بودن اسلش (کاربرد اول):

الگو: ^/blog/my-post/?$

اینجا /? یعنی اون اسلش آخر «اختیاریه». هم با اسلش رو میاره هم بدون اسلش.

مثال برای «اختیاری» بودن کل پارامتر (کاربرد اول):

الگو: /blog/my-post/?(?.*)?$

اینجا (?.*)? یه شاهکاره! یعنی یه گروه ( ) داریم که «اختیاریه» ?.

داخل گروه چیه؟ ?.* (یعنی یه کاراکتر ? واقعی و بعدش هر چیزی).

ترجمه انسانی: «اون URLهایی رو بیار که با /blog/my-post/ یا /blog/my-post شروع می‌شن و شاید (اختیاری) یه علامت سوال و کلی پارامتر هم تهش داشته باشن.»

پاکسازی و بازنویسی (Rewrite) URLها در فایل .htaccess

وای… .htaccess. یادش بخیر! قبل از اینکه اینقدر سیستم‌های مدیریت محتوا پیشرفته بشن، نصف عمر ما تو این فایل می‌گذشت. این فایل قلب تپنده‌ی سرور آپاچی برای ما سئوکارها بود.

مهم‌ترین کاربرد (؟) اینجا برای یکسان‌سازی (Canonicalization) بود.

تجربه من (مشکل رایج): گوگل دو تا URL زیر رو دو تا صفحه‌ی «جدا» می‌دید و این یعنی فاجعه‌ی محتوای تکراری (Duplicate Content):

example.com/about

example.com/about/

راه‌حل با (؟) در RewriteRule:

ما یه قانونی می‌نوشتیم که به سرور بگه: «ببین، اگه کسی آدرس رو بدون اسلش آخر زد، تو خودت با اسلش بازش کن (یا برعکس)».

الگوی رجکس توی اون قانون (مثلاً RewriteCond یا RewriteRule) دقیقاً از همین ? برای «اختیاری» گرفتن اون اسلش استفاده می‌کرد تا بتونه هر دو حالت رو تشخیص بده و به یه حالت واحد ریدایرکت 301 کنه.

این کار ساده، جلوی تقسیم شدن اعتبار صفحه (PageRank) بین دو تا URL رو می‌گرفت.

اعتبارسنجی (Validation) فرم‌ها (مانند شماره تلفن با پیش‌شماره اختیاری)

این یه تجربه مشترک بین سئو (که روی تجربه کاربری یا UX تأثیر داره) و بچه‌های فرانت‌اند (Front-end) هست.

تجربه من: داریم یه لندینگ پیج طراحی می‌کنیم. می‌خوایم شماره تلفن کاربر رو بگیریم.

چالش:

یه کاربر ممکنه بزنه: 09121234567

یه کاربر دیگه ممکنه بزنه: 9121234567 (بدون صفر اول)

یه کاربر خارج از کشور یا حرفه‌ای ممکنه بزنه: +989121234567

اگه فرم ما «سخت‌گیر» باشه و فقط یه حالت رو قبول کنه، کاربر رو فراری می‌ده (نرخ تبدیل یا CR میاد پایین).

راه‌حل با (؟) در اعتبارسنجی:

الگو: ^(+98|0)?9d{9}$

تحلیل الگو:

^: شروع رشته

(+98|0)?: این همون جادوی ماست! یه گروه ( ) که توش «یا +98» یا «» هست. علامت ? تهش می‌گه کل این گروه «اختیاریه». یعنی می‌تونه باشه، می‌تونه هم نباشه!

9d{9}: حتماً باید با ۹ شروع بشه و ۹ تا عدد (d) دیگه هم بعدش بیاد.

$: پایان رشته

نتیجه: هر سه حالتی که کاربر وارد می‌کنه درسته و فرم ارور نمی‌ده.

تحلیل لاگ فایل‌های سرور برای یافتن الگوهای خاص

این دیگه بخش خیلی خفن و تکنیکال سئو هست! وقتی می‌خوای رفتار ربات‌های گوگل (Googlebot) یا هر ربات دیگه‌ای رو تحلیل کنی، دیگه آنالیتیکس و سرچ کنسول جواب نمی‌دن. باید بری سراغ فایل‌های متنی سنگین و ترسناک لاگ سرور (Server Logs).

تجربه من: می‌خواستم ببینم یه ربات مخرب (Bad Bot) که داشت سایت رو اسکرپ می‌کرد، دقیقاً به کدوم صفحه‌ها سر می‌زنه. مشکل اینجا بود که این ربات بعضی وقتا خودشو جای یوزرای واقعی جا می‌زد و یه «کوکی سشن» (Session Cookie) الکی هم تو درخواستش می‌فرستاد.

چالش: درخواست‌هاش دو شکل داشت:

GET /page/1 HTTP/1.1 “BadBot-Agent”

GET /page/2 HTTP/1.1 “BadBot-Agent” “session_id=xyz123”

راه‌حل با (؟) در جستجوی لاگ:

من نمی‌تونستم فقط دنبال “BadBot-Agent” بگردم، چون باید می‌فهمیدم الگوی درخواستش چیه.

از یه الگو شبیه این استفاده کردم: “BadBot-Agent”( “session_id=.*”)?

ترجمه: «دنبال BadBot-Agent بگرد که شاید (اختیاری ?) بعدش یه گروه ( ) شامل session_id=… هم داشته باشه.»

اینطوری تونستم هر دو نوع درخواست این ربات مزاحم رو پیدا کنم و الگوی رفتاریش رو برای بلاک کردنش دربیارم.

اینا فقط چند تا مثال کوچیک بود. می‌بینی؟ همون یه علامت سوال، تو هر بخشی از کار ما، از تجربه کاربری گرفته تا تکنیکال‌ترین بخش سئو، می‌تونه یه دنیای تفاوت ایجاد کنه.

 جمع‌بندی: (؟) یک ابزار دو لبه برای دقت و کنترل در عبارات باقاعده

اگه بخوام همه‌ی حرفامون رو تو چند جمله خلاصه کنم، باید بگم که این علامت سوال (؟) کوچولو، دقیقاً مصداق بارز «فلفل نبین چه ریزه» تو دنیای رجکسه.

همون‌طور که با هم دیدیم، این یه ابزار دو لبه است:

لبه‌ی اول: «دقت» (Precision)

این همون کاربرد اول و ساده‌اش بود: اختیاری کردن (Optional).

به ما این دقت رو می‌ده که بگیم یه کاراکتر (مثل s در https?) یا یه گروه کامل (مثل (www.)?) می‌تونه باشه یا نباشه. این یعنی انعطاف و دقت بالا تو پیدا کردن الگوها.

لبه‌ی دوم: «کنترل» (Control)

این همون کاربرد پیشرفته و خفنش بود: تنبل کردن (Lazy).

به ما این کنترل رو می‌ده که جلوی «حریص بودن» و «بلعیدن» متن توسط تکرارکننده‌هایی مثل .* رو بگیریم و بهشون بگیم: «رفیق! کم بخور، همیشه بخور!».

تجربه‌ی من می‌گه، دونستن همین تفاوت‌های ظریف مثل فرق بین ? به‌تنهایی و ? بعد از *، همون چیزیه که یه کاربر معمولی رجکس رو از یه متخصص جدا می‌کنه. این فقط یه کاراکتر نیست؛ یه ابزار قدرتمند برای رام کردن الگوهای پیچیده‌ است.

حالا نوبت توئه. بعد از این گپ، کدوم یکی از این دو تا کاربرد (؟) حس می‌کنی بیشتر قراره گره از کارت باز کنه و تو تحلیل‌هات به کمکت بیاد؟ برام بنویس.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *