کلاسهای کاراکتر POSIX

محدودههای معمول کاراکتر بهسادگی قابل درک هستند و برای حل سریع مشکلات، بهمنظور اختصاص مجموعهای از کاراکترها موثر هستند. متاسفانه آنها همیشه راهگشا نیستند. زمانی که قادر به استفاده از آنها نباشیم، چه کار باید کنیم؟
اگر نگاهی به دروس قبلی داشته باشیم، مشاهده خواهیم کرد که بهصورت گسترده از Wildcardها بهمنظور اجرای بسط نام مسیر (pathname) استفاده کردیم. در آن مباحث کفتیم که محدودههای کاراکتری را میتوان به شیوهای استفاده کرد که در عبارات منظم استفاده میکنیم، ولی مشکل اینجاست:
1 2 3 4 |
[me@linuxbox ~]$ ls /usr/sbin/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]* /usr/sbin/MAKEFLOPPIES /usr/sbin/NetworkManagerDispatcher /usr/sbin/NetworkManager |
بنا به توزیع لینوکسی که استفاده میکنید، لیست فایلهای متفاوتی را دریافت خواهید کرد و حت ممکن است یک لیست خالی را دریافت کنید. این مثال در توزیع اوبونتو اجرا شده است.
این فرمان نتایج دلخواه را ایجاد کرد (لیستی از فایلهایی که نامشان با حروف بزرگ آغاز میگردد). اما فرمان زیر نتایج کاملا متفاوتی را به ما نشان خواهد داد (فقط بخشی از نتایج نمایش داده شده است).
1 2 3 4 5 6 7 8 9 |
[me@linuxbox ~]$ ls /usr/sbin/[A-Z]* /usr/sbin/biosdecode /usr/sbin/chat /usr/sbin/chgpasswd /usr/sbin/chpasswd /usr/sbin/chroot /usr/sbin/cleanup-info /usr/sbin/complain /usr/sbin/console-kit-daemon |
زمانی که در ابتدا یونیکس توسعه یافت، فقط کاراکترهای ASCII را میشناخت و این ویژگی در اثزر همین شناخت پایین بهوجود آمده است. در کاراکترهای ASCII، ۳۲ کاراکتر اول، کدها را کنترل میکنند (کاراکترهایی مثل tabs، backspace، carriage return). کاراکترهای بعدی، یعنی شمارههای ۳۲ تا ۶۴ شامل کاراکترهای چاپی، بیشتر کاراکترهای نقطهگذاری و شمارههای صفر تا نه هستند. کاراکترهای بعدی هم، یعنی شمارههای ۶۴ تا ۹۵ شامل حروف بزرگ و برخی دیگر از نشانههای نقطهگذاری هستند. در نهایت بخش آخر، یعنی کاراکترهای ۹۶ تا ۱۲۷ شامل حروف کوچک و باز هم نشانههای دیگر نقطهگذاری هستند. براساس این ترتیب، سیستمهایی که از ASCII استفاده میکنند، از یک ترکیب تلفیقی به این شکل استفاده میکنند:
1 |
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
و همانطور که میدانید، این ترتیب با آنچه بهصورت عادی در ترتیب دیکشنری قرار دارد متفاوت است:
1 |
aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ |
همانطور که یونیکس گسترش یافت، نیاز به رشد، برای پشتیبانی از کاراکترهایی که در زبان انگلیسی وجود ندارند، ایجاد شد. جدول ASCII گسترش یافت تا از هشت بیت کامل استفاده کند و کاراکترهای شماره ۱۲۸ تا ۲۵۵ به آن اضافه شدند تا زبانهای زیاد دیگری را پشتیبانی کند. برای پشتیبانی از این ویژگی، استانداردهای POSIX مفهومی با نام locale را معرفی کردند. locale را میتوان تنظیم کرد تا مجموعهای از کاراکترهای ناحیهای خاص را انتخاب کند. تنظیمات زبان سیستم خود را میتوانیم با این فرمان مشاهده کنیم:
1 2 |
[me@linuxbox ~]$ echo $LANG en_US.UTF-8 |
با این تنظیم، اپلیکیشنهای سازگار با POSIX بهجای استفاده از ترتیب ASCII از یک ترتیب تلفیقی دیکشنری استفاده خواهند کرد. این مسئله، رفتار بالا را توضیح میدهد.
برای حل این مشکل، استاندارد POSIX تعدادی از کلاسهای کاراکتری را دارد که محدودههای مفید کاراکتری را فراهم میکند. جدول زیر این کلاسها را توصیف میکند:
کلاس کاراکتر | توضیحات |
---|---|
[:alnum:] | کاراکترهای عددی الفبایی، در ASCII معادل [A-Za-z0-9] |
[:word:] | درست مثل [:alnum:] به علاوه کاراکتر زیرخط یا همان Underline |
[:alpha:] | کاراکترهای الفبایی. در ASCII معادل [A-Za-z] |
[:blank:] | شامل کاراکترهای فاصله و تب |
[:cntrl:] | کدهای کنترل ASCII. شامل کاراکترهای 0 تا 31 و 127. |
[:digit:] | اعداد 0 تا 9 |
[:graph:] | کاراکترهای نمایان. در ASCII شامل 33 تا 126 میباشد. |
[:lower:] | کاراکترهای حروف کوچک |
[:punct:] | کاراکترهای نقطهگذاری. در ASCII معادل [-!"#$%&'()*+,./:;<=>?@[\\\]_`{|}~] |
[:print:] | کاراکترهای چاپشدنی. همه کاراکترهای موجود در [:graph:] به علاوه کاراکتر فاصله. |
[:space:] | کاراکترهای فضای خالی شامل فاصله، Carriage، Tab، return، خط جدید، تب عمودی و form feed در ASCII معادل [ \t\r\n\v\f] |
[:upper:] | کاراکترهای حروف بزرگ |
[:xdigit:] | کاراکترهایی که به منظور بیان اعداد هگزادسیمال استفاده میشود. در ASCII معادل [0-9A-Fa-f] |
حتی با وجود کلاسهای کاراکتری، هنوز هیچ راه مناسبی برای بیان محدودههای بخشی مانند [A-M] وجود ندارد.
با استفاده از کلاسهای کاراکتریف میتوانیم لیست دیکسنری خود را تکرار کنیم و نتایج بهبودیافتهای را دریافت کنیم:
1 2 3 4 |
[me@linuxbox ~]$ ls /usr/sbin/[[:upper:]]* /usr/sbin/MAKEFLOPPIES /usr/sbin/NetworkManagerDispatcher /usr/sbin/NetworkManager |
به یاد داشته باشید که هر چند این مثالی از یک عبارت منظم نیست، ولی در عوض بسط نام مسیر را انجام میدهد. از آنجایی که کلاسهای کارامتری POSIX برای هر دو نظور استفاده میشوند، این مثال را بیان کردیم.
POSIX پایه در برابر عبارات منظم توسعهیافته
POSIX اجرای عبارات منظم را به دو نوع تقسیم میکند: عبارات منظم ساده (BRE = basic regular expressions) و عبارت منظم توسعهیافته (ERE = extended regular expressions).
ویژگیهایی که ما تا اینجا پوشش دادیم، توسط هر اپلیکیشنی که با POSIX و اجراهای BRE سازگار باشد، پشتیبانی میشود. برنامه grep یکی از این اپلیکیشنها است. حال چه تفاوتی میان BRE و ERE وجود دارد؟
موضوع بر سر متاکاراکترها است. با BRE، متاکاراکترهای زیر تشخیص داده میشوند:
1 |
^ $ . [ ] * |
دیگر کاراکترها نیز بهعنوان لیترالها در نظر گرفته میشوند.
در حالیکه با ERE کاراکترهای زیر و توابع آنها اضافه میشوند:
1 |
( ) { } ? + | |
بخش جالب اینجاست که کاراکترهای () {} در صورتی که با بکاسلش (\) نادیده گرفته شوند، بهعنوان متاکارکترهای BRE رفتار میکنند. در حالی که در ERE نادیده گرفتن (Escaping) هر متاکاراکتری با بکاسلش (\) باعث میشود که بهعنوان لیترال رفتار کند.
از آنجایی که ویژگیهایی که ما در بخش بعدی درباره آنها صحبت میکنیم، بخشی از ERE هستند، نیاز به استفاده از نوع متفاوتی از grep داریم. بهصورت معمول این کار با برنامه egrep انجام میشود، ولی نسخه GNU فرمان grep نیز عبارات منظم توسعهیافته را بهوسیله اضافه کردن گزینه –E پشتیبانی میکند.
تناوب (Alternation)
از اولین ویژگیهای عبارات منظم توسعهیافته که درباره آن گفتگو خواهیم کرد، تناوب است. تناوب مهارتی است که اجازه میدهد تا تطبیقی از مجموعهای از عبارات اتفاق بیفتد. درست شبیه عبارت براکتی که اجازه میدهد یک کاراکتر از مجموعه کاراکترهای اختصاصیافته مطابقت یابد، تناوب نیز اجازه میدهد تا تطبیق از مجموعهای از رشتهها یا دیگر عبارات منظم صورت پذیرد. برای توضیح آن، grep را بههمراه echo استفاده خواهیم کرد. ابتدا یک تطبیق رشته ساده قدیمی را شرح میدهیم:
1 2 3 4 |
[me@linuxbox ~]$ echo "AAA" | grep AAA AAA [me@linuxbox ~]$ echo "BBB" | grep AAA [me@linuxbox ~]$ |
مثال واضحی که در آن خروجی echo را به داخل grep پایپ کردیم و نتیجه را میبینیم. زمانی که تطبیق صورت میپذیرد، نتیجه را در خروجی میبینیم، ولی زمانی که تطبیقی وجود ندارد، خروجی خالی است.
حال، تناوب را بهصورت زیر اضافه میکنیم:
1 2 3 4 5 6 |
[me@linuxbox ~]$ echo "AAA" | grep -E 'AAA|BBB' AAA [me@linuxbox ~]$ echo "BBB" | grep -E 'AAA|BBB' BBB [me@linuxbox ~]$ echo "CCC" | grep -E 'AAA|BBB' [me@linuxbox ~]$ |
این مثال واقعا نیازی به توضیح ندارد، ولی من توضیح میدهم. در اینجا عبارت منظم ‘AAA|BBB’ را مشاهده میکنیم که به معنای این است که یکی از دو رشته AAA و BBB را تطبیق بده. از آنجایی که فرمان grep را بهجای egrep استفاده میکنیم و این یک ویژگی توسعهیافته است، بایستی از گزینه –E استفاده نماییم.
علاوه بر این، عبارت منظم را درون کوتیشن قرار دادیم تا Shell را از تفسیر متاکاراکتر پایپ بهعنوان یک عملگر پایپ منع کنیم (در اینجا AAA|BBB پایپ بخشی از یک عبارت منظم است). تناوب، محدود به انتخاب نیست. میتوانیم موارد دیگری هم بهصورت زیر اضافه کنیم:
1 2 |
[me@linuxbox ~]$ echo "AAA" | grep -E 'AAA|BBB|CCC' AAA |
بهمنظور ترکیب تناوب با عناصر منظم، میتوانیم از () جهت جداسازی تناوب استفاده کنیم:
1 |
[me@linuxbox ~]$ grep -Eh '^(bz|gz|zip)' dirlist*.txt |
این عبارت، اسامی فایلهایی در لیستهای ما را که با یکی از موارد bz، gz و یا zip مطابقت دارند، تطبیق داده است. اگر که پرانتزها را حذف کنیم، معنای عبارت منظم به این صورت تغییر میکند که هر نام فایلی را تطبیق بده که با bz شروع شده و یا حاوی gz و یا حاوی zip میباشد. در حالی که ما اینگونه نمیخواهیم (تصویر زیر خلاصهای از نتایج است).
1 |
[me@linuxbox ~]$ grep -Eh '^bz|gz|zip' dirlist*.txt |