مقدمه

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

اینجاست که ساختارهای شرطی، و در رأس آن‌ها دستور if، وارد میدان می‌شوند. دستور if به اسکریپت‌های ما قابلیت افزودن منطق و انشعاب را می‌دهد. با استفاده از آن، اسکریپت‌های ما از یک دنباله‌ی ثابت از دستورات، به برنامه‌های هوشمندی تبدیل می‌شوند که می‌توانند به ورودی‌ها و شرایط مختلف واکنش‌های متناسب نشان دهند.

دستور if و کدهای خروج (Exit Codes)

برای درک عملکرد if در شل، ابتدا باید با یک مفهوم بنیادی آشنا شویم: کد خروج. هر دستوری که در لینوکس اجرا می‌شود، پس از پایان کار خود یک عدد صحیح به نام «کد خروج» را به سیستم برمی‌گرداند. این عدد وضعیت نهایی اجرای دستور را مشخص می‌کند:

  • کد خروج 0: به معنای موفقیت کامل است. دستور بدون هیچ مشکلی کار خود را انجام داده است.
  • کد خروج غیر صفر (از ۱ تا ۲۵۵): به معنای بروز یک خطا یا شکست است.

ساختار if در شل، برخلاف بسیاری از زبان‌های برنامه‌نویسی، یک عبارت را به true یا false ارزیابی نمی‌کند. در عوض، یک دستور را اجرا کرده و کد خروج آن را بررسی می‌کند. اگر کد خروج 0 (موفقیت) بود، بلوک دستورات بعد از then اجرا می‌شود.

if some_command
then
  echo "Command was successful."
fi

شما می‌توانید کد خروج آخرین دستور اجرا شده را همیشه با متغیر خاص $? مشاهده کنید:

$ ls /etc/passwd; echo $?
/etc/passwd
0
$ ls /not/a/real/file; echo $?
ls: cannot access '/not/a/real/file': No such file or directory
2

دستور test و شرط‌ها

حال سؤال اینجاست: چگونه می‌توانیم شرایطی مانند «آیا این عدد بزرگتر از ۱۰ است؟» یا «آیا این فایل وجود دارد؟» را بررسی کنیم؟ این‌ها که دستور نیستند! پاسخ، استفاده از دستور test است. وظیفه این دستور، ارزیابی یک عبارت شرطی و برگرداندن کد خروج 0 در صورت درست بودن شرط، و کد خروج 1 در صورت غلط بودن آن است.

امروزه به جای نوشتن خود کلمه‌ی test، از معادل آن یعنی براکت‌ها استفاده می‌شود. فرم مدرن و توصیه‌شده، استفاده از دو براکت [[ ... ]] است که قابلیت‌های بیشتر و امنیت بهتری نسبت به تک براکت [ ... ] دارد.

آزمون‌های رایج

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

نوع آزمون عبارت توضیح
رشته‌ها (Strings) [[ "$str1" == "$str2" ]] آیا دو رشته مساوی هستند.
[[ "$str1" != "$str2" ]] آیا دو رشته نامساوی هستند.
[[ -z "$str" ]] آیا رشته خالی است (طول صفر دارد).
اعداد (Numbers) [[ "$num" -eq "10" ]] مساوی (equal)
[[ "$num" -gt "10" ]] بزرگتر از (greater than)
[[ "$num" -lt "10" ]] کوچکتر از (less than)
فایل‌ها (Files) [[ -e "$path" ]] آیا فایل یا دایرکتوری وجود دارد (exists).
[[ -f "$path" ]] آیا یک فایل عادی است (file).
[[ -d "$path" ]] آیا یک دایرکتوری است (directory).
همیشه متغیرهای خود را داخل دابل کوتیشن ("") قرار دهید. این کار از بروز خطا در صورتی که متغیر خالی باشد یا حاوی فاصله باشد، جلوگیری می‌کند.

ساختن دستورات if کامل

اکنون می‌توانیم با ترکیب if و [[ ... ]]، اسکریپت‌های هوشمند بنویسیم.

اضافه کردن else و elif

ساختار if می‌تواند شامل دو بخش دیگر نیز باشد:

  • else: بلوک دستوراتی که در صورت برقرار نبودن شرط if اجرا می‌شود.
  • elif (else if): به ما اجازه می‌دهد تا شرط‌های متعددی را پشت سر هم بررسی کنیم.

اسکریپت زیر یک مثال کامل است که تمام این مفاهیم را به کار می‌گیرد:

#!/bin/bash

if [[ -z "$1" ]]; then
  echo "Error: No input file provided."
  exit 1 
fi

if [[ -f "$1" ]]; then
  echo "'$1' is a regular file."
elif [[ -d "$1" ]]; then
  echo "'$1' is a directory."
else
  echo "'$1' does not exist or is of another type."
fi

آرگومان‌های خط فرمان و خروج از اسکریپت

در مثال بالا از دو مفهوم جدید استفاده کردیم:

  • $1: این یک متغیر خاص به نام «پارامتر موقعیتی» است. $1 به اولین آرگومانی که جلوی نام اسکریپت در خط فرمان تایپ می‌شود اشاره دارد، $2 به دومی و الی آخر. (مثلاً در ./script.sh /etc/passwd، مقدار $1 برابر با /etc/passwd خواهد بود.)
  • exit 1: این دستور، اجرای اسکریپت را فوراً متوقف کرده و کد خروج ۱ (به معنای خطا) را برمی‌گرداند. این کار برای مدیریت خطاها بسیار مفید است.