در این درس، میخواهیم به مدیریت خطاها در خلال اجرای اسکریپتهایتان نگاه کنیم.
تفاوت میان یک برنامه خوب و یک برنامه ضعیف بیشتر اوقات بر حسب
همچنانکه از درسهای قبل به یاد دارید، هر برنامه خوب نوشته شده هنگامی که پایان میپذیرد یک وضعیت خروج برگشت میدهد. اگر برنامه به طور موفقیتآمیز به پایان برسد، وضعیت خروج صفر خواهد بود. اگر وضعیت خروج مورد دیگری غیر از صفر باشد، آنوقت برنامه به طریقی مردود گردیده است.
بررسی نمودن وضعیت خروج برنامههایی که شما در اسکریپتهایتان احضار میکنید بسیار با اهمیت است. همچنین اهمیت دارد که اسکریپتهای شما هنگامی که به پایان میرسند یک وضعیت خروج معنادار برگشت بدهند. من یکبار مدیر سیستم یونیکسی داشتم که او یک اسکریپت برای یک سیستم تولید شامل دو سطر کد پایین نوشت:
# مثالی از یک ایده حقیقتاً نامساعد cd $some_directory rm *
چرا انجام چنین موردی یک روش نامساعد است؟ اگر هیچ عدم موفقیتی نباشد، نادرست نیست. این دو سطر دایرکتوری کاری را به دایرکتوری نام برده شده در $some_directory تغییر میدهد و فایلها در آن دایرکتوری را حذف میکند. این رفتارِ مورد انتظار است. اما اگر دایرکتوری نامبرده در $some_directory موجود نباشد چه میشود؟ در آن حالت، فرمان cd ناموفق میگردد و اسکریپت فرمان rm را در دایرکتوری کاری جاری اجرا میکند. رفتار مورد انتظار نیست!
در ضمن، اسکریپت مدیر سیستم بیچاره من به این نارساییِ زیاد اجازه داد و قسمت بزرگی از یک سیستم تولید را تخریب نمود. اجازه ندهید این مورد برای شما رخ بدهد!
مشکل به وسیله اسکریپت آن بود که قبل از پیشروی با فرمان rm وضعیت خروج فرمان cd را بررسی نکرد.
چند روش وجود دارد که میتوانید وضعیت خروج یک برنامه را به دست آورده و به آن واکنش نشان بدهید. نخست، شما میتوانید محتوای متغیر محیط
[me] $ true; echo $? 0 [me] $ false; echo $? 1
فرمانهای true و false برنامههایی هستند که غیر از اینکه به ترتیب یک وضعیت خروج صفر و یک برگشت بدهند کاری انجام نمیدهند. با کاربرد آنها، ما میتوانیم مشاهده کنیم چگونه متغیر دارای وضعیت خروجِ برنامه قبل میگردد.
بنابراین برای بررسی وضعیت خروج، میتوانستیم اسکریپت را به این طریق بنویسیم:
# بررسی وضعیت خروج cd $some_directory if [ "$?" = "0" ]; then rm * else echo "Cannot change directory!" 1>&2 exit 1 fi
در این نگارش، ما وضعیت خروج فرمان cd را معاینه میکنیم و اگر صفر نباشد، یک پیغام خطا در خروجی استاندارد خطا چاپ نموده و اسکریپت را با وضعیت خروج 1 خاتمه میدهیم.
در حالیکه این یک راه حل کاری برای مشکل است، روشهای چابکتری که مقداری در تایپ کردن ما صرفهجویی خواهد نمود وجود دارند. در رویکرد بعدی ما سعی میکنیم به طور مستقیم از جمله if استفاده کنیم، چون وضعیت خروج فرمان داده شده را ارزیابی میکند.
با کاربرد if، میتوانستیم آنرا به این شکل بنویسیم:
# یک روش بهتر if cd $some_directory; then rm * else echo "Could not change directory! Aborting." 1>&2 exit 1 fi
در اینجا ما بررسی میکنیم ببینیم آیا فرمان cd موفق است. تنها آنوقت فرمان rm مجاز به اجرا میگردد، در غیر اینصورت یک پیغام خطا صادر و برنامه با وضعیت خروج 1 نشاندهنده آنکه یک خطا رخ داده است، خارج میشود.
چون بیشتر اوقات ما در برنامههایمان خطاها را بررسی خواهیم نمود، نوشتن یک تابع که پیغام خطاها را نمایش خواهد داد، عقلانی است. این تابع از تایپ کردن بیشتر صرفهجویی میکند و تنبلی را رواج میدهد.
# یک تابع خروج خطا error_exit() { echo "$1" 1>&2 exit 1 } # error_exit طرز استعمال if cd $some_directory; then rm * else error_exit "Cannot change directory! Aborting." fi
سر انجام، ما میتوانیم اسکریپتهایمان را با عملگرهای کنترل AND و OR بیشتر ساده کنیم. برای روشن کردن چگونگی کار آنها، من از صفحه man bash نقلقول خواهم نمود:
«عملگرهای کنترل command1 && command2 command2 در صورتی اجرا میگردد که اگر یک لیست OR به این شکل است command1 || command2 command2 در صورتی اجرا میشود که اگر و فقط اگر، command1 یک وضعیت خروج غیر صفر برگشت بدهد. وضعیت خروج لیستهای AND و OR وضعیت خروج فرمان اجرا شده در لیست است.» |
یک بار دیگر، ما میتوانیم از فرمانهای true و false برای دیدن این کار استفاده کنیم:
[me] $ true || echo "echo executed" [me] $ false || echo "echo executed" echo executed [me] $ true && echo "echo executed" echo executed [me] $ false && echo "echo executed" [me] $
با استفاده از این شیوه، حتی میتوانیم یک نگارش سادهتر بنویسیم:
# سادهتر از همه cd $some_directory || error_exit "Cannot change directory! Aborting" rm *
اگر در مورد خطا یک خروج لازم نباشد، آنوقت شما حتی میتوانید این مورد را انجام بدهید:
# یک روش دیگر برای انجام آن در صورتیکه خارج شدن مطلوب نیست cd $some_directory && rm *
میخواهم اشاره کنم که حتی با دفاع در برابر خطاها که ما در مثالهایمان برای استفاده از cd معرفی کردهایم، بازهم این کد به واسطه یک خطای رایج برنامهنویسی آسیبپذیر است، یعنی، اگر نام متغیر شامل نام دایرکتوری با املای غلط نوشته شود چه موردی رخ میدهد؟ در آن حالت، پوسته متغیر را به عنوان متغیر تهی تفسیر میکند و cd موفق میشود، اما دایرکتوری به دایرکتوری خانگی کاربر تغییر میکند، بنابراین مواظب باشید!
تعدادی بهبود وجود دارد که میتوانیم برای تابع error_exit به وجود آوریم. من دوست دارم نام برنامه را برای روشن کردن اینکه خطا از کجا ناشی میشود پیوست کنم. این مطلب وقتی برنامه شما پیچیدهتر میشود و دارای اسکریپتهای راهاندازی کننده سایر اسکریپتها باشید بیشتر اهمیت مییابد. همچنین، به گنجاندن متغیر محیط LINENO که در شناسایی دقیق سطری از اسکریپت که خطا در آن رخ داده است به شما کمک میکند توجه نمایید.
#!/bin/bash # یک روال مدیریت خطای ماهرتر # در اسکریپت خود قرار میدهم که PROGNAME من متغیری به نام # نام برنامهای که باید اجرا شود را نگهداری میکند. شما این # .به دست بیاورید ($0) کمیت را میتوانید از اولین فقره سطر فرمان PROGNAME=$(basename $0) error_exit() { # ------------------------------------------- # تابع برای خروج ناشی از خطای مهلک برنامه # :یک شناسه میپذیرد # رشته شامل پیغام خطای توصیف کننده # -------------------------------------------- echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 exit 1 } # به گنجاندن متغیر محیطِ error_exit مثال فراخوانی تابع # توجه کنید. این متغیر محتوی شماره سطر جاری است LINENO echo "Example of error with line number and message" error_exit "$LINENO: An error has occurred."
استفاده از ابروها در داخل تابع error_exit یک مثال از