سلام! من نگینم.
تا حالا شده موقع کار با Regex (عبارات باقاعده) حس کنی داری دنبال یه سوزن خاص تهِ انبار کاه میگردی، اما هی سوزنهای اشتباهی دستت میاد؟
مثلاً فرض کن میخوای فقط آدرسهایی رو پیدا کنی که دقیقاً به .pdf ختم میشن، اما نتایجت پر میشه از URLهایی مثل …/what-is-a-pdf-file/! کلافهکنندهست، نه؟
اینجاست که حس میکنی Regex داره باهات بازی میکنه. اما مشکل از اون نیست؛ مشکل از اینه که ما هنوز با نگهبانهای اصلی ورودی و خروجی متن، یعنی «موقعیتیابی و انکرها در Regex»، درست رفیق نشدیم.
توی این گپ دوستانه، میخوایم بریم سراغ یکی از مهمترین نگهبانها: کاراکتر جادویی $. این دوست کوچیک، همون نگهبانِ درِ خروجیه که به الگوهای ما «دقت» و «اطمینان» اضافه میکنه.
آمادهای که با هم بفهمیم چطور از شر نتایج «تقریباً درست» خلاص بشیم و دقیقاً بزنیم به هدف؟
قبل از اینکه عمیق بشیم، بیا توی یه نگاه سریع، نقش لنگرهای اصلی رو با هم مقایسه کنیم:
| الگو (انکر) | توضیح ساده (کارش چیه؟) | مثال کاربردی |
|---|---|---|
| ^ | لنگر شروع: مطمئن میشه الگو اول خط یا رشتهست. | ^https:// (فقط URLهایی که با https شروع میشن) |
| $ | لنگر پایان (پیشفرض): مطمئن میشه الگو آخر کل رشتهست. | .pdf$ (فقط فایلهایی که به pdf ختم میشن) |
| $ (با فلگ /m) | لنگر پایان خط: مطمئن میشه الگو آخر هر خط هست. | (برای کار با فایلهای لاگ چندخطی) |
| z | لنگر پایان واقعی: همیشه فقط آخر کل رشته رو چک میکنه. | (برای اطمینان قطعی از انتهای کل متن) |
معنای اصلی $: «لنگر» (Anchor) در انتهای رشته
بذار ساده بگم: کاراکتر $ توی دنیای Regex یه معنی بیشتر نداره: «همینجا آخر خطه!»
این یه «لنگر» (Anchor) محسوب میشه. درست مثل لنگر کشتی. وقتی لنگر رو میندازی، کشتی دقیقاً همونجا میایسته. $ هم دقیقاً به موتور Regex میگه: «ببین، الگویی که دنبالش میگردی، باید دقیقاً همینجا، یعنی در انتهای کامل این رشته یا این خط، تموم بشه. نه یه کاراکتر اینورتر، نه اونورتر.»
لنگر (Anchor) چیست و چرا $ یک کاراکتر مصرف نمیکند؟
اینجاش یه کم فنی اما خیلی جالبه. میگیم $ «کاراکتر مصرف نمیکنه» یا (Zero-Width) هست.
یعنی چی؟
ببین، وقتی دنبال کلمه cat میگردی، موتور Regex سه تا کاراکتر (c، a، t) رو «میخوره» یا «مصرف میکنه» تا تطابق رو پیدا کنه.
اما $ مثل یه حرف الفبا نیست. اون یه «موقعیت» رو چک میکنه، نه یه «کاراکتر» رو. $ فقط میره ته رشته و نگاه میکنه و میگه: «آها، اینجا آخرشه.» اون هیچ فضایی اشغال نمیکنه، هیچ حرفی رو از رشته کم نمیکنه.
مثل علامت «تمام» آخر یه فیلمه. اون علامت، بخشی از داستان فیلم نیست، فقط اعلام میکنه که داستان همینجا تموم شد.
چگونه $ تضمین میکند تطابق فقط در انتهای رشته رخ دهد؟
دقیقاً به خاطر همون خاصیت «لنگر» بودنش و اینکه یه «موقعیت» رو چک میکنه.
وقتی تو الگویی مثل error$ رو جستجو میکنی، موتور Regex اینجوری فکر میکنه:
۱. کلمه error رو پیدا میکنه.
۲. بلافاصله بعد از r در error، به کاراکتر $ میرسه.
۳. $ بهش دستور میده: «وایسا! همین الان چک کن که آیا بلافاصله بعد از این r، انتهای رشته هست یا نه؟»
۴. اگه بعدش کاراکتر دیگهای (مثل فاصله، نقطه، یا هر حرف دیگهای) باشه، $ میگه: «نه، اینجا آخرش نیست.» و کل تطابق «رد» میشه (Fail).
۵. اما اگه دقیقاً بعد از r، هیچچیز دیگهای نباشه و رشته تموم بشه، $ میگه: «آفرین! خودشه!» و تطابق «موفق» (Success) اعلام میشه.
مثال عملی: تفاوت بین الگوی com و com$
این مثال دقیقاً مشکل من توی اون فایل لاگ بود و احتمالاً تو هم توی گوگل آنالیتیکس یا سرچ کنسول بهش برمیخوری.
فرض کن میخوای آدرس تمام سایتهایی که با .com تموم میشن رو از یه لیست بلندبالا بکشی بیرون.
۱. اگه از الگوی com استفاده کنی (بدون $):
این الگو هرجایی که سه حرف c-o-m پشت هم باشن رو پیدا میکنه. نتایج چی میشه؟ یه فاجعه!
example.com (پیدا میشه – درسته)
example.commercial.net (پیدا میشه – غلط! چون com وسطشه)
example.org/communication (پیدا میشه – افتضاح! چون com بخشی از یه کلمه دیگهس)
۲. اگه از الگوی com$ استفاده کنی (با $):
این الگو به موتور میگه: «دنبال c-o-m بگرد که بلافاصله بعدش، انتهای رشته باشه.»
example.com (پیدا میشه – درسته)
example.commercial.net (پیدا نمیشه. چون بعد از com، کلی کاراکتر دیگه تا انتهای رشته (.net) فاصله هست.)
example.org/communication (پیدا نمیشه. چون آخر رشته n هست، نه m.)
دیدی؟ $ مثل یه فیلتر دقیق عمل میکنه که جلوی نتایج «مثبت کاذب» (False Positives) رو میگیره.
مقایسه کلیدی: $ (لنگر پایان) در مقابل ^ (لنگر شروع)
اگه $ نگهبان درِ خروجی باشه، یه نگهبان دیگه هم برای درِ ورودی داریم: ^ (بهش میگن کَرِت یا Caret).
این دوتا، زوج طلایی لنگرها توی Regex هستن:
^ (لنگر شروع): میگه «تطابق باید از ابتدای رشته شروع بشه.»
$ (لنگر پایان): میگه «تطابق باید دقیقاً در انتهای رشته تموم بشه.»
بذار توی یه جدول ساده مقایسهشون کنیم:
| الگو (Pattern) | معنی | مثال تطابق | مثال عدم تطابق |
|---|---|---|---|
| ^start | رشته باید با start شروع بشه. | start of the line | this is the start |
| end$ | رشته باید با end تموم بشه. | this is the end | end of the line |
| ^exact$ | رشته باید دقیقاً exact باشه. | exact | exact match یا an exact |
| word | رشته شامل word باشه (هرجاش). | any word here | (فقط اگه کلمه نباشه) |
بریم سراغ یه بحث عمیقتر که یه زمانی حسابی گیجم کرده بود!
یادمه داشتم روی یه فایل متنی کار میکردم که چند تا پاراگراف داشت. میخواستم تمام خطهایی که با یه کلمه خاص تموم میشن رو پیدا کنم. الگوی word$ رو زدم و… هیچی! فقط خط آخر رو پیدا کرد (اگه شانسی با اون کلمه تموم شده بود).
احساس کردم Regex بهم خیانت کرده! اما نکته اینجا بود که من داشتم از $ به شکل سادهاش استفاده میکردم، در حالی که متنم «چندخطی» بود.
کاربرد پیشرفته: $ و حالت چندخطی (Multiline Mode)
اینجا جاییه که $ یه شخصیت دوگانه از خودش نشون میده!
رفتار پیشفرض $ (تطبیق انتهای کل متن)
ببین، به طور پیشفرض، $ فقط یه چیز رو میشناسه: انتهای مطلقِ کلِ رشتهای که بهش دادی.
براش مهم نیست تو اون رشته ۱ خط داری یا ۱۰۰۰ خط. اون صاف میره تهِ تهِ متن وایمیسته. کاراکترهای خط جدید (n) رو توی این حالت اصلاً به عنوان «پایان» حساب نمیکنه؛ براش فقط یه کاراکتر عادی مثل بقیه هستن.
مثال: اگه الگوی end$ رو روی این متن اجرا کنی (بدون حالت چندخطی):
This is the first end
This is the second end
نتیجه: هیچچیزی پیدا نمیکنه! چون انتهای کل متن، کلمه end نیست، بلکه second end هست. $ فقط بعد از second end رو چک میکنه.
چگونه فلگ Multiline (/m) رفتار $ را تغییر میدهد؟ (تطبیق انتهای هر خط)
اینجاست که جادو اتفاق میافته. اکثر ابزارهای Regex به تو اجازه میدن یه «فلگ» (Flag) یا حالت رو فعال کنی. یکی از معروفترینهاش، فلگ m (مخفف Multiline) هست.
وقتی تو فلگ /m رو فعال میکنی، انگار داری به $ میگی:
«خب رفیق، قوانین عوض شد! از این به بعد، علاوهبر انتهای کل متن، هرجایی که یه کاراکتر ‘خط جدید’ (n) دیدی، اونجا رو هم مثل انتهای خط حساب کن.»
مثال: حالا همون الگوی end$ رو با فلگ /m روی متن قبلی اجرا میکنیم:
This is the first end
This is the second end
نتیجه: دو تا تطابق پیدا میکنه!
first end (چون $ انتهای خط اول، یعنی قبل از n رو چک میکنه)
second end (چون $ انتهای خط دوم، یعنی انتهای کل رشته رو چک میکنه)
این فلگ برای کار با فایلهای لاگ، کدهای برنامهنویسی یا هر متنی که ساختار خطبهخط داره، حیاتیه.
تفاوت ظریف $ و z (پایان واقعی کل رشته)
حالا میرسیم به اون نکتهی خیلی ظریف که ویراستارهای حرفهای Regex عاشقش میشن.
گفته شد که $ در حالت چندخطی (/m)، انتهای هر خط رو پیدا میکنه. اما این یه مشکلی داره. فرض کن میخوای فقط و فقط انتهای کل متن رو پیدا کنی، و برات مهم نیست که حالت /m روشنه یا خاموش.
اینجا یه ابزار دقیقتر به اسم z (بعضی جاها Z هم داریم که یه کم فرق داره، ولی z رایجتره) وارد میشه.
z یعنی: پایان مطلق و واقعی کل رشته.
z کاری به فلگ /m نداره.
z کاری به کاراکترهای خط جدید (n) نداره.
اون فقط و فقط به انتهای فیزیکی کل دادهای که بهش دادی نگاه میکنه.
مقایسه در حالت چندخطی (/m):
| الگو | روی متن line1nline2 | نتیجه |
|---|---|---|
| line1$ | بله | $ بعد از line1 (قبل از n) تطابق پیدا میکنه. |
| line2$ | بله | $ بعد از line2 (انتهای کل رشته) تطابق پیدا میکنه. |
| line1z | نه! | z فقط انتهای کل رشته (بعد از line2) رو میشناسه. |
| line2z | بله | z دقیقاً انتهای کل رشته رو پیدا میکKE. |
پس اگه بخوام ساده بگم:
$ (در حالت /m): نگهبان هر طبقه (هر خط).
z: نگهبان درِ خروجی اصلی کل ساختمون (کل متن).
این تفاوتهای کوچیک، همون چیزهایی هستن که وقتی داری دادههای حساس رو تمیز میکنی، جلوی خطاهای بزرگ رو میگیرن.
اعتبارسنجی فرمت فایل (مثال: .pdf$ یا .jpe?g$)
یادمه یه بار داشتیم تمام لینکهای خروجی سایت به فایلهای PDF رو از توی دیتابیس محتوا میکشیدیم بیرون. میخواستیم ببینیم چقدر «دارایی» (Asset) قابل دانلود داریم که داریم بهشون لینک میدیم.
مشکل کجا بود؟
اگه من فقط کلمهی .pdf رو جستجو میکردم، یه فاجعه پیش میومد. چرا؟ چون ممکن بود مقالهای با این URL هم پیدا بشه:
example.com/blog/best-pdf-tools-for-seo/
این یه صفحهی بلاگه، نه یه فایل PDF!
راه حل با $:
اینجا بود که $ وارد شد. من از این الگو استفاده کردم: .pdf$
. (بکاسلش قبل از نقطه): به Regex میگه که منظورم خودِ کاراکتر «نقطه» است، نه «هر کاراکتری» (چون نقطه توی Regex یه معنی خاص داره).
pdf: خب، اینم که خود کلمهست.
$: و اینم نگهبان طلایی ما! میگه: «رشته باید همینجا تموم بشه.»
نتیجه؟
این الگو فقط رشتههایی مثل example.com/files/my-ebook.pdf رو پیدا میکرد و اون URL مقاله بلاگ رو خیلی شیک نادیده میگرفت.
همین کار رو دقیقاً میتونی برای .jpe?g$ (که هم .jpg و هم .jpeg رو پوشش میده) یا .png$ استفاده کنی تا مطمئن شی داری دربارهی خود فایل عکس حرف میزنی، نه یه صفحهای که توش کلمه jpg داره.
اعتبارسنجی URL (مثال: اطمینان از اینکه URL با اسلش / تمام میشود)
اوه… این یکی دردِ آشنا و کابوس همیشگی ما سئوکارهاست: محتوای تکراری (Duplicate Content).
خیلی از سرورها طوری تنظیم شدن که دو تا URL زیر رو به عنوان دو صفحهی جدا میشناسن:
example.com/category/shoes (بدون اسلش)
example.com/category/shoes/ (با اسلش)
از نظر گوگل، اینا دو تا صفحهی متفاوت با محتوای یکسان هستن و این برای سئو افتضاحه!
راه حل با $:
ما معمولاً توی تیم تصمیم میگیریم که همیشه از یه نسخه (مثلاً نسخهی با اسلش /) استفاده کنیم و اون یکی رو روی این ریدایرکت کنیم.
حالا چطوری توی ابزارهایی مثل اسکریمینگ فراگ (Screaming Frog) یا حتی توی گوگل شیت، URLهایی که باید اسلش داشته باشن ولی ندارن رو پیدا کنیم؟
اینجاست که $ بهمون کمک میکنه. ما میتونیم با الگوی /$ چک کنیم که کدوم URLها درست هستن (یعنی با اسلش تموم میشن) و بعد لیست رو برعکس کنیم تا URLهای غلط (اونایی که با اسلش تموم نمیشن) رو پیدا کنیم و همهشون رو بفرستیم برای ریدایرکت ۳۰۱.
پاکسازی دادهها (اطمینان از عدم وجود کاراکترهای ناخواسته در انتهای ورودی)
این یکی رو توی فرمهای ثبتنام، بخش نظرات یا هرجایی که کاربر چیزی وارد میکنه، زیاد دیدم.
مشکل کجاست؟
کاربر اسمش رو وارد میکنه: “نگین ” (با یه فاصله اضافه تهش).
شاید به چشمت نیاد، اما توی دیتابیس، “نگین ” با “نگین” دو تا چیز کاملاً متفاوته! این باعث میشه موقع جستجو، فیلتر کردن یا مرتبسازی، کل دادههات به هم بریزه.
راه حل با $:
موقع اعتبارسنجی ورودی کاربر (Validation)، ما میخوایم مطمئن شیم که ورودی با یه کاراکتر ناخواسته مثل فاصله (Whitespace) تموم نشه.
میتونیم از الگویی مثل s$ استفاده کنیم.
s: این کد مخفف هر نوع فاصلهای هست (مثل اسپیس، تب و…).
$: یعنی این فاصله دقیقاً در انتهای رشته باشه.
اگه این الگو تطابق پیدا کرد، یعنی ورودی کاربر «کثیف» (Dirty) هست و باید قبل از ذخیره شدن توی دیتابیس، اون فاصلهی اضافه با یه تابع مثل trim() پاک بشه.
$ اینجا مثل یه جاروبرقی دقیق عمل میکنه که میره تهِ ورودی کاربر و چک میکنه هیچ آشغال اضافهای اونجا نمونده باشه.
دیدی؟ $ فقط یه کاراکتر خشک و فنی نیست. یه نگهبان دقیقه، یه ابزار پاکسازی دادهست و یه فیلتر قدرتمند برای ماهایی که با دادههای متنی سروکار داریم.
جمعبندی: $، ابزار شما برای اطمینان از پایان دقیق الگو
خب، به آخر این سفر هیجانانگیز به دنیای $ رسیدیم!
یادته اولش از اون فایل لاگ کذایی برات گفتم که چطور کلافهم کرده بود؟ حالا میدونیم که این کاراکتر کوچولو، در واقع یه ابزار فوقالعاده قدرتمند برای «دقت» و «اطمینان» هست.
$ اون نگهبان سختگیریه که دمِ در خروجی ایستاده و مطمئن میشه که تطابق تو دقیقاً همونجایی که باید، تموم میشه.
دیگه نگران پیدا کردن example.com وسط یه URL طولانی نیستی، چون با com$ دقیقاً انتهای دامنه رو هدف میگیری.
دیگه نگران پیدا کردن .pdf توی عنوان یه مقاله نیستی، چون با .pdf$ خودِ فایل رو پیدا میکنی.
و دیگه نگران اون فاصلههای اضافه و مزاحم آخر ورودیهای کاربر نیستی، چون با s$ مچشون رو میگیری.
$ به ما کنترل میده. کنترل روی دادهها، کنترل روی نتایج جستجو و اطمینان از اینکه دقیقاً همون چیزی رو به دست میاریم که دنبالش بودیم.
حالا تو بهم بگو، با این قدرتی که از $ شناختی، اولین جایی که میخوای ازش برای «دقیق» کردن کارت استفاده کنی کجاست؟ مشتاقم بشنوم!