فصل ۲۷ – کنترل جریان: انشعاب با 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 سنتی، ساختار مدرن‌تری با نام [[ ]] فراهم می‌کند که مزایایی همچون:

نمونه:

if [[ -d "$WORKDIR" && -w "$WORKDIR" ]]; then
    echo "دایرکتوری آماده است."
else
    echo "نمی‌توان در $WORKDIR نوشت."
fi

برای تطبیق الگو می‌توان نوشت:

if [[ $HOSTNAME == web-* ]]; then
    echo "این میزبان جزو سرورهای وب است."
fi

بازبینی پروژه: بررسی ورودی‌ها

به نسخهٔ فعلی sys_info_page برمی‌گردیم.
می‌خواهیم پیش از تولید گزارش، چند شرط را کنترل کنیم:

  1. فقط کاربری که در گروه adm یا ریشه است مجاز به اجرای اسکریپت باشد.
  2. اگر مسیر خروجی در خط فرمان مشخص شده، وجود و قابلیت نوشتن آن بررسی شود.
  3. در صورت وجود فایل خروجی، نسخهٔ پشتیبان با پسوند زمان ایجاد گردد.

کد نمونه:

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 آشنا می‌شویم که برای انتخاب از میان گزینه‌های متعدد مناسب است.


تمرین‌ها

  1. اسکریپتی بنویسید که نام یک فایل را از کاربر بگیرد و اگر فایل معمولی موجود بود، اندازهٔ آن را نمایش دهد؛ در غیر این صورت پیام خطا چاپ کند.
  2. نسخه‌ای از sys_info_page بسازید که در صورت کمبود فضای دیسک (کمتر از ۱ گیگابایت در /tmp) از تولید گزارش صرف‌نظر کند.
  3. فرمانی بنویسید که بررسی کند آیا سرویس sshd در حال اجراست یا خیر؛ در صورت اجرا پیام «فعال» و در غیر این صورت «غیرفعال» چاپ شود.

مطالعهٔ بیشتر