عبارات منظم (Regular Expressions)

در آموزشهای پیش رو ابزارهای را استفاده خواهیم کرد که با استفاده از آنها میتوان متن را دستکاری کرد. همانطور که پیشتر عنوان شد، داده متنی، نقش بسیار مهمی را در سیستمهای یونیکس ایفا میکند؛
ولی قبل از آنکه بتوانیک از تمامی ویژگیهایی که توسط این ابزارها ارایه شده استفاده نماییم، میبایست تکنولوژی پیچیدهای که در این ابزارها بهکار رفته را بشناسیم، که آن را عبارات منظم (Regular Expressions) مینامند.
عبارات منظم (Regular Expressions)
عبارات منظم (Regular Expressions) نشانههای سمبولیکی هستند که بهمنظور شناسایی الگوها در متن استفاده میشوند. عبارات منظم از برخی جهات، شبیه wildcardهای Shell هستند؛ چرا که آنها نیز فایل و نام مسیر (pathname) را (در مقیاسی بزرگتر) مطابقت میدهند.
عبارات منظم توسط بسیاری از ابزارهای خط فرمان و همچنین بیشتر زبانهای برنامهنویسی، بهمنظور سهولت راهکارهای دستکاری مشکلات متن پشتیبانی میشوند.
شایان ذکر است که همه عبارات منظم در ابزارهای مختلف و زبانهای برنامهنویسی مختلف، متفاوت عمل میکنند. در این آموزش، فقط به عبارات منظم تحت استاندارد POSIX (که اکثر ابزارهای خط فرمان را پوشش میدهد)، پرداختهایم.
فرمان grep (جستجو در میان متن)
برنامه اصلی که ما از آن بهمنظور کار با عبارات منظم استفاده خواهیم کرد، grep خواهد بود. در واقع نام grep برگرفته از عبارت Global Regular Expression Print به معنای «چاپ عبارت منظم سراسری» گرفته شده است. برنامه grep فایلهای متنی را برای وجود یک عبارت منظم جستجو کرده و هر خطی که دارای عبارت منظم تعیین شده، باشد را در استاندارد خروجی چاپ مینماید.
در مثال زیر، فرمان grep با رشتههای معین استفاده شده است:
1 |
[me@linuxbox ~]$ ls /usr/bin | grep zip |
این فرمان، همه فایلهای موجود در دایرکتوری /usr/bin که شامل نام رشته zip هستند را لیست میکند. فرمان grep گزینهها و آرگومانها را به این صورت قبول میکند:
1 |
grep [options] regex [file...] |
که در فرمان فوق بهجای regex عبارت منظم مربوطه قرار خواهد گرفت:
در جدول زیر گزینههای رایج grep لیست شده است:
گزینه | توضیحات |
---|---|
-i | نادیده گرفتن کاراکترهای بزرگ و کوچک از هم. با --ignore-case هم تعیین میشود. |
-v | معکوس کردن تطبیق. بصورت عادی grep خطوطی که دارای مقاومت هستند را چاپ میکند. این گزینه موجب میشود که هر grep هر خطی را که حاوی مطابقت نیست را هم چاپ میکند. |
-c | چاپ تعداد تطبیقها به جای خطوط ممکن است بوسیله --count نیز تعیین شود. |
-l | چاپ نام هر فایلی که حاوی یک تطبیق است به جای خود خطوط. ممکن است بوسیله --files-with-matches نیز تعیین شود. |
-L | درست شبیه گزینه -l اما فقط نام فایلهایی که حاوی مطابقت نیستند را چاپ میکند. ممکن است بوسیله --files-without-math نیز تعیین شود. |
-n | پیشوند کردن هر خط با شماره خط درون فایل. ممکن است با گزینه --line-number نیز تعیین گردد. |
-h | برای جستجوهای چندفایلی، جلوگیری از خروجی نام فایلها. ممکن است با گزینه --no-filename نیز تعیین گردد. |
بهمنظور توصیف کاملتر فرمان grep چند فایل متنی برای جستجو ایجاد میکنیم:
1 2 3 4 5 6 7 |
[me@linuxbox ~]$ ls /bin > dirlist-bin.txt [me@linuxbox ~]$ ls /usr/bin > dirlist-usr-bin.txt [me@linuxbox ~]$ ls /sbin > dirlist-sbin.txt [me@linuxbox ~]$ ls /usr/sbin > dirlist-usr-sbin.txt [me@linuxbox ~]$ ls dirlist*.txt dirlist-bin.txt dirlist-sbin.txt dirlist-usr-sbin.txt dirlist-usr-bin.txt |
در ادامه میتوانیم جستجوی سادهای از لیست فایلها را به این صورت نیز انجام دهیم:
1 2 3 |
[me@linuxbox ~]$ grep bzip dirlist*.txt dirlist-bin.txt:bzip2 dirlist-bin.txt:bzip2recover |
در این مثال، grep همه فایلهای لیست شده را برای رشته bzip جستجو میکند و دو مورد تطبیق را پیدا میکند که هر دو مورد درون فایل dirlist-bin.txt موجودند. اگر بخواهیم بهجای لیست موارد تطبیق، فقط فایلهای مربوطه را لیست نماییم، میتوانیم از گزینه –l استفاده کنیم:
1 2 |
[me@linuxbox ~]$ grep -l bzip dirlist*.txt dirlist-bin.txt |
در مقابل، اگر بخواهیم لیستی از فقط فایلهایی که با مورد ما مطابقت ندارند را ببینیم، میتوانیم از فرمان زیر استفاده کنیم:
1 2 3 4 |
[me@linuxbox ~]$ grep -L bzip dirlist*.txt dirlist-sbin.txt dirlist-usr-bin.txt dirlist-usr-sbin.txt |
متاکاراکترها و لیترالها (Metacharacters and Literals)
شاید مشخص نبود، ولی جستجوهای grep ما از عبارات منظم سادهای استفاده میکردند. با این همه، مثالهای سادهای بودند. عبارت منظم bzip بهصورتی عمل میکند که حداقل چهار کاراکتر b، z، i و p جایی درون یک خط به ترتیب ذکر شده و بدن هیچ کاراکتری مابین آنها (یعنی بهصورت bzip) یافت شوند.
کاراکترهای موجود در رشته bzip کاراکترهای لیترال (Literal) هستند، به این صورت که آنها با خودشان تطبیق داده میشوند. ممکن است عبارات منظم، علاوه بر کاراکترهای لیترال، حاوی متاکاراکترها نیز باشند. بدین صورت که آنها میتوان بهمنظور مطابقتهای پیچیدهتر مورد استفاده قرار داد. متاکاراکترهای عبارات منظم شامل موارد زیر هستند:
1 |
^ $ . [ ] { } - ? * + ( ) | \ |
مابقی کاراکترها را کاراکترهای لیترال (Literal) در نظر میگیرند.
البته کاراکتر بکاسلش (\) در برخی موارد برای ایجاد metasequenceها استفاده میشود و همچنین متاکاراکترها را قادر میسازد تا بهعنوان کاراکترهای لیترال (بهجای اینکه بهعنوان یک متاکاراکتر تفسیر شوند) رفتار کنند.
نکته: همانطور که میبینیم، بسیاری از عبارات منظم حاوی متاکاراکترها را در خط فرمان بهکار میبریم، حتما بایستی برای جلوگیری از بسط آنها را داخل کوتیشن قرار دهیم.
کاراکتر همه (The Any Character)
اولین متاکاراکتری که آن را بررسی میکنیم کاراکتر dot یا همان نقطه (.) است که بهمنظور مطابقت همه کاراکترها استفاده میشود. اگر که آن را در یک عبارت منظم قرار دهیم، موجب تطبیق هر کاراکتری در آن موقعیت کاراکتری میشود.
مثال زیر گویای همه چیز است:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[me@linuxbox ~]$ grep -h '.zip' dirlist*.txt bunzip2 bzip2 bzip2recover gunzip gzip funzip gpg-zip preunzip prezip prezip-bin unzip unzipsfx |
در این مثال، ما بهدنبال هر خطی در فایلهای خود میگردیم که با عبارت منظم .zip مطابقت دارد. توجه کنید که در نتایج، برنامه zip پیدا نشد! به این دلیل که قرار دادن متاکاراکتر نقطه در عبارت منظم، طول مورد نیاز برای مطابقت را به چهار کاراکتر افزایش میدهد و از آنجایی که zip فقط سه کاراکتر دارد، مطابقت پیدا نمیکند.
همچنین اگر فایلی در لیستهای ما دارای پسوند .zip باشد تطبیق داده میشود؛ چرا که کاراکتر نقطه، در پسوند فایل نیز بهعنوان کاراکتر همه (any) رفتار میکند.
لنگرها (Anchors)
کاراکترهای caret (یا همان ^) و دلار ($)، کاراکترهایی هستند که در داخل عبارت منظم، بهعنوان یک لنگر رفتار میکنند. این بدان معنا است که این مطابقت فقط زمانی رخ میدهد که عبارت منظم، در ابتدای خط (^) و یا در آخر خط ($) پیدا شود:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[me@linuxbox ~]$ grep -h '^zip' dirlist*.txt zip zipcloak zipgrep zipinfo zipnote zipsplit [me@linuxbox ~]$ grep -h 'zip$' dirlist*.txt gunzip gzip funzip gpg-zip preunzip prezip unzip zip [me@linuxbox ~]$ grep -h '^zip$' dirlist*.txt zip |
در اینجا لیست فایلهایی را برای رشته zip که در ابتدای خط، انتهای خط و در هر دو موقعیت ابتدا و انتهای خط بهکار رفته است را جستجو نمودیم. به نتایج دقت کرده و موقعیت رشته zip را مشاهده کنید.
عبارات براکت و کلاسهای کاراکتر (Bracket Expressions and Character Classes)
علاوه بر مطابقت هر کاراکتری در یک موقعیت در عبارت منظم، میتوانیم کاراکتری را (با استفاده از عبارتهای براکت) از یک مجموعه کاراکتر تعیین شده، تطبیق دهیم. با عبارتهای براکت (Bracket Expressions) میتوانیم مجموعهای کاراکترها را (شامل کاراکترهایی که غیر از این بهعنوان متاکاراکترها تفسیر میشوند) برای تطبیق اختصاص دهیم. در این مثال با استفاده از یک مجموعه دو کاراکتری، هر خطی که حاوی رشته bzip یا gzip است را تطبیق میدهیم:
1 2 3 4 |
[me@linuxbox ~]$ grep -h '[bg]zip' dirlist*.txt bzip2 bzip2recover gzip |
یک مجموعه ممکن است حاوی تعدادی از کاراکترها و متاکاراکترها باشد که وقتی در داخل براکتها قرار میگیرند، معنی خود را از دست میدهند. هر چند دو مورد وجود دارد که در آنها متاکاراکترها درون عبارات براکت استفاده شدهاند و معنای متفاوتی دارند. اولین آنها کاراکتر (^) بوده که به منظور نشان دادن نفی به کار میرود و دومین آنها کاراکتر (-) بوده به منظور نشان دادن یک محدوده کاراکتری، مورد استفاده قرار میگیرد.
کاراکتر نفی (Negation)
اولین کاراکتر در یک عبارت براکت، کاراکتر (^) است و بقیه کاراکترها مجموعه کاراکترهایی هستند که نبایستی در موقعیت کاراکتری داده شده قرار بگیذند. این کار را با ویرایش قبلی خود انجام میدهیم:
1 2 3 4 5 6 7 8 9 10 |
[me@linuxbox ~]$ grep -h '[^bg]zip' dirlist*.txt bunzip2 gunzip funzip gpg-zip preunzip prezip prezip-bin unzip unzipsfx |
با فعال شدن نفی، لیستی از فایلهایی که حاوی رشته zip هستند را به جز آنهایی که دارای کاراکتر b یا g هستند را دریافت میکنیم. توجه داشته باشید که فایل zip پیدا نشد. مجموعه کاراکتر نفی شده هنوز نیازمند یک کاراکتر در موقعیت داده شده میباشد ولی کاراکتر نباید عضوی از مجموعه کاراکتر نفی شده باشد.
کاراکتر (^) فقط در صورتی نفی را فراخوانی میکند که اولین کاراکتر، در داخل یک عبارت براکت باشد. در غیر اینصورت مفهوم خاص خود را از دست میدهد و به یک کاراکتر معمولی در یک مجموعه تبدیل میشود.
محدودههای معمول کاراکتر (Traditional Character Ranges)
اگر که بخواهیم عبارت منظمی ایجاد کنیم که در آن هر فایلی که نام آن با یک حرف بزرگ شروع میشود را پیدا کند، میتوانیم بهصورت زیر عمل کنیم:
1 |
[me@linuxbox ~]$ grep -h '^[ABCDEFGHIJKLMNOPQRSTUVWXZY]' dirlist*.txt |
نیازی به این همه شلوغکاری نیست! کافی است که محدوده مورد نظر را با علامت (-) بهصورت زیر از هم جدا کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 |
[me@linuxbox ~]$ grep -h '^[A-Z]' dirlist*.txt MAKEDEV ControlPanel GET HEAD POST X X11 Xorg MAKEFLOPPIES NetworkManager NetworkManagerDispatcher |
با بهکار بردن محدوده سه کاراکتری، میتوانیم ۲۶ کاراکتر را خلاصه کنیم. هر محدوده از کاراکترها را میتوان به این شیوه بیان کرد، شامل محدودههایی مثل این عبارت که همه نامهای فایلی که با حروف و اعداد شروع میشوند را شامل میشود:
1 |
[me@linuxbox ~]$ grep -h '^[A-Za-z0-9]' dirlist*.txt |
در محدودههای کاراکتری، میبینیم که کاراکتر (-) به صورت ویژه رفتار میکند؛ حال چگونه یک کاراکتر (-) را در عبارت براکت قرار دهیم؟
به این صورت که آن را بهعنوان اولین کاراکتر در عبارت قرار میدهیم. مورد زیر را فرض کنید:
1 |
[me@linuxbox ~]$ grep -h '[A-Z]' dirlist*.txt |
این مورد هر نام فایلی که حاوی یک حرف بزرگ است را تطبیق میدهد ولی از سوی دیگر:
1 |
[me@linuxbox ~]$ grep -h '[-AZ]' dirlist*.txt |
این مورد هر نام فایلی که حاوی یک علامت (-)، یک حرف A (بهصورت بزرگ) و یک حرف Z (بهصورت کوچک) است را تطبیق میدهد.
منبع: لینوکسسیزن نوشته فرشید نوتاش حقیقت