اجرای ترتیبی فرمانها به جای خود، اما برای دستیابی به منطق پیشرفته در اسکریپتهایتان یا در خط فرمان یک جملهای، به شرطها و بررسیها نیاز دارید. بررسیها تعیین میکنند که یک مطلبی صحیح است یا غلط. شرطها برای تصمیمسازی در مورد انجام فرامینی در اسکریپت به کار میروند.
از هر دستور موقعیکه خاتمه مییابد یک کد خروج حاصل میشود. این کد خروج توسط هر برنامهای که آن دستور را اجرا نموده برای تعیین آنکه مقصودش به درستی انجام شده یا نه استفاده میشود. این کد خروج مشابه مقدار برگشتی از توابع میباشد. این کد یک عدد صحیح از صفر تا ۲۵۵ میباشد. مطابق قرارداد از صفر برای مشخص نمودن موفقیت استفاده میکنیم، وهر عدد دیگر بیانگر نوعی شکست میباشد. هر برنامه معینی، عدد خاصی را برای اشاره به آنکه دقیقاً چه اشتباهی رخ داده به کار میبرد.
به عنوان مثال، دستور ping بستههای ICMP را در شبکه برای یک میزبان معین ارسال میکند. به طور معمول آن میزبان، با برگشت دادن دقیق همان بسته پاسخ میدهد. به این طریق میتوانیم کنترل کنیم که آیا میتوانیم یک ارتباط با میزبان راه دور برقرار کنیم. دستورping دامنهای از کدهای خروج دارد که اگر مشکلی باشد، میتواند به ما بگوید، چه چیز نادرست است:
از مستندات ping لینوکس:
اگر ping هیچ بسته بازگشتی دریافت نکند، با کد 1 خارج خواهد شد. اگر یک شماره بسته و یک محدوده زمانی تعیین شده باشد، و شمارش بستههای دریافتی در زمان تعیین شده با عدد کمتری اعلام شود نیز با کد 1 خارج میشود. در سایر موارد خطا با کد 2 خارج میشود. در غیر اینصورت با کد صفر خارج میشود. و استفاده از کد خروج امکان آن را فراهم میکند که ببینیم میزبان فعال میباشد یا خیر.
پارامتر ویژه
$ ping Godping: unknown host God $ echo$? 2 $ ping-c 1 -W 1 1.1.1.1PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. --- 1.1.1.1 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 0ms $ echo$? 1
همواره باید مطمئن شوید که اسکریپت شما در صورت وقوع رخداد ناخواسته در جریان اجرایش، کد خروج غیر صفر برمیگرداند. میتوانید با استفاده از دستور داخلی exit این کار را عملی کنید:
rm file|| { echo 'Could not delete file!'>&2; exit 1; }
در مستندات گنو: Exit Status
کد خروج / وضعیت خروج: هنگامی که یک دستور خاتمه مییابد به والدش( در موقعیت ما همیشه پوستهای میشود که شروع کردهایم) ، وضعیت خروج خود را گزارش میکند. این وضعیت با یک عدد از صفر تا ۲۵۵ نمایانده میشود. این کد اشارهای به موفقیت اجرای دستور است.
حال که میدانیم کدهای خروج چیستند، و یک کد خروج صفر به معنای اجرای موفق یک دستور میباشد، استفاده از این اطلاعات را خواهیم آموخت. سادهترین روش انجام یک عمل معین بر اساس موفقیت دستور قبلی از راه به کارگیری عملگرهای کنترلی میباشد. این عملگرها
بیایید این مطلب را در عمل به کار ببریم:
$ mkdir d&& cd d
این مثال ساده دو دستور دارد، mkdir d و cd d. میتوانستید از یک سمیمالن در آنجا برای جدا کردن دستورهاو اجرای ترتیبی آنها استفاده کنید، اما ما چیزی بیش از آن میخواهیم. در مثال فوق، BASH فرمان mkdir d را اجرا میکند، سپس
مثالی دیگر:
$ rm /etc/some_file.conf|| echo "I couldn't remove the file"rm: cannot remove `/etc/some_file.conf': No such file or directory I couldn't remove the file
به طور کلی، متصل کردن چند دستور کنترلی در یک جمله منفرد ایده خوبی نیست(ما این مطلب را در بخش بعدی باز خواهیم کرد).
وقتی با عبارتهای شرطی سر و کار دارید خیلی هواخواه این عملگرها نباشید. اینها میتوانند درک اسکریپت شما را دشوار سازند، به ویژه برای کسی که به نگهداری آن منصوب شده و خودش اسکریپت را ننوشته است.
در مستندات گنو: Lists of Commands
عملگرهای کنترل: این عملگرها برای پیوند زدن دستورها با یکدیگر استفاده میشوند. آنها کد خروج دستور قبلی را برای تعیین اجرا یا عدم اجرای دستور بعدی بررسی میکنند.
استفاده از عملگرهای شرطی ساده و موجز میباشد، به شرطی که بخواهیم کنترل خطای سادهای انجام دهیم. گرچه، موقعی که بخواهیم در صورت صحیح بودن یک شرط، جملات چندگانهای را اجرا کنیم، یا نیاز به بررسی شرطهای چندگانه داشته باشیم، مسائل قدری خطرناکتر میشوند.
فرض کنید میخواهید یک فایل را در صورت وجود کلمه معین "good" در آن و نیز عدم وجود کلمه مشخص "bad" در آن حذف کنید. با استفاده از grep ( فرمانی که ورودیاش را برای الگوهای تعیین شده بررسی میکند)، این شرایط را به این صورت ترجمه میکنیم:
grep | exit status 0 (success) if "$file" contains 'goodword' |
| exit status 0 (success) if "$file" does not contain 'badword' |
ما از گزینه
علامت
حال برای متصل کردن این شرطها به یکدیگر و ربط دادن حذف فایل به موفقیت هر دو، میتوانستیم از عملگرهای شرطی استفاده کنیم:
$ grep-q goodword "$ file "&& ! grep-q badword "$ file "&& rm "$ file "
این بخوبی کار میکند( در حقیقت میتوانیم هر تعداد از
$ grep-q goodword "$ file "&& ! grep-q badword "$ file"&& rm "$ file "|| echo "Couldn't delete:$ file ">& 2
این هم ظاهراً در نگاه اول صحیح است. اگر کد خروج rm برابر
اما مشکلی وجود دارد. موقعی که ما یک توالی از دستوراتی که با
همچنین تصور کنید اولین grep ناموفق است(کد وضعیت یک میشود) . Bash حالا
وقتی فقط پیغام خطای اشتباهی دریافت میکنید انعکاس خیلی بدی ندارد، اما اگر دقیق نباشید، سرانجام در کدهای خطرناکتر، این اتفاق خواهد افتاد. شما که نمیخواهید به طور تصادفی در اثر نارسایی منطق برنامه خود فایلی را حذف یا رونویسی نمایید!
نقص منطق ما در این واقعیت است که ما میخواهیم فرمانهای rm و echo وابسته به یکدیگر باشند. دستور echo مربوط به rm میباشد، نه مربوط به grepها. بنابراین آنچه ما لازم داریم، گروهبندی آنها است. گروهبندی با استفاده از ابروها انجام میگردد:
$ grep-q goodword "$ file "&& ! grep-q badword "$ file "&& { rm "$ file "|| echo "Couldn't delete:$ file ">& 2; }
(
حالا دستورات rm و echo را با هم گروهبندی نمودهایم. این به طورمؤثر و کارامدی به معنای آنست که گروه به عنوان یک جمله در نظر گرفته میشود، نه چند دستور. برگردیم به موقعیتی که اولین دستور grep ما ناموفق بود، حالا BASH به جای اینکه به جمله
گروهبندی دستورات برای موارد بیشتری غیر از عملگرهای شرطی نیز میتواند به کار رود. ممکن است بخواهیم دستورات را گروهبندی کنیم تا یک ورودی را به این گروه تغییر مسیر بدهیم، نه فقط به یکی از دستورات:
{ read firstLine read secondLinewhile read otherLine;do somethingdone } < file
در اینجا ما
یک مورد استفاده رایج دیگر از گروهبندی، مدیریت خطای ساده است:
cd "$ appdir "|| { echo "Please create the appdir and try again">& 2; exit 1; }
$if true >then echo "It was true." >else echo "It was false." >fi It was true.
در اینجا یک نمای کلی اساسی از
افراد مختلف شیوههای متفاوتی از نوشتن جملات
if commandsthen other commandsfi -------------------if commandsthen other commandsfi -------------------if commands; then other commandsfi
چند دستور وجود دارد که به طور ویژه برای بررسی موارد و بازگرداندن وضعیت خروج نسبت به آنچه تشخیص میدهند، طراحی گردیدهاند. اولین دستور از این قبیل test میباشد( که [ نیز شناخته میشود.) یک نگارش پیشرفتهتر آن
$if [ a= b ] >then echo "a is the same as b." >else echo "a is not the same as b." >fi a is not the same as b.
حال ببینیم که چرا
$myname = 'Greg Wooledge' yourname= 'Someone Else' $ [$ myname = $ yourname ]-bash: [: too many arguments
میتوانید حدس بزنید چه مشکلی موجب بروز خطا شده؟
دستور [ با شناسههای
$ ["$ myname " = "$ yourname " ]
در این حالت [ دومین شناسه را یک عملگر(
برای کمی مساعدت با ما، پوسته Korn یک سبک جدید بررسی شرطی را معرفی نموده(و BASH نیز آن را اخذ کرده). مؤلف اصل اینها که
یکی از ویژگیهای
$[[ $ filename = * .png]] && echo "$ filename looks like a PNG file"
ویژگی دیگر
$[[ $ me = $ you ]] # Fine. $[[ I am$ me = I am$ you ]] # Not fine!-bash: conditional binary operator expected -bash: syntax error near `am'
در این حالت، نیازی به نقلقولی کردن
$[[ "I am$ me "= "I am$ you "]]
همچنین، تفاوت ظریف زیرکانهای بین نقلقولی کردن و نکردن سمت راست مقایسه در
$foo = [a-z]*name = lhunath $[[ $ name = $ foo ]] && echo "Name$ name matches pattern$ foo "Name lhunath matches pattern [a-z]* $[[ $ name = "$ foo "]] || echo "Name$ name is not equal to the string$ foo "Name lhunath is not equal to the string [a-z]*
بررسی اول کنترل میکند که آیا
یادآوری: اگر اطمینان ندارید، همواره نقلقولی کنید. اگر
میتوانید چندین دستور
$name = lhunath $if [[ $ name = "George"]] >then echo "Bonjour,$ name " >elif [[ $ name = "Hans"]] >then echo "Goeie dag,$ name " >elif [[ $ name = "Jack"]] >then echo "Good day,$ name " >else > echo "You're not George, Hans or Jack. Who the hell are you,$ name ?" >fi
حال که درک مناسبی از مسائلی که با نقلقولها ممکن است ایجاد شود به دست آوردهاید، بیایید به سایر ویژگیهایی که [ و
بررسیهایی که با [ ( که به عنوان test نیزشناخته میشود) پشتیبانی میشود:
-e FILE: اگر فایل موجود باشد صحیح است.
-f FILE: اگر فایل موجود معمولی باشد صحیح است.
-d FILE: اگر فایل یک دایرکتوری باشد صحیح است.
-h FILE: اگر فایل یک پیوند نمادین باشدصحیح است.
-r FILE: اگر فایل برای شما قابل خواندن باشد صحیح است.
-s FILE: اگر فایل موجود باشد وتهی نباشد صحیح است.
-t FD : اگر FD(توصیفگر فایل) در یک ترمینال باز شده باشد صحیح است.
-w FILE: اگر فایل برای شما قابل نوشتن باشد صحیح است.
-x FILE: اگر فایل برای شما قابل اجرا باشد صحیح است.
-O FILE: اگر فایل به طور مؤثر در مالکیت شما باشد صحیح است.
-G FILE: اگر فایل به طور مؤثر در مالکیت گروه شما باشد صحیح است.
FILE -nt FILE: اگر فایل اول جدیدتر از فایل دوم باشد صحیح است.
FILE -ot FILE: اگر فایل اول قدیمیتر از فایل دوم باشد صحیح است.
-z STRING: اگر رشته تهی باشد(طول آن صفر باشد) صحیح است.
-n STRING: اگر رشته تهی نباشد(طول آن صفر نباشد) صحیح است.
STRING = STRING: اگر رشته اول از هر نظر مانند دومی باشد صحیح است.
STRING != STRING: اگر رشته اول دقیقاً مانند رشته دوم نباشد صحیح است.
STRING < STRING:اگر در مرتبسازی رشته اول قبل از دومی قرار میگیرد صحیح است.
STRING > STRING: اگر رشته اول در مرتبسازی بعد از رشته دوم قرارمیگیرد صحیح است.
EXPR -a EXPR: اگر هر دوعبارت صحیح باشندصحیح است(and منطقی).
EXPR -o EXPR: اگر هر یک از دو عبارت صحیح باشد صحیح است(or منطقی).
! EXPR: نتیجه عبارت را معکوس میکند( NOTمنطقی).
INT -eq INT: اگر هر دو عدد صحیح دقیقاً برابر باشند صحیح است.
INT -ne INT: اگر هر دو عدد صحیح دقیقاً برابر نباشند، صحیح است.
INT -lt INT: اگر عدد صحیح اولی کوچکتر از دومی باشد صحیح است.
INT -gt INT: اگر عدد صحیح اولی از دومی بزرگتر باشد صحیح است.
INT -le INT: اگر عدد صحیح اولی کوچکتر یا مساوی دومی باشد صحیح است.
INT -ge INT: اگر عدد صحیح اولی بزرگتر یا مساوی دومی باشد صحیح است.
بررسیهای اضافی که فقط توسط
STRING = (or ==) PATTERN: مانند [ (یا test) مقایسه نمیکند، بلکه انطباق الگو انجام میشود. اگر رشته با الگوی جانشین منطبق گردد، صحیح است.
STRING =~ REGEX: اگر رشته با الگوی regex(عبارت منظم)تطبیق کند، صحیح است.
( EXPR ): پرانتزها میتوانند برای تغییر اولویت ارزیابیها به کار بروند.
EXPR && EXPR: خیلی مشابه عملگر
EXPR || EXPR: خیلی مشابه عملگر
چند مثال؟ حتماً:
$ test-e /etc/X11/xorg.conf&& echo 'Your Xorg is configured!'Your Xorg is configured! $ test-n "$ HOME "&& echo 'Your homedir is set!'Your homedir is set! $[[ boar!= bear]] && echo "Boars aren't bears."Boars aren't bears! $[[ boar!= b? ar]] && echo "Boars don't look like bears." $ $[[ $ DISPLAY ]] && echo "Your DISPLAY variable is not empty, you probably have Xorg running."Your DISPLAY variable is not empty, you probably have Xorg running. $[[ ! $ DISPLAY ]] && echo "Your DISPLAY variable is not not empty, you probably don't have Xorg running." $
تکرارمفید:
هنگامی که یک اسکریپت BASH ایجاد میکنید، همیشه باید از
وقتی یک اسکریپت پوسته مینویسید، که پس از اتمام ممکن است در محیطی که BASH در دسترس نباشد، به کار برود، باید از [ استفاده کنید، به دلیل آنکه به مراتب قابل حملتر میباشد. ( در حالیکه در BASH و برخی پوستههای دیگر، [ یک دستور داخلی است، به صورت یک برنامه خارجی نیز به خوبی در دسترس میباشد، یعنی به عنوان شناسه مثلاً exec و xargs کار خواهد کرد.)
هرگز از
if [ "$ food "= apple ]&& [ "$ drink "= tea ]; then echo "The meal is acceptable."fi
در پرسش و پاسخهای رایج:
چگونه میتوانم عبارتها را گروهبندی کنم، مثل (a
چطور میتوانم تعیین نمایم که آیا یک متغیر شامل یک زیر رشته هست؟
چگونه میتوانم بگویم که یک متغیر آیا محتوی یک عدد معتبر هست؟
تا اینجا آموختهاید چگونه برخی تصمیمگیری های اساسی در اسکریپتهایتان را بسازید. اگر چه، برای انجام همه انواع وظایفی که ممکن است از اسکریپت بخواهیم کافی نمیباشد. گاهی اوقات نیاز به تکرار برخی کارها داریم. برای همین، کاربرد یک حلقه لازم است. دو نوع اصلی ازحلقه( به اضافه نوع دیگری) وجود دارد، و استفاده از نوع صحیح حلقه به شما در نگهداری خوانایی و قابلیت پشتیبانی اسکریپتهایتان کمک میکند.
دو نوع اساسی حلقهها
هر شکل از حلقهها با کلمهکلیدی
در عمل، حلقهها برای انواع مختلفی از وظایف به کار میروند. حلقه
در اینجا چند مثال برای تشریح تفاوتها و همچنین شباهتهای حلقهها میآوریم. (یادآوری: در اکثر سیستمعاملها، برای کشتن برنامهای که در ترمینال در حال اجرا است از ترکیب کلیدی
$while true >do echo "Infinite loop" >done
$while ! ping-c 1 -W 1 1.1.1.1; do > echo "still waiting for 1.1.1.1" > sleep 1 >done
$(( i =10 ));while (( >i >0 ))do echo "$ i empty cans of beer." >(( >i -- ))done $for (( >i =10 ;i >0 ;i -- ))do echo "$ i empty cans of beer." >done $for i in { 10..1 } >do echo "$ i empty cans of beer." >done
سه حلقه آخری با ترکیب متفاوت، دقیقاً به نتیجه یکسانی میرسند. در تجربه اسکریپتنویسی شل خود بارها با این مورد مواجه میشوید. تقریباً همیشه راهکارهای چندگانهای برای حل یک مسئله موجود است. به زودی تشحیص مهارت شما در حل مسئله نخواهد بود، آنقدر که درچگونگی
بیایید نگاه نزدیکتری به آن مثال آخری داشته باشیم، زیرا اگر چه از دو حلقه
به طوری که قبلاً اشاره کردهام:
$for i in 10 9 8 7 6 5 4 3 2 1 >do echo "$ i empty cans of beer." >done
BASH کاراکترهای بین کلمهکلیدی
در نتیجه،
$ lsThe best song in the world.mp3 $for file in $ (ls* .mp3 ) >do rm "$file" >done rm: cannot remove `The': No such file or directory rm: cannot remove `best': No such file or directory rm: cannot remove `song': No such file or directory rm: cannot remove `in': No such file or directory rm: cannot remove `the': No such file or directory rm: cannot remove `world.mp3': No such file or directory
شما از قبل نسبت به نقلقولی کردن
خواهید گفت، آن را نقلقولی میکنم؟ اجازه دهید فایل دیگری اضافه کنم:
$ lsThe best song in the world.mp3 The worst song in the world.mp3 $for file in "$(ls * .mp3)" >do rm "$ file" >done rm: cannot remove `The best song in the world.mp3 The worst song in the world.mp3': No such file or directory
نقلقولها به راستی از فضای سفید در نام فایلهای شما محافظت میکنند، اما چیزی بیش از آن انجام میدهند. نقلقولها از تمام فضاهای سفید خروجی فرمان ls محافظت خواهند کرد. راهی وجود ندارد که BASH بتواند تشخیص بدهد کدام بخشهای خروجی فرمان ls نام فایلها را نمایندگی میکنند. خروجی فرمان ls یک رشته ساده است، و BASH با آن به همین عنوان رفتار میکند. بعد
بنابراین چه کار بکنیم؟ به طوری که قبلاً پیشنهاد نمودم، جانشینها بهترین دوست شما هستند:
$for file in * .mp3 >do rm "$ file" >done
حالا، BASH میداند که با نام فایلها سروکار دارد، و نام فایها را میشناسد، و بنابراین به طور مطلوبی آنها راتفکیک میکند. نتیجه بسط جابشین چنین است:
حالا بیایید به حلقه
$ # ماشین نوشیدنی، نوشیدنیها را در ازای بهای ۲۰ سنت تحویل میدهد $while read-p $ 'The sweet machine.\n Insert 20c and enter your name: 'name >do echo "The machine spits out three lollipops at$ name ." >done
$ # هر پنج دقیقه یکبار ایمیل شما را بررسی میکند $while sleep 300 >do kmail--check >done
$ # برای برخط(آنلاین) شدن مجدد میزبان منتظر میماند $while ! ping-c 1 -W 1 "$ host " >do echo "$ host is still unavailable." >done ; echo-e "$ host is available again.\a"
حلقه
$ # برای برگشت میزبان به حالت آماده برقراری ارتباط منتظر میماند $until ping-c 1 -W 1 "$ host " >do echo "$ host is still unavailable." >done ; echo-e "$ host is available again.\a"
در عمل، اکثر مردم حقیقتاً از حلقه
بالإخره، از دستور داخلی continue برای پرش به جلو بدون اجرای بقیه بدنه و اجرای دور بعدی تکرار حلقه، و دستور داخلی break برای پریدن به خارج از حلقه و ادامه دستورات پس از حلقه در اسکریپت، میتوانید استفاده کنید. این دستورات با هر دو حلقه
در مستندات گنو: Looping Constructs
در پرسش و پاسخهای رایج:
چگونه میتوانم یک دستور را با تمام فایلهای دارای پسوند .gz اجرا کنم؟
چگونه میتوانم از اعدادی که با صفر شروع میشوند مثل 01 و 02 در یک حلقه استفاده کنم؟
چطو میتوانم نام فایلهای شامل کاراکتر فاصله یا سطر جدید یا هر دو را پیدا کرده وبا آنها کار کنم؟
میخواهم بررسی کنم که آیایک کلمه در یک لیست وجود دارد( یا یک عنصر عضوی از یک مجموعه هست).
حلقه: یک حلقه ساختاری است که برای تکرار کد تا موقع تحقق یک وضعیت معین، طراحی میشود. در آن نقطه حلقه متوقف میشود و کد بعد از آن اجرا میگردد.
گاهی اوقات میخواهید برنامهای منطقی بر مبنای محتوای یک متغیر بسازید. این میتوانست با گرفتن انشعابهای مختلف یک جمله
shopt-s extglobif [[ $ LANG = en*]] ;then echo 'Hello!'elif [[ $ LANG = fr*]] ;then echo 'Salut!'elif [[ $ LANG = de*]] ;then echo 'Guten Tag!'elif [[ $ LANG = nl*]] ;then echo 'Hallo!'elif [[ $ LANG = it*]] ;then echo 'Ciao!'elif [[ $ LANG = es*]] ;then echo 'Hola!'elif [[ $ LANG = @(C|POSIX)]] ;then echo 'hello world'else echo 'I do not speak your language.'fi
اما این همه مقایسه یک مقدار زائد است. BASH یک کلمهکلیدی به نام
case $ LANG in en *) echo 'Hello!';; fr *) echo 'Salut!';; de *) echo 'Guten Tag!';; nl *) echo 'Hallo!';; it *) echo 'Ciao!';; es *) echo 'Hola!';; C | POSIX) echo 'hello world';; *) echo 'I do not speak your language.';; esac
هر انتخاب در جمله
یک ساختار دیگر برای انتخاب، ساختار
به کاربر انتخابهایی ارائه میشود و از او درخواست میشود یک عدد منعکس کننده انتخابش را وارد کند کند. سپس کد داخل بلوک
$ echo "Which of these does not belong in the group?"; \ >select choice in Apples Pears Crisps Lemons Kiwis ; do >if [[ $ choice =Crisps ]] >then echo "Correct! Crisps are not fruit."; break; fi > echo "Errr... no. Try again." >done
تا موقعی که دستور break اجرا نشده است، منو باز تولید میشود. در این مثال دستور break فقط وقتی اجرا میشود که کاربر مورد صحیح را انتخاب نماید.
همچنین میتوانیم از متغیر
$PS3 = "Which of these does not belong in the group (#)? " \ >select choice in Apples Pears Crisps Lemons Kiwis ; do >if [[ $ choice = Crisps ]] >then echo "Correct! Crisps are not fruit."; break; fi > echo "Errr... no. Try again." >done
تمام این ساختارهای شرطی(
# یک منوی سادهwhile true; do echo "Welcome to the Menu" echo " 1. Say hello" echo " 2. Say good-bye" read-p "-> " responsecase $ response in 1 ) echo 'Hello there!';; 2 ) echo 'See you later!'; break;; *) echo 'What was that?';; esac done
تکرارمفید:
جمله
در مستندات گنو: Conditional Constructs
در پرسش و پاسخهای رایج:
چگونه میتوانم شناسههای( گزینههای ) خطفرمان را به آسانی مدیریت کنم؟