ویرایش بر روی هوا (Editing on the Fly)

مگر میشود بر روی هوا هم ویرایش کرد؟ تعجب نکنید. دقیقا منظورمان همین است. تجربه ما در کار با ویرایشگرهای متنی نشان داده که این ویرایشگرها بسیار تعاملی هستند؛ بدین معنا که میتوان مکاننما را بهصورت دستی جابهجا نموده و سپس تغییرات خود را در آن وارد کرد.
هرچند که چندین روش غیرتعاملی برای ویرایش متن نیز وجود دارد؛ بهعنوان مثال برای اعمال مجموعهای از تغییرات بر روی چندین فایل، میتوانیم بدون استفاده از ویرایشگرهای متنی و با یک فرمان و «بر روی هوا» انجام دهیم. در این درس به توضیح فرمان tr میپردازیم:
فرمان tr (بازنگاری یا حذف کاراکترها)
فرمان tr بهمنظور بازنگاری کاراکترها مورد استفاده قرار میگیرد. میتوانیم آن را بهعنوان مجموعهای از عملیات مبتنی بر کاراکتر و جستجو و جایگزینی در نظر بگیریم. بازنگاری به مثابه پروسه تغییر کاراکترها از یک حرف الفبا به دیگر میباشد. چنین تبدیلی را میتوان با فرمان tr بهصورت زیر انجام داد:
1 2 |
[me@linuxbox ~]$ echo "lowercase letters" | tr a-z A-Z LOWERCASE LETTERS |
همانگونه که مشاهده میکنید، فرمان tr بر روی ورودی استاندارد اعمال شده و نتایج خود را در خروجی استاندارد نشان میدهد. فرمان tr دو آرگومان را قبول میکند: یکی، مجموعهای از کاراکترها برای تبدیل و دیگری مجموعه کاراکتری که قرار است به آن تبدیل شود.
مجموعههای کاراکتری را میتوان به سه شیوه بیان کرد:
- یک لیست شمارش شده، بهعنوان مثال ABCDEFGHIJKLMNOPQRSTUVWXYZ.
- محدودهای از کاراکترها. بهعنوان مثال A-Z. به یاد داشته باشید که استفاده از این شیوه، گاهی اوقات موجب ایجاد مشکلاتی برای سایر فرمانها میشود (اشاره به ترتیب تطبیقی) در نتیجه بایستی با دقت استفاده شود.
- کلاسهای کاراکتری POSIX، بهعنوان مثال [:upper:].
در اکثر موارد، مجموعهای از کاراکترها، بایستی طولی برابر داشته باشند. هر چند که ممکن است مجموعه اول را با طولی بزرگتر نسبت به مجموعه دوم قرار داد؛ بهویژه اگر بخواهیم چندین کاراکتر را به یک کاراکتر تبدیل کنیم.
1 2 |
[me@linuxbox ~]$ echo "lowercase letters" | tr [:lower:] A AAAAAAAAA AAAAAAA |
فرمان tr علاوه بر بازنگری، به کاراکترها این اجازه را میدهد تا بهسادگی از جریان ورودی حذف شوند. بهعنوان مثال برای تبدیل فایلهای متنی MS-DOS به فایلهای متنی Unix-Style کاراکترهای return بایستی از پایان هر خط حذف شوند. این کار را میتوان بهصورت زیر و با استفاده از فرمان tr انجام داد:
1 |
tr -d '\r' < dos_file > unix_file |
در این فرمان، dos_file میبایست به فایل unix_file تبدیل شود. این شکل دستوری، از توالی عبور \r برای ارایه return استفاده میکند. برای لیست کاملی از توالیها و کلاسهای کاراکتری، فرمان زیر را وارد کنید:
1 |
[me@linuxbox ~]$ tr –help |
فرمان tr میتواند ترفند دیگری را نیز اجرا کند. فرمان tr، با استفاده از گزینه –s (سرنام واژه squeeze بهمعنای حذف) میتواند نمونههای تکرار شده از یک نمونه کاراکتر را حذف کند:
1 2 |
[me@linuxbox ~]$ echo "aaabbbccc" | tr -s ab abccc |
در اینجا رشتهای حاوی کاراکترهای تکراری داریم. درواقع با اختصاص مجموعه ab به فرمان tr، نمونههای تکراری را به مجموعه خود محدود میکنیم؛ در حالیکه کاراکتر c بدون تغییر باقی میماند؛ چرا که در مجموعه ما نیست.
به یاد داشته باشید که کاراکترهای تکراری میبایست در کنار هم باشند و اگر اینگونه نباشد، حذف صورت نخواهد پذیرفت:
1 2 |
[me@linuxbox ~]$ echo "abcabcabc" | tr -s ab abcabcabc |
فرمان sed (ویرایشگر جریان برای فیلتر کردن و انتقال متن)
نام فرمان sed از عبارت stream editor بهمعنای «ویرایشگر جریان» گرفته شده است. این فرمان، ویرایش متن را بر روی یک جریان متنی انجام میدهد که این جریان، میتواند مجموعهای از فایلها یا ورودی استاندارد باشد. فرمان sed برنامهای قوی و بهنحوی پیچیده است (که درباره آن، حتی میتوان یک کتاب نوشت). قاعدتا ما آن را بهصورت کامل پوشش نخواهیم داد.
بهطور کلی، شیوهای که فرمان sed با آن کار میکند، بدین شکل است که یک فرمان ویرایشی (بر روی خط فرمان) یا نام فایل اسکریپتی (که حاوی چندین فرمان است) را دریافت کرده و سپس فرمانها را بر روی هر خط در جریان متن اجرا میکند. این نمونه بسیار سادهای از فرمان sed میباشد:
1 2 |
[me@linuxbox ~]$ echo "front" | sed 's/front/back/' back |
در این مثال، یک جریان یککلمهای را با استفاده از فرمان echo ایجاد کردیم و سپس آن را به درون sed پایپ کردیم. فرمان sed در مقابل دستورالعمل s/front/back/ را بر روی متنی که در جریان وجود دارد اجرا نموده و خروجی back را ایجاد میکند. همچنین میتوانیم این فرمان را با استفاده از جستجو و جایگزینی vi نیز تشخیص دهیم.
فرمانهای sed با یک کاراکتر آغاز میگردند. در مثال بالا، فرمان جایگزینی با استفاده از کاراکتر s ارایه شده و سپس بهدنبال آن، رشتههای جستجو و جایگزینی مورد نظر که با استفاده از اسلش جدا شدهاند، آورده شده است. انتخاب کاراکتر جداکننده اختیاری است. بهعنوان یک قانون کلی، غالبا کاراکتر اسلش (/) بهکار برده میشود، ولی فرمان sed هر کاراکتر دیگری که بهعنوان جداکننده استفاده شده باشد را قبول میکند.
میتوان فرمان بالا را بهصورت زیر نیز اجرا کرد:
1 2 |
[me@linuxbox ~]$ echo "front" | sed 's_front_back_' back |
از آنجاییکه کاراکتر زیرخط (_) بلافاصله پس از فرمان s بهکار رفته است، بهعنوان جداکننده در نظر گرفته میشود.
فرمان بالا هیچ تفاوتی با قبلی نکرده، تنها کاراکتر جداکننده را تغییر دادیم. این توانایی تغییر کاراکتر جداکننده باعث میشود که فرمانها را خواناتر کنیم. اکثر فرمانها در sed با یک آدرس همراه میشوند که تعیین میکند چه خطی از جریان ورودی ویرایش شود. اگر که ادرس حذف شود، سپس فرمان ویرایش بر روی هر خط از جریان ورودی اعمال میشود. سادهترین شکل آدرس، یک شماره خط است.
ما میتوانیم ۱ را به مثال خود اضافه کنیم:
1 2 |
[me@linuxbox ~]$ echo "front" | sed '1s/front/back/' back |
با افزودن آدرس ۱ به فرمان خود، موجب میشویم که جانشینی بر روی اولین خط از جریان ورودی انجام شود. همچنین میتوانیم شماره دیگری اختصاص دهیم:
1 2 |
[me@linuxbox ~]$ echo "front" | sed '2s/front/back/' front |
اکنون میبینیم که ویرایش صورت نمیپذیرد، زیرا جریان ورودی دارای خط دومی نیست.
آدرسها را میتوان به شیوههای مختلفی بیان کرد، که جدول زیر رایجترین آنها را نشان میدهد:
آدرس | توضیحات |
---|---|
n | یک شماره خط جایی که n یک عدد صحیح مثبت است |
$ | خط آخر |
/regexp/ | خطوطی که با عبارت منظم اولیه POSIX مطابقت دارند. قابل ذکر است که عبارت منظم بوسیله کاراکترهای اسلش جدا شده است. به طور اختیاری، عبارت منظم ممکن است با یک کاراکتر جایگزین شده باشد. با تعیین عبارت \cregexpc که در آن c یک کاراکتر جایگزین است. |
addr1,addr2 | یک محدوده از خطوط از addr1 به addr2. آدرسها ممکن است از هر فرمی از آأرس باشد. |
first~step | مطابقت خط ارایه شده با شماره first و سپس هر خط بعدی در فواصل زمانی step. برای مثال 1~2 اشاره به هر شماره خط فرد 5~5 اشاره به خط پنجم و هر خط پنجم پس از آن دارد. |
addr1,+n | مطابقت addr1 و خطوط n به دنبال آن. |
addr! | مطابقت همه خطوط به جز addr، که ممکن است هر کدام از انواع بالا باشد. |
در ادامه انواع مختلف آدرسها را با استفاده از فایل distros.txt که در دروس قبلی ایجاد کردهایم، شرح خواهیم داد. ابتدا محدودهای از شمارههای خطوط:
1 2 3 4 5 6 |
[me@linuxbox ~]$ sed -n '1,5p' distros.txt SUSE 10.2 12/07/2006 Fedora 10 11/25/2008 SUSE 11.0 06/19/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007 |
در این مثال، محدودهای از خطوط را که از خط ۱ آغاز شده و به خط ۵ ختم میشود، چاپ کردیم. برای انجام این کار از فرمان p استفاده میکنیم که بهسادگی موجب میشود تا خطوط تطبیقیافته چاپ شوند. برای اینکه اینکار اثرگذار باشد، بایستی گزینه –n را نیز قرار دهیم. برای جلوگیری از چاپ خودکار که موجب میشود، فرمان sed هر خط را بهصورت پیشفرض چاپ نکند. سپس یک عبارت منظم را امتحان خواهیم کرد:
1 2 3 4 5 |
[me@linuxbox ~]$ sed -n '/SUSE/p' distros.txt SUSE 10.2 12/07/2006 SUSE 11.0 06/19/2008 SUSE 10.3 10/04/2007 SUSE 10.1 05/11/2006 |
با قرار دادن عبارت منظم با جداکننده اسلش (/SUSE/)ف قادر هستیم تا خطوط حاوی آن را به همان شیوه فرمان grep ایزوله کنیم. درنهایت با اضاف کردن کاراکتر علامت تعجب (!) به آدرس، نفی را امتحان میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[me@linuxbox ~]$ sed -n '/SUSE/!p' distros.txt Fedora 10 11/25/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007 Ubuntu 6.10 10/26/2006 Fedora 7 05/31/2007 Ubuntu 7.10 10/18/2007 Ubuntu 7.04 04/19/2007 Fedora 6 10/24/2006 Fedora 9 05/13/2008 Ubuntu 6.06 06/01/2006 Ubuntu 8.10 10/30/2008 Fedora 5 03/20/2006 |
همانگونه که مشاهده میکنید، همه موارد، غیر از مواردی که با عبات منظم مطابقت دارند، آورده شدهاند.
تاکنون به دو فرمان ویرایشی sed (یعنی s و p) پرداختیم. جدول زیر لیست کاملتری از فرمانهای ویرایشی اساسی را نشان میدهد:
فرمان | شرح |
---|---|
= | خروجی شماره خط فعلی |
a | اضافه کردن متن پس از خط فعلی |
d | حذف خط فعلی |
i | درج متن در جلو خط فعلی |
p | چاپ خط فعلی. به صورت پیشفرض sed هر خط را چاپ میکند و فقط خطوطی را ویرایش میکند که با یک آدرس تعیین شده درون فایل مطابقت دارند. رفتار پیشفرض میتواند با اختصاص گزینه -n لغو گردد. |
q | خروج از sed بدون پردازش خطوطی بیشتر. اگر گزینه -n تعیین نشده باشد، خط فعلی را خروجی میدهد. |
Q | خروج sed بدون پردازش هیچ خط دیگری |
s/regexp/replacement/ | تعویض محتویات replacement هر کجا که regexp یافت شد. replacement ممکن است شامل کاراکتر ویژه & باشد، که معادل متن منطبق با regexp است. بعلاوه replacement ممکن است حاوی توالیهای \1 تا \9 که محتویات مربوط در regexp است. |
y/set1/set2 | انجام آوانگاری و ترجمه بوسیله تبدیل کاراکترها از set1 به کاراکترهای مربوطه در set2. لازم به ذکر است که بر خلاف tr ، sed نیاز دارد که هر دو مجموعهها یا یک طول مشخص باشند. |
فرمان s تاکنون رایجترین فرمان ویرایش بوده که به شرح برخی از تواناییهای این فرمان را با اجرای ویرایش بر روی فایل distros.txt خواهیم پرداخت. قبلا اشاره شد که چگونه فیلد تاریخ در فایل distros.txt دارای فرمت مناسبی برای سیستمهای کامپیوتری نیست.
در حالیکه فرمت ما در فایل distros.txtبهصورت MM/DD/YYYY میباشد، بهمنظور آسان شدن مرتبسازی بهتر است که فرمت YYYY-MM-DD را داشته باشیم. بهمنظور اعمال این تغییر بر روی فایل (بهطور دستی) زمان زیادی را باید صرف ویرایش فایل کنیم، ولی با استفاده از فرمان sed این تغییر در یک گام انجام خواهد شد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[me@linuxbox ~]$ sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1 -\2/' distros.txt SUSE 10.2 2006-12-07 Fedora 10 2008-11-25 SUSE 11.0 2008-06-19 Ubuntu 8.04 2008-04-24 Fedora 8 2007-11-08 SUSE 10.3 2007-10-04 Ubuntu 6.10 2006-10-26 Fedora 7 2007-05-31 Ubuntu 7.10 2007-10-18 Ubuntu 7.04 2007-04-19 SUSE 10.1 2006-05-11 Fedora 6 2006-10-24 Fedora 9 2008-05-13 Ubuntu 6.06 2006-06-01 Ubuntu 8.10 2008-10-30 Fedora 5 2006-03-20 |
عجب فرمان پیچیده و زشتی! ولی به هر حال کاری که خواستیم انجام شد. بهوسیله یک فرمان زشت و فقط در یک گام (بدون نیاز به ویرایش دستی فایل) فرمت تاریخ رو در فایل distros.txt تغییر دادیم. به همین دلیل است که گاهی به طنز گفته میشود که عبارات منظم، عباراتی فقط نوشتنی هستند. بدین معنی که فقط آن را یکبار مینویسیم ولی خواندن آن، دیگر با خودتان است. پیش از آنکه ببینیم این فرمان چگونه نوشته شده است، نگاهی به ساختار کلی فرمان خواهیم انداخت. اول اینکه میدانیم که ساختار کلی زیر را دارد:
1 |
sed 's/regexp/replacement/' distros.txt |
گام بعدی این است که عبارت منظمی را بفهمیم که میتواند تاریخ را جدا کرده و دربربگیرد. از آنجاییکه تاریخ در فرمت MM/DD/YYYY میباشد و در آخر خط مشخص شده، میتوانیم از یک عبارت به شکل زیر استفاده کنیم:
1 |
[0-9]{2}/[0-9]{2}/[0-9]{4}$ |
که این عبارت، به ترتیب دو رقم، یک اسلش (/)، دو رقم، یک اسلش (/)، چهار رقم و در آخر هم علامت $ پایان خط را نمایش میدهد. پس این عبارت طولانی را در جایگاه regexp قرار میدهیم. حال، replacement یا همان عبارت جایگزین چه میشود؟
برای آنکه آن را فراهم کنیم، ابتدا بایستی با یک ویژگی جدید در عبارت منظم آشنا شوید که در برخی از اپلیکیشنهایی که از BRE استفاده میکنند، نمایان میشود. این ویژگی را book reference (بهمعنای مرجع کتاب) مینامند و به اینصورت کار میکند که اگر توالی \n در جایگاه replacement پدیدار شود (که در آن n رقمی مابین صفر تا ۹ است) توالی به زیرعبارت متناظر در عبارت منظم قبلی اشاره میکند. برای ایجاد این زیرعبارت، بهسادگی آنها را با پرانتزهایی محصور میکنیم:
1 |
([0\9]{2})/([0-9]{2})/([0-9]{4})$ |
اکنون سه زیرعبارت داریم. اولی شامل ماه، دومی شامل روز و سومی شامل سال است. حال، میتوانیم جایگزین (replacement) را بهصورت زیر بنا کنیم:
1 |
\3-\1-\2 |
که به ترتیب به ما سال، یک کاراکتر دش (-)، ماه، یک کاراکتر دش (-) و روز را میدهد. اکنون فرمان به این صورت میباشد:
1 |
sed 's/([0-9]{2})/([0-9]{2})/([0-9]{4})$/\3-\1-\2/' distros.txt |
دو مشکل حل نشده داریم. یکی اسلشهای اضافی در عبارت منظم فرمان sed را بههنگام تفسیر فرمان s گیج میکند. دوم اینکه از آنجاییکه فرمان sed بهصورت پیشفرض فقط عبارات منظم پایه را قبول میکند، برخی از کاراکترها در عبارت منظم ما بهجای متاکاراکتر بهصورت لیترال رفتار خواهند کرد. میتوانیم این مشکلات را با چندین بکاسلش، بهمنظور نادیده گرفتن کاراکترهای مختلف حل کنیم:
1 |
sed 's/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/' distros.txt |
ویژگی دیگر فرمان s، استفاده از پرچمهای (flags) اختیاری است که ممکن است بهدنبال رشته جایگزین یابند. مهمترین این پرچمها، پرچم g بوده که به sed فرمان میدهد که جستجو و جایگزینی را بر روی یک خط بهصورت سراسری انجام دهد؛ نه فقط بر روی نمونه اول که پیشفرض است.
این هم یک مثال:
1 2 |
[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/' aaaBbbccc |
مشاهده میشود که جایگزینی صورت گرفت ولی فقط بر روی اولین نمونه از حرف b، درحالیکه سایر نمونهها بهصورت دستنخورده باقی ماندند. با اضافه کردن پرچم g، قادر خواهیم بود که تغییر را بر روی همه نمونهها انجام دهیم:
1 2 |
[me@linuxbox ~]$ echo "aaabbbccc" | sed 's/b/B/g' aaaBBBccc |
تاکنون فقط یک فرمان به sed از طریق خط فرمان دادیم. علاوه بر این، میتوانیم فرمانهای پیچیدهتری را در یک فایل اسکریپت با استفاده از گزینه –f قرار بدهیم. بهمنظور شرح آن، از فرمان sed به همراه فایل distros.txt برای ایجاد یک گزارش استفاده میکنیم.
در این گزارش کلیه نامهای توزیعها به حروف بزرگ تبدیل میشوند. برای انجام این کار، بایستی یک اسکریپت بنویسیم، بههمین منظور ویرایشگر متن را گشوده و کد زیر را در فایل اسکریپت واردکرده، Ctrl+X، سپس Y و در نهایت Enter را فشار داده تا فایل ذخیره گردد. اکنون یک فایل اسکریپت با نام distros.sed ایجاد کردیم:
1 2 3 4 5 6 |
# sed script to produce Linux distributions report 1 i\ \ Linux Distributions Report\ s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/ y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ |
در ادامه، بهوسیله فرمان زیر، اسکریپت خود را درون sed اجرا میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[me@linuxbox ~]$ sed -f distros.sed distros.txt Linux Distributions Report SUSE 10.2 2006-12-07 FEDORA 10 2008-11-25 SUSE 11.0 2008-06-19 UBUNTU 8.04 2008-04-24 FEDORA 8 2007-11-08 SUSE 10.3 2007-10-04 UBUNTU 6.10 2006-10-26 FEDORA 7 2007-05-31 UBUNTU 7.10 2007-10-18 UBUNTU 7.04 2007-04-19 SUSE 10.1 2006-05-11 FEDORA 6 2006-10-24 FEDORA 9 2008-05-13 UBUNTU 6.06 2006-06-01 UBUNTU 8.10 2008-10-30 FEDORA 5 2006-03-20 |
همانطور که میبینیم، اسکریپت مربوطه، نتیج دلخواه را ایجاد میکند، ولی چگونه این کار را میکند. بیایید به اسکریپتی که ساختیم بار دیگر، نگاهی کنیم. این کار با استفاده از فرمان cat بهصورت زیر انجام میشود:
1 2 3 4 5 6 7 8 9 |
[me@linuxbox ~]$ cat -n distros.sed 1 # sed script to produce Linux distributions report 2 3 1 i\ 4 \ 5 Linux Distributions Report\ 6 7 s/\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3-\1-\2/ 8 y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ |
خط اول، کامنت (Comment) بوده و توضیحی را در رابطه با فرمان میدهد. خط دوم خالی است. خطوط سوم تا ششم، حاوی متنی است که بایستی درون آدرس ۱ اولین خط ورودی درج شود. فرمان i و بهدنبال آن توالی بکاسلش (\). خط هفتم فرمان جستجو و جایگزینی ما بوده و درنهایت خط هشتم بازنگاری را بر روی حروف بزرگ انجام میدهد.
منبع: لینوکسسیزن نوشته فرشید نوتاش حقیقت