شروع برنامهنویسی Shell Script از صفر؛ متغیرها، شرطها، حلقهها و آرگومانها
در دو قسمت قبلی این مجموعه با محیط لینوکس، ساختار فایلها، دستورات پایه، Pipe و ابزارهای پردازش متن آشنا شدیم. اکنون زمان آن رسیده است که این دستورات را در قالب برنامههایی قابلاستفاده، قابلتکرار و قابلنگهداری قرار دهیم.
در این قسمت وارد دنیای Shell Scripting میشویم؛ جایی که میتوانیم کارهای روزمره، مدیریتی و تکراری را خودکار کنیم.
برای نمونه، با یک اسکریپت Bash میتوانیم:
وضعیت یک سرویس را بررسی کنیم؛
از فایلهای مهم نسخه پشتیبان بگیریم؛
فضای دیسک را کنترل کنیم؛
چندین فایل را بهصورت خودکار پردازش کنیم؛
کاربران یا پوشههای جدید ایجاد کنیم؛
لاگهای سیستم را تحلیل کنیم؛
و فرایندهای استقرار نرمافزار را سادهتر کنیم.
Bash چیست؟
Bash مخفف عبارت Bourne Again Shell است و یکی از محبوبترین Shellهای دنیای لینوکس محسوب میشود.
Bash دو نقش اصلی دارد:
اجرای مستقیم دستورات در ترمینال؛
اجرای مجموعهای از دستورات ذخیرهشده در یک فایل اسکریپت.
برای بررسی Shell پیشفرض کاربر:
echo "$SHELL"
برای بررسی نسخه Bash:
bash --version
البته باید توجه داشت که هر اسکریپت Shell الزاماً Bash نیست. Shellهایی مانند sh، zsh و fish نیز وجود دارند و syntax آنها ممکن است متفاوت باشد. در این مجموعه تمرکز ما روی Bash است.
۱. ساخت اولین اسکریپت Bash
ابتدا یک پوشه برای تمرین ایجاد کنید:
mkdir -p ~/bash-course/part-3
cd ~/bash-course/part-3
سپس فایلی به نام hello.sh بسازید:
nano hello.sh
محتوای زیر را در فایل قرار دهید:
#!/usr/bin/env bash
echo "Hello, Linux!"
فایل را ذخیره کنید و سپس آن را اجرایی کنید:
chmod +x hello.sh
اجرای اسکریپت:
./hello.sh
خروجی:
Hello, Linux!
Shebang چیست؟
خط اول اسکریپت:
#!/usr/bin/env bash
به سیستمعامل میگوید این فایل باید با Bash اجرا شود.
دو شکل رایج Shebang عبارتاند از:
#!/bin/bash
و:
#!/usr/bin/env bash
فرم دوم معمولاً انعطافپذیرتر است؛ زیرا مسیر Bash را از متغیر محیطی سیستم پیدا میکند.
دو روش اجرای اسکریپت
روش اول: اجرای مستقیم
در این روش فایل باید مجوز اجرا داشته باشد:
chmod +x hello.sh
./hello.sh
روش دوم: اجرای مستقیم توسط Bash
در این روش نیازی به مجوز اجرایی فایل نداریم:
bash hello.sh
تفاوت مهم:
./hello.sh
از Shebang استفاده میکند، اما:
bash hello.sh
فایل را مستقیماً با Bash اجرا میکند.
۲. کامنتگذاری در Bash
برای نوشتن توضیحات در Bash از علامت # استفاده میکنیم:
#!/usr/bin/env bash
# این خط یک توضیح است
echo "Hello Linux"
هر چیزی که بعد از # قرار بگیرد، در همان خط اجرا نمیشود.
کامنتها برای موارد زیر کاربرد دارند:
توضیح هدف اسکریپت؛
توضیح بخشهای پیچیده؛
ثبت اطلاعات نگهداری؛
غیرفعالکردن موقت یک خط کد.
نمونه:
# نمایش فضای آزاد دیسک
df -h
از نوشتن کامنتهای بیدلیل برای دستورات کاملاً واضح خودداری کنید؛ اما برای منطقهای پیچیده یا تصمیمهای فنی، توضیح مناسب بنویسید.
۳. متغیرها در Bash
متغیر برای ذخیره داده استفاده میشود.
نحوه تعریف متغیر:
name="TivaCode"
در Bash نباید اطراف علامت = فاصله قرار گیرد.
صحیح:
name="TivaCode"
غلط:
name = "TivaCode"
برای استفاده از مقدار متغیر، قبل از نام آن علامت $ قرار میدهیم:
echo "$name"
خروجی:
TivaCode
چرا بهتر است متغیرها را داخل کوتیشن بنویسیم؟
کد زیر ممکن است در صورت وجود فاصله یا کاراکتر خاص مشکل ایجاد کند:
echo $name
روش امنتر:
امنتر:
```bash
echo "$name"
بهعنوان مقدار متغیرها را معمولاً داخل کوتیشن دوتایی قرار دهید.
نامگذاری متغیرها
نام متغیر میتواند شامل حروف، اعداد و _ باشد؛ اما نباید با عدد شروع شود.
صحیح:
user_name="ali"
server_port=8080
غلط:
2server="test"
Bash به حروف بزرگ و کوچک حساس است:
name="Ali"
Name="Sara"
این دو متغیر متفاوت هستند.
متغیرهای ثابت
اگر مقدار متغیری نباید تغییر کند، میتوان از readonly استفاده کرد:
readonly APP_NAME="TivaCode"
تلاش برای تغییر آن با خطا مواجه میشود:
APP_NAME="Other"
۴. متغیرهای محیطی
برخی متغیرها توسط سیستم یا Shell در اختیار برنامه قرار میگیرند.
مشاهده تمام متغیرهای محیطی:
env
یا:
printenv
چند متغیر مهم:
echo "$HOME"
echo "$USER"
echo "$PWD"
echo "$PATH"
echo "$SHELL"
کاربرد آنها:
متغیر--------------------------کاربرد
'$HOME'----------------------مسیر پوشه خانگی کاربر
'$USER'-----------------------نام کاربر فعلی
'$PWD'-----------------------مسیر فعلی
'$PATH'----------------------مسیرهای جستوجوی دستورات
'$SHELL---------------------Shell پیشفرض
مثال :
#!/usr/bin/env bash
echo "User: $USER"
echo "Home: $HOME"
echo "Current directory: $PWD"
ایجاد متغیر محیطی با export
متغیرهای معمولی فقط در Shell فعلی شناخته میشوند:
APP_ENV="development"
برای در دسترس قرارگرفتن متغیر برای فرایندهای فرزند، از export استفاده میکنیم:
export APP_ENV="production"
بررسی مقدار:
echo "$APP_ENV"
نمونه:
#!/usr/bin/env bash
export APP_NAME="TivaCode"
bash -c 'echo "$APP_NAME"'
۵. دریافت ورودی از کاربر
دستور read برای دریافت ورودی استفاده میشود:
#!/usr/bin/env bash
read -r -p "نام شما چیست؟ " name
echo "سلام، $name"
گزینه -r باعث میشود Backslash بدون پردازش ویژه خوانده شود و انتخاب مناسبی برای دریافت ورودی متنی است.
مثال دریافت رمز عبور:
read -r -s -p "Password: " password
echo
echo "رمز دریافت شد."
گزینه -s باعث میشود ورودی روی صفحه نمایش داده نشود.
۶. آرگومانهای خط فرمان
اسکریپتها میتوانند هنگام اجرا داده دریافت کنند.
فایل greet.sh:
#!/usr/bin/env bash
echo "سلام، $1"
اجرای اسکریپت:
chmod +x greet.sh
./greet.sh Ali
خروجی:
سلام، Ali
در Bash:
پارامتر---------------------------مفهوم
— ----------------------------- —
'$1'-----------------------------آرگومان اول
'$2'-----------------------------آرگومان دوم
'$#'-----------------------------تعداد آرگومانها
'$@'----------------------------تمام آرگومانها
'$?'-----------------------------کد خروجی آخرین دستور
'$$'-----------------------------شناسه فرایند اسکریپت
مثال :
#!/usr/bin/env bash
echo "Script: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Argument count: $# argument: $2"
echo "Argument count: $#"
اجرا:
##
برای پیمایش آرگومانها:
```bash
#!/usr/bin/env bash
for arg in "$@"; do
echo "Argument: $arg"
done
استفاده از "$@" باعث میشود آرگومانهایی که دارای فاصله هستند، بهدرستی حفظ شوند.
مثال:
./script.sh "file one.txt" "file two.txt"
بررسی تعداد آرگومانها
فرض کنید اسکریپت باید دقیقاً یک مسیر دریافت کند:
#!/usr/bin/env bash
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <directory>" >&2
exit 1
fi
echo "Directory: $1"
در اینجا:
-ne: نابرابر عددی>&2: ارسال پیام بهstderrexit 1: پایان اسکریپت با وضعیت خطا
۷. کد خروجی دستورات
هر دستور در لینوکس خروجی به پایان می به پایان میرسد.
قاعده کلی:
0 موفقیت
غیرصفر خطا یا وضعیت غیرموفق
بررسی کد خروجی آخرین دستور:
ls /tmp
echo "$?"
اگر مسیر وجود داشته باشد، معمولاً مقدار 0 مشاهده میشود.
مثال:
ls /path/that/does/not/exist
echo "$?"
کد خروجی غیرصفر نشاندهنده خطا است.
استفاده از && و ||
اجرای دستور دوم فقط در صورت موفقیت دستور اول:
mkdir test && echo "Directory created"
اجرای دستور دوم در صورت شکست دستور اول:
cd /not-found || echo "Directory does not exist"
ترکیب هر دو:
bashنجیرهایاین الگو برای اجرای زنجیرهای عملیات مفید است، اما در اسکریپتهای پیچیده بهتر است از شرطهای واضح استفاده کنیم.
۸. عبارات شرطی در Bash
ساختار عمومی شرط:
if [[ condition ]]; then
commands
elif [[ another_condition ]]; then
commands
else
commands
fi
مثال ساده:
#!/usr/bin/env bash
if [[ "$USER" == "root" ]]; then
echo "You are root"
else
echo "You are a regular user"
fi
در Bash مدرن، استفاده از [[ ... ]] معمولاً نسبت به [ ... ] امنتر و قدرتمندتر است.
مقایسه رشتهها
if [[ "$name" == "Ali" ]]; then
echo "Welcome Ali"
fi
مقایسه نابرابری:
if [[ "$name" != "Ali" ]]; then
echo "This is not Ali"
fi
بررسی خالیبودن متغیر:
if [[ -z "$name" ]]; then
echo "Name is empty"
fi
بررسی غیرخالیبودن:
if [[ -n "$name" ]]; then
echo "Name is not empty"
fi
مقایسه عددی
عملگر-------------------------مفهوم
'-eq'--------------------------برابر
'-ne'--------------------------نابرابر
'-gt'--------------------------بزرگتر
'-lt'--------------------------کوچکتر
'-le'-------------------------کوچکتر یا مساوی
مثال :
age=25
if [[ "$age" -ge 18 ]]; then
echo "Adult"
else
echo "Minor"
fi
در Bash میتوان از عملگرهای ریاضی داخل (( )) نیز استفاده کرد:
if (( age >= 18 )); then
echo "Adult"
fi
۹. بررسی فایلها و پوشهها
Bash عملگرهای مختلفی برای بررسی فایلها دارد:
عملگر-----------------------------کاربرد
'-e'-------------------------------مسیر وجود دارد
'-f'-------------------------------فایل عادی است
'-d'------------------------------دایرکتوری است
'-r'------------------------------قابل خواندن است
'-w'-----------------------------قابل نوشتن است
'-x'-----------------------------قابل اجرا است
'-s'-----------------------------خالی نیست
مثال :
#!/usr/bin/env bash
file="config.env"
if [[ -f "$file" ]]; then
echo "File exists"
else
echo "File not found"
fi
بررسی پوشه:
if [[ -d "/var/log" ]]; then
echo "Log directory exists"
fi
بررسی قابلاجرا بودن:
if [[ -x "./deploy.sh" ]]; then
echo "Script is executable"
fi
۱۰. عملگرهای منطقی
استفاده از && در شرط:
if [[ -f "$file" && -r "$file" ]]; then
echo "File exists and is readable"
fi
استفاده از ||:
if [[ ! -f "$file" || ! -r "$file" ]]; then
echo "File is missing or unreadable"
fi
علامت ! نتیجه شرط را معکوس میکند.
۱۱. دستور case
هنگامی که چند مقدار مشخص داریم، case خواناتر از چندین if است.
#!/usr/bin/env bash
read -r -p "Enter an option: " option
case "$option" in
start)
echo "Starting service..."
;;
stop)
echo "Stopping service..."
;;
restart)
echo "Restarting service..."
;;
*)
echo "Unknown option"
exit 1
;;
esac
الگوی * برای حالت پیشفرض استفاده میشود.
ساخت منوی ساده
#!/usr/bin/env bash
echo "1) نمایش تاریخ"
echo "2) نمایش فضای دیسک"
echo "3) نمایش حافظه"
read -r -p "انتخاب شما: " choice
case "$choice" in
1)
date
;;
2)
df -h
;;
3)
free -h
;;
*)
echo "گزینه نامعتبر است."
exit 1
;;
esac
۱۲. حلقه for
حلقه for برای تکرار روی مجموعهای از دادهها استفاده میشود.
for item in one two three; do
echo "$item"
done
پیمایش فایلها:
for file in *.log; do
echo "Processing: $file"
done
نکته مهم درباره الگوی خالی
اگر هیچ فایل .log وجود نداشته باشد، ممکن است Bash عبارت *.log را بهصورت لفظی نگه دارد. برای مدیریت بهتر این حالت:
shopt -s nullglob
for file in *.log; do
echo "Processing: $file"
done
پیمایش آرگومانها
for file in "$@"; do
echo "File: $file"
done
اجرای نمونه:
./process.sh "first file.txt" "second file.txt"
۱۳. حلقه عددی
for ((i = 1; i <= 5; i++)); do
echo "$i"
done
خروجی:
1
2
3
4
5
در Bash برای عملیات عددی از (( )) استفاده میشود:
count=0
((count++))
echo "$count"
۱۴. حلقه while
حلقه while تا زمانی که شرط درست باشد ادامه پیدا میکند:
counter=1
while (( counter <= 5 )); do
echo "$counter"
((counter++))
done
خواندن فایل خطبهخط
read -r line; do
echo “Line: $line”
done < file.txtچرا این روش مناسب است؟
- `IFS=` فاصلههای ابتدایی و انتهایی را حفظ میکند.
- `-r` از تفسیر Backslash جلوگیری میکند.
- هدایت ورودی با `<` فایل را به حلقه میدهد.
۱۵. حلقه `until`
# `until` تا زمانی اجرا میشود که شرط درست شود:
```bash
attempt=1
until [[ -f ready.flag ]]; do
echo "Waiting..."
sleep 2
((attempt++))
done
echo "Flag found"
این ساختار برای انتظار جهت آمادهشدن یک فایل، سرویس یا وضعیت خاص مفید است.
۱۶. توقف یا ردکردن یک مرحله از حلقه
break
پایان کامل حلقه:
for number in 1 2 3 4 5; do
if (( number == 3 )); then
break
fi
echo "$number"
done
continue
ردکردن مرحله فعلی و ادامه حلقه:
for number in 1 2 3 4 5; do
if (( number == 3 )); then
continue
fi
echo "$number"
done
۱۷. آرایهها در Bash
تعریف آرایه:
servers=("web-1" "web-2" "db-1")
دسترسی به عضو اول:
echo "${servers[0]}"
دسترسی به تمام اعضا:
echo "${servers[@]}"
تعداد اعضا:
echo "${#servers[@]}"
پیمایش آرایه:
for server in "${servers[@]}"; do
echo "Checking $server"
done
اضافهکردن عضو:
servers+=("cache-1")
دسترسی به اندیسها:
for index in "${!servers[@]}"; do
echo "$index: ${servers[index]}"
done
۱۸. محاسبات ریاضی
محاسبات ساده:
a=10
b=3
echo $((a + b))
echo $((a - b))
echo $((a * b))
echo $((a / b))
echo $((a % b))
ذخیره نتیجه:
sum=$((a + b))
echo "$sum"
Bash برای محاسبات اعشاری مناسب نیست. برای اعداد اعشاری میتوان از ابزارهایی مانند bc استفاده کرد:
echo "scale=2; 10 / 3" | bc
۱۹. دریافت آرگومانهای استاندارد با getopts
برای اسکریپتهای حرفهای بهتر است از گزینههایی مانند -f، -v یا -o استفاده کنیم.
نمونه:
#!/usr/bin/env bash
verbose=false
file=""
while getopts ":f:v" opt; do
case "$opt" in
f)
file="$OPTARG"
;;
v)
verbose=true
;;
*)
echo "Usage: $0 -f <file> [-v]" >&2
exit 1
;;
esac
done
if [[ -z "$file" ]]; then
echo "File is required." >&2
exit 1
fi
echo "Selected file: $file"
if [[ "$verbose" == true ]]; then
echo "Verbose mode enabled."
fi
اجرا:
./script.sh -f application.log -v
متغیرهای مهم:
OPTARG: مقدار گزینهOPTIND: محل گزینه فعلیgetopts: پردازش گزینههای کوتاه خط فرمان
۲۰. پروژه عملی: اسکریپت بررسی سلامت سیستم
اکنون یک اسکریپت کاربردی مینویسیم که اطلاعات پایه سیستم را نمایش دهد.
فایل را ایجاد کنید:
nano system_info.sh
محتوا:
#!/usr/bin/env bash
echo "===== System Information ====="
echo "Hostname: $(hostname)"
echo "User: $USER"
echo "Date: $(date)"
echo
echo "===== Operating System ====="
if [[ -f /etc/os-release ]]; then
. /etc/os-release
echo "Distribution: $PRETTY_NAME"
else
echo "OS information is unavailable."
fi
echo
echo "===== Uptime ====="
uptime
echo
echo "===== Disk Usagedf -h /
echo
echo "===== Memory===== Memory Usage ====="
plus -h
اجرای اسکریپت:
chmod +x system_info.sh
./system_info.sh
در این مثال چند مفهوم مهم را همزمان به کار بردیم:
Shebang
متغیرهای محیطی
Command Substitution با
$()شرط
ifبررسی فایل
بارگذاری فایل
/etc/os-releaseاجرای دستورات سیستمی
۲۱. پروژه عملی: بررسی فضای دیسک
اسکریپت زیر اگر فضای استفادهشده ریشه از حد مشخصی بیشتر شود، هشدار نمایش میدهد:
#!/usr/bin/env bash
threshold=80
usage=$(df --output=pcent / | tail -n 1 | --output=pcent / | tail -n 1 | threshold )); then
echo "WARNING: Disk usage is ${usage}%"
exit 1
else
echo "OK: Disk usage is ${usage}%"
fi
توضیح بخش اصلی:
df --output=pcent /
درصد استفاده از دیسک را دریافت میکند.
tail -n 1
آخرین خط را نگه میدارد.
tr -dc '0-9'
تمام کاراکترها بهجز اعداد را حذف میکند.
۲۲. ساخت اسکریپت پاکسازی فایلهای موقت
این اسکریپت فقط یک نمونه آموزشی است. مسیرها را پیش از اجرا بررسی کنید.
#!/usr/bin/env bash
target_dir="${1:-./tmp}"
if [[ ! -d "$target_dir" ]]; then
echo "Directory not found: $target_dir" >&2
exit 1
fi
echo "Files that would be removed:"
find "$target_dir" -type f -name "*.tmp" -print
در این مرحله عمداً فایلها را حذف نمیکنیم و فقط فهرست را نمایش میدهیم. پس از بررسی نتیجه، میتوان نسخه اجرایی را با تأیید کاربر توسعه داد.
نمونه نسخه تعاملی:
#!/usr/bin/env bash
target_dir="${1:-./tmp}"
if [[ ! -d "$target_dir" ]]; then
echo "Directory not found: $target_dir" >&2
exit 1
fi
find "$target_dir" -type f -name "*.tmp" -print
read -r -p "Delete these files? [y/N] " answer
if [[ "$answer" == "y" || "$answer" == "Y" ]]; then
find "$target_dir" -type f -name "*.tmp" -delete "Operation cancelled."
Temporary files deleted."
else
echo "Operation cancelled."
fi
۲۳. اشتباهات رایج در Bash
اشتباه اول: فاصله اطراف =
غلط:
name = "Ali"
صحیح:
name="Ali"
اشتباه دوم: کوتیشنگذاری نکردن متغیرها
خطرناک:
rm -f $file
امنتر:
rm -f -- "$file"
اشتباه سوم: نادیده گرفتن فاصله در نام فایل
غلط:
for file in $(ls); do
echo "$file"
done
روش بهتر:
for file in *; do
echo "$file"
done
اشتباه چهارم: استفاده بیدقت از rm -rf
دستور زیر بسیار خطرناک است:
rm -rf "$path"
پیش از اجرای آن، مقدار متغیر را کنترل کنید:
printf 'Target: <%s>\n' "$path"
اشتباه پنجم: فرضکردن اینکه آرگومان وجود دارد
قبل از استفاده از $1 تعداد آرگومانها را بررسی کنید:
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <file>" >&2
exit 1
fi
۲۴. اجرای Bash در حالت بررسی نحوی
برای بررسی Syntax بدون اجرای واقعی:
bash -n script.sh
اگر خروجیای مشاهده نشود، معمولاً Syntax مشکلی ندارد.
برای اجرای مرحلهبهمرحله و مشاهده دستورات:
bash -x script.sh
یا در ابتدای اسکریپت:
set -x
و برای غیرفعالکردن آن:
set +x
۲۵. استفاده از ShellCheck
ابزار ShellCheck میتواند خطاها و الگوهای خطرناک Bash را شناسایی کند.
در توزیعهای مبتنی بر Debian و Ubuntu:
sudo apt update
sudo apt install shellcheck
بررسی اسکریپت:
shellcheck script.sh
ShellCheck میتواند مواردی مانند اینها را شناسایی کند:
کوتیشنگذاری ناقص؛
استفاده ناامن از متغیرها؛
خطاهای رایج در حلقهها؛
اشتباهات Syntax؛
الگوهای منسوخ یا آسیبپذیر.
تمرینهای قسمت سوم
تمرین اول
اسکریپتی بنویسید که نام فایل را از کاربر دریافت و بررسی کند:
آیا فایل وجود دارد؟
آیا فایل قابل خواندن است؟
تعداد خطوط آن چند است؟
راهنمای شروع:
#!/usr/bin/env bash
read -r -p "File path: " file
if [[ -f "$file" && -r "$file" ]]; then
wc -l "$file"
else
echo "File does not exist or is not readable." >&2
exit 1
fi
تمرین دوم
اسکریپتی بنویسید که همه فایلهای .log را در مسیر مشخص پیدا کند و تعداد خطوط هرکدام را نمایش دهد.
تمرین سوم
اسکریپتی بسازید که یک گزینه از میان start، stop و restart دریافت کند و پیام مناسب نمایش دهد.
تمرین چهارم
اسکریپتی بنویسید که چند فایل را بهعنوان آرگومان دریافت کرده و برای هرکدام نوع فایل را با دستور file نمایش دهد.
جمعبندی قسمت سوم
در این قسمت وارد برنامهنویسی واقعی Bash شدیم و با مفاهیم بنیادی زیر آشنا شدیم:
ساختار اسکریپت و Shebang؛
تعریف و استفاده از متغیرها؛
متغیرهای محیطی و
export؛دریافت ورودی با
read؛آرگومانهای خط فرمان؛
کدهای خروجی؛
شرطهای
ifوcase؛حلقههای
for،whileوuntil؛آرایهها؛
محاسبات عددی؛
پردازش گزینهها با
getopts؛بررسی Syntax و خطایابی؛
و استفاده از ShellCheck.
از این مرحله به بعد، شما دیگر فقط دستورات لینوکس را اجرا نمیکنید؛ بلکه میتوانید ابزارهای شخصی و اتوماسیونهای کوچک خودتان را بسازید.
در قسمت چهارم چه میآموزیم؟
در قسمت بعد وارد مباحث حرفهایتر Bash میشویم:
توابع و طراحی ماژولار؛
مدیریت خطا؛
set -euo pipefail؛سیگنالها و
trap؛لاگگیری استاندارد؛
فایلهای موقت امن؛
پردازش JSON با
jq؛و ساخت اسکریپتهایی قابلاعتماد برای محیط سرور.
