فصل ۲۷ – کنترل جریان: انشعاب با if
در دو فصل گذشته یک پروژهٔ واقعی را آغاز کردیم و اسکریپت sys_info_page را با دیدگاه «طراحی از بالا به پایین» ساختاردهی کردیم.
گام بعدی این است که بتوانیم وابسته به شرایط رفتار متفاوتی نشان دهیم؛
برای مثال اگر کاربر مجوز کافی نداشته باشد هشدار دهیم یا اگر فایل خروجی موجود است، نسخهٔ تازهای نسازیم.
ابزار اصلی برای این کار عبارت شرطی if است که به کمک آن میتوانیم جریان اجرا را کنترل کنیم.
شکل کلی دستور if
دستور if در bash شکل عمومی زیر را دارد:
if فرمان یا عبارت شرطی
then
دستورات در صورت صدق شرط
fi
شل ابتدا فرمان شرطی را اجرا میکند.
اگر مقدار بازگشتی فرمان (Exit Status) برابر صفر باشد، بخش then اجرا میشود؛ در غیر این صورت کنترل به انتهای ساختار یعنی fi منتقل میشود.
برای خوانایی، معمولاً کلمهٔ then در خط جداگانه قرار میگیرد، اما شکل فشردهٔ زیر نیز صحیح است:
if فرمان; then
دستورات
fi
افزودن بخش else
گاهی لازم است در صورت برقرار نبودن شرط هم عملی انجام دهیم.
با افزودن کلمهٔ else، دو شاخهٔ مجزا خواهیم داشت:
if فرمان; then
دستورات در صورت موفقیت
else
دستورات در صورت شکست
fi
انشعابهای چندگانه با elif
برای بررسی چند شرط پشت سر هم از elif استفاده میکنیم؛ هر elif شرط جدیدی را آزمایش میکند و به محض موفقیت، سایر شاخهها نادیده گرفته میشوند:
if شرط_۱; then
کار ۱
elif شرط_۲; then
کار ۲
elif شرط_۳; then
کار ۳
else
حالت پیشفرض
fi
فرمان test و براکتهای []
به صورت سنتی برای ساخت شرطها از برنامهٔ test استفاده میشود.
این فرمان عبارت منطقی را دریافت و با توجه به درستی یا نادرستی آن مقدار بازگشتی صفر/غیرصفر تولید میکند.
دو شکل نوشتاری معادل در bash وجود دارد:
test EXPRESSION
یا
[ EXPRESSION ]
نکته: فاصلهها الزامی هستند؛ یعنی باید میان براکت و عبارت فاصله بگذارید.
مقایسهٔ رشتهای
| عملگر | توضیح |
|---|---|
string1 = string2 |
برابر بودن دو رشته |
string1 != string2 |
نابرابر بودن |
-n string |
طول رشته بزرگتر از صفر است |
-z string |
طول رشته صفر است |
string1 < string2 |
مقایسهٔ لغتنامهای (درون [[ ]]) |
string1 > string2 |
مقایسهٔ لغتنامهای (درون [[ ]]) |
در محیط [ و test، عملگرهای < و > باید با escape (\<) استفاده شوند، اما در [[ ]] نیازی نیست.
مقایسهٔ عددی
| عملگر | توضیح |
|---|---|
int1 -eq int2 |
برابر بودن |
int1 -ne int2 |
نابرابر |
int1 -lt int2 |
کوچکتر |
int1 -le int2 |
کوچکتر یا مساوی |
int1 -gt int2 |
بزرگتر |
int1 -ge int2 |
بزرگتر یا مساوی |
برای محاسبات پیچیدهتر میتوان از ساختار (( )) بهره گرفت که نحو C مانند دارد و خودکار نتیجهٔ منطقی تولید میکند.
آزمون فایلها
بسیاری از اسکریپتها باید دربارهٔ فایلها تصمیمگیری کنند؛ مثلاً آیا فایل موجود است؟ آیا قابل نوشتن است؟
مهمترین عملگرهای test برای فایلها عبارتاند از:
| عملگر | توضیح |
|---|---|
-e file |
وجود فایل یا دایرکتوری |
-f file |
فایل معمولی |
-d file |
دایرکتوری |
-s file |
اندازهٔ غیرصفر |
-r file |
دارای مجوز خواندن |
-w file |
دارای مجوز نوشتن |
-x file |
قابل اجرا |
-O file |
مالکیت فایل متعلق به کاربر جاری |
-G file |
فایل به گروه کاربر جاری تعلق دارد |
file1 -nt file2 |
فایل اول جدیدتر از فایل دوم |
file1 -ot file2 |
فایل اول قدیمیتر |
این آزمونها در پروژهٔ ما کاربرد فراوانی دارند؛ مثلاً برای بررسی وجود دایرکتوری خروجی.
ساختار [[ ]] و عملگرهای منطقی
bash علاوه بر test سنتی، ساختار مدرنتری با نام [[ ]] فراهم میکند که مزایایی همچون:
- عدم نیاز به escape برای اکثر کاراکترهای متا
- امکان استفاده از عملگرهای منطقی
&&،||و! - پشتیبانی از تطبیق الگو (Pattern Matching) با
=~
نمونه:
if [[ -d "$WORKDIR" && -w "$WORKDIR" ]]; then
echo "دایرکتوری آماده است."
else
echo "نمیتوان در $WORKDIR نوشت."
fi
برای تطبیق الگو میتوان نوشت:
if [[ $HOSTNAME == web-* ]]; then
echo "این میزبان جزو سرورهای وب است."
fi
بازبینی پروژه: بررسی ورودیها
به نسخهٔ فعلی sys_info_page برمیگردیم.
میخواهیم پیش از تولید گزارش، چند شرط را کنترل کنیم:
- فقط کاربری که در گروه
admیا ریشه است مجاز به اجرای اسکریپت باشد. - اگر مسیر خروجی در خط فرمان مشخص شده، وجود و قابلیت نوشتن آن بررسی شود.
- در صورت وجود فایل خروجی، نسخهٔ پشتیبان با پسوند زمان ایجاد گردد.
کد نمونه:
REPORT_FILE=${1:-/var/www/html/sys_info.html}
BACKUP_FILE="$REPORT_FILE.$(date +%s)"
if ! id -nG "$USER" | grep -qwE '(^|\s)(root|adm)(\s|$)'; then
echo "خطا: برای اجرای اسکریپت باید عضو گروه adm یا کاربر ریشه باشید." >&2
exit 1
fi
if [[ -e "$REPORT_FILE" ]]; then
echo "هشدار: فایل مقصد وجود دارد؛ نسخهٔ پشتیبان میسازم."
cp "$REPORT_FILE" "$BACKUP_FILE" || {
echo "عدم موفقیت در ایجاد نسخهٔ پشتیبان." >&2
exit 1
}
fi
در این مثال از ترکیب [[ ]]، اپراتور ! و فرمانهای کمکی استفاده کردهایم تا جریان تصمیمگیری بهوضوح مشخص شود.
دستور case در آینده
در برخی شرایط استفاده از زنجیرههای طولانی if/elif ناخوانا میشود.
در فصل ۳۱ با ساختار case آشنا میشویم که برای انتخاب از میان گزینههای متعدد مناسب است.
تمرینها
- اسکریپتی بنویسید که نام یک فایل را از کاربر بگیرد و اگر فایل معمولی موجود بود، اندازهٔ آن را نمایش دهد؛ در غیر این صورت پیام خطا چاپ کند.
- نسخهای از
sys_info_pageبسازید که در صورت کمبود فضای دیسک (کمتر از ۱ گیگابایت در/tmp) از تولید گزارش صرفنظر کند. - فرمانی بنویسید که بررسی کند آیا سرویس
sshdدر حال اجراست یا خیر؛ در صورت اجرا پیام «فعال» و در غیر این صورت «غیرفعال» چاپ شود.
مطالعهٔ بیشتر
- صفحهٔ راهنمای bash دربارهٔ ساختارهای
if,[[ ]]و(( )) - مقالهٔ «Bash Reference Manual» بخش Conditional Constructs
- بخش «test» در صفحات man برای مشاهدهٔ کاملترین فهرست عملگرها