خط فرمان لینوکس

ترجمه فارسی LinuxCommand.org

خط فرمان لینوکس

ترجمه فارسی LinuxCommand.org

خطاها و سیگنالها

خطاها و سیگنالها و Trapها (ای وای!) - بخش 1

در این درس، می‌خواهیم به مدیریت خطاها در خلال اجرای اسکریپت‌هایتان نگاه کنیم.

تفاوت میان یک برنامه خوب و یک برنامه ضعیف بیشتر اوقات بر حسب نیرومندی برنامه اندازه‌گیری می‌شود. یعنی توانایی برنامه در مدیریت موقعیت‌هایی که چیزی درست کار نمی‌کند.

وضعیت خروج

همچنانکه از درس‌های قبل به یاد دارید، هر برنامه خوب نوشته شده هنگامی که پایان می‌پذیرد یک وضعیت خروج برگشت می‌دهد. اگر برنامه به طور موفقیت‌آمیز به پایان برسد، وضعیت خروج صفر خواهد بود. اگر وضعیت خروج مورد دیگری غیر از صفر باشد، آنوقت برنامه به طریقی مردود گردیده است.

بررسی نمودن وضعیت خروج برنامه‌هایی که شما در اسکریپت‌هایتان احضار می‌کنید بسیار با اهمیت است. همچنین اهمیت دارد که اسکریپت‌های شما هنگامی که به پایان می‌رسند یک وضعیت خروج معنادار برگشت بدهند. من یکبار مدیر سیستم یونیکسی داشتم که او یک اسکریپت برای یک سیستم تولید شامل دو سطر کد پایین نوشت:

# مثالی از یک ایده حقیقتاً نامساعد‎

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

سر انجام، ما می‌توانیم اسکریپت‌هایمان را با عملگرهای کنترل AND و OR بیشتر ساده کنیم. برای روشن کردن چگونگی کار آنها، من از صفحه man bash نقل‌قول خواهم نمود:

«عملگرهای کنترل && و || به ترتیب علامت لیست‌های ANDو لیست‌های ORهستند.یک لیست AND به این شکل است

command1 && command2

command2 در صورتی اجرا می‌گردد که اگر و فقط اگر، command1 یک وضعیت خروج صفر برگشت بدهد.

یک لیست 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 یک مثال از بسط پارامتر است. در صورتیکه لازم است مطمئن باشید که نام متغیر از متن اطراف جدا می‌شود، می‌توانید آن را با ابروها احاطه کنید (مانند ‎${PROGNAME}). بعضی اشخاص طبق عادت آنها را پیرامون هر متغیری قرار می‌دهند. آن کاربرد در واقع یک مورد سلیقه‌ای است. در کاربرد دوم، ‎${1:-"Unknown Error"} به معنای این است که اگر ‎parameter 1‎ ‏یعنی ‎$1‎ تعریف نشده است، رشته ‎"Unknown Error"‎ در محل آن جایگزین بشود. انجام تعدادی از دستکاری‌های سودمند رشته‌ای، با استفاده از بسط پارامتر مقدور است. در باره بسط پارامتر می‌توانید در صفحه مستندات bash تحت عنوان ‎"EXPANSIONS"‎ بیشتر بخوانید.