فصل ۱۹ – عبارات منظم

عبارات منظم (Regular Expressions) ابزاری قدرتمند برای توصیف الگوهای متنی هستند.
با کمک آن‌ها می‌توانیم رشته‌هایی را که با الگوی ما مطابقت دارند جست‌وجو، استخراج یا دستکاری کنیم.
در جهان یونیکس، بسیاری از برنامه‌ها مانند grep، sed، awk و حتی ویرایشگرهایی چون vim و emacs از این قابلیت پشتیبانی می‌کنند.
کار کردن با عبارات منظم در نگاه اول پیچیده به نظر می‌رسد، اما پس از درک اصول اولیه می‌بینیم که چقدر در اتوماسیون و فیلتر کردن داده‌ها مفیدند.

در این فصل یاد می‌گیریم:


عبارات منظم چه هستند؟

عبارت منظم رشته‌ای از کاراکترهاست که یک الگوی جست‌وجو را تعریف می‌کند.
وقتی برنامه‌ای از regex استفاده می‌کند، رشتهٔ ورودی را بررسی می‌کند تا ببیند آیا جایی با الگو تطابق دارد یا نه.
برای مثال عبارت foo هرجا که سه کاراکتر f، o و o پشت سر هم باشند تطابق پیدا می‌کند.
اما قدرت اصلی regex در متاکاراکترهاست؛ کاراکترهایی که معنای ویژه دارند و می‌توانند مجموعه‌ای از کاراکترها، تکرارها، یا موقعیت‌ها را توصیف کنند.

در این فصل از نحو «توسعه‌یافتهٔ» عبارات منظم که grep -E یا egrep از آن پشتیبانی می‌کند استفاده می‌کنیم.
بیشتر برنامه‌های مدرن همین مجموعهٔ امکانات را ارائه می‌دهند.


مثال ساده با grep

برنامهٔ grep برای جست‌وجوی الگو در فایل‌ها یا ورودی استاندارد استفاده می‌شود.
ساختار کلی این‌گونه است:

grep [گزینه‌ها] 'الگو' فایل‌ها

با گزینهٔ -n شمارهٔ خط و با -i حالت حساسیت به حروف را کنترل می‌کنیم.
اگر به‌جای grep از egrep یا grep -E استفاده کنیم، نحو پیشرفتهٔ regex فعال می‌شود.

فرض کنیم فایل /etc/passwd را بررسی می‌کنیم:

grep -n '^root' /etc/passwd

این دستور خطی را پیدا می‌کند که با واژهٔ root در ابتدای خط شروع شده باشد.
علامت ^ متاکاراکتری است که ابتدای خط را مشخص می‌کند.
به همین صورت $ انتهای خط را نشان می‌دهد.


متاکاراکترهای بنیادی

در اینجا با مهم‌ترین متاکاراکترها آشنا می‌شویم. برای خوانایی بیشتر، جدول زیر نمادهای اصلی را خلاصه می‌کند:

📋 جدول ۱۹-۱: متاکاراکترهای پایه در عبارات منظم

نماد توضیح
. هر کاراکتر منفرد (به‌جز پایان خط) را تطبیق می‌دهد؛ مثل gr.p که grep یا grap را پیدا می‌کند.
[ ] تعریف مجموعهٔ کاراکترهای مجاز؛ gr[ae]y واژه‌های gray و grey را می‌یابد.
[^ ] اگر اولین کاراکتر داخل براکت ^ باشد، مجموعه معکوس می‌شود؛ [^0-9] هر چیزی جز رقم را می‌پذیرد.
- داخل براکت‌ها برای تعریف بازه‌ها استفاده می‌شود؛ a-z همهٔ حروف کوچک است.
\ کاراکتر بعدی را «فرار» می‌دهد تا معنای ویژه‌اش خنثی شود یا یک توالی خاص بسازد.

نکته: اگر لازم است یکی از کاراکترهای خاص (., [, ], * و...) به شکل عادی ظاهر شود، قبل از آن \ بگذارید تا معناي ویژه‌اش از بین برود.


تکرار و کمیت‌سنج‌ها

برای بیان «چند بار رخ دادن» از کمیت‌سنج‌ها استفاده می‌کنیم:

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


عملگر «یا» و تقدم الگوها

برای بیان «یکی از چند الگو» می‌توان از عملگر | استفاده کرد. این عملگر مشابه OR منطقی است و نخستین تطابق ممکن را پیدا می‌کند.

grep -E 'cat|dog' pets.txt

در مثال بالا هر خطی که شامل واژهٔ «cat» یا «dog» باشد چاپ می‌شود. اگر به دنبال تطبیق‌های طولانی‌تر هستیم، بهتر است از پرانتز برای گروه‌بندی استفاده کنیم تا تقدم عملگر روشن باشد:

grep -E 'gr(e|a)y' colors.txt

در اینجا پرانتز مشخص می‌کند که | فقط روی دو حرف «e» و «a» اعمال شود. بدون پرانتز نتیجهٔ متفاوتی خواهیم گرفت. در regex های طولانی‌تر همیشه تقدم عملگرها را با پرانتز شفاف کنید تا الگو دقیقاً همان چیزی باشد که انتظار دارید.


گروه‌بندی و مرجع‌گیری

با قرار دادن بخشی از الگو میان پرانتز می‌توانیم آن را گروه‌بندی کنیم.
این کار دو مزیت دارد: اول اینکه کمیت‌سنج‌ها را روی کل گروه اعمال می‌کنیم و دوم اینکه می‌توانیم از بخش‌های تطبیق‌یافته دوباره استفاده کنیم.

(ha)+

این الگو رشته‌هایی مانند ha, haha, hahaha را می‌یابد.

برای اشارهٔ مجدد به گروه‌ها در بعضی برنامه‌ها از \1, \2 و ... استفاده می‌شود.
به عنوان نمونه در sed می‌توانیم یک عبارت را جابه‌جا کنیم:

s/\([[:alpha:]]\+\) \([[:alpha:]]\+\)/\2 \1/

این فرمان نام و نام خانوادگی را جابه‌جا می‌کند.


لنگرها و مرزهای کلمه

لنگرها برای اشاره به موقعیت‌ها هستند نه کاراکترها.
^ ابتدای خط و $ انتهای خط را مشخص می‌کند.
علاوه بر آن، \b مرز کلمه و \B غیرمرز کلمه را مشخص می‌کند (در برنامه‌هایی که از آن پشتیبانی می‌کنند).
به کمک آن‌ها می‌توانیم از تطبیق‌های ناخواسته جلوگیری کنیم.

grep -E '\bcat\b' داستان.txt

این مثال فقط واژهٔ کامل «cat» را می‌یابد و scatter یا concatenate را نادیده می‌گیرد.


کلاس‌های کاراکتری از پیش تعریف شده

برای راحتی، کلاس‌هایی وجود دارد که مجموعه‌ای از کاراکترها را توصیف می‌کنند:

این کلاس‌ها داخل براکت استفاده می‌شوند و مستقل از Locale سیستم کار می‌کنند.


ترکیب ابزارها

عموماً regex همراه با فیلترهای متنی استفاده می‌شود.
برای مثال می‌توانیم با grep خطوط مورد نظر را انتخاب کنیم و سپس با sed تغییر دهیم:

grep -E '^[[:digit:]]{4}-' log.txt | sed 's/-/\//'

در این دستور، خطوطی که با تاریخ چهاربخشی شروع می‌شوند انتخاب و سپس خط تیره‌ها با / جایگزین می‌گردند.

همچنین find گزینهٔ -regex دارد که اجازه می‌دهد مسیرها را براساس الگو انتخاب کنیم.
در bash نیز دستور [[ string =~ regex ]] برای تطبیق الگو در شرط‌ها به کار می‌رود.


مثال‌های عملی

  1. پیدا کردن آدرس‌های IP:
grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
  1. یافتن خطوطی که با عدد شروع و با نقطه تمام می‌شوند:
grep -E '^[[:digit:]]+.*\.$' report.txt
  1. استخراج تگ‌های HTML ساده:
grep -o -E '<[[:alpha:]][^>]*>' index.html

هرچند regex برای HTML پیچیده مناسب نیست، اما برای جست‌وجوهای سریع کارآمد است.


جمع‌بندی

عبارات منظم در آغاز گیج‌کننده هستند، اما تمرین مداوم باعث می‌شود الگوهای پیچیده را به‌راحتی بسازیم.
برای تسلط بیشتر باید نمونه‌های واقعی را بررسی و آزمایش کنیم.
بسیاری از ویرایشگرها و IDEها امکان «جست‌وجو با regex» را دارند که تمرین خوبی به‌شمار می‌رود.

ابزارهایی که در ادامهٔ کتاب بررسی می‌کنیم مانند sed و awk به‌شدت به regex متکی‌اند، بنابراین شناخت آن‌ها پایه‌ای مهم برای پردازش متن در لینوکس است.


تمرین

  1. با استفاده از grep -E تمام خطوطی را بیابید که با شمارهٔ تلفن به فرمت (xxx) xxx-xxxx شروع می‌شوند و سپس همان الگو را طوری تغییر دهید که شماره‌های ۳ یا ۴ رقمی داخلی (extension) را نیز بپذیرد.
  2. فایلی شامل نشانی‌های ایمیل تهیه کنید و با کمک regex آدرس‌هایی را که دامنهٔ آن‌ها .edu نیست فیلتر کنید.
  3. با sed یا perl -pe از قابلیت مرجع‌گیری (\1, \2 و غیره) استفاده کنید تا ترتیب نام و نام خانوادگی در فهرستی را عوض کنید.

مطالعهٔ بیشتر