برنامهنویسی پیشرفته در Bash: توابع ماژولار، مدیریت خطای حرفهای و کدهای آماده استقرار (Production-Ready)
در سه قسمت پیشین این دوره، از مبانی سیستمعامل لینوکس آغاز کردیم، توانمندیهای خط لولهها و پردازش متن را شناختیم و سپس وارد دنیای مقدماتی اسکریپتنویسی با متغیرها، شروط و حلقهها شدیم. تفاوت یک «توسعهدهنده تازهکار Bash» با یک «متخصص حرفهای DevOps و System Administration» در این نقطه مشخص میشود: آیا کدهایی که مینویسید در محیطهای واقعی سروری، شکستهای پیشبینینشده، فایلهای سنگین و سناریوهای بحرانی پایدار میمانند یا باعث فاجعه میشوند؟
در این مقاله، ساختار اسکریپتهای Bash را ارتقا میدهیم تا به سطحی صنعتی، قابل نگهداری، ماژولار و بسیار پایدار برسند.
۱. توابع (Functions) در Bash و معماری ماژولار
برای جلوگیری از تکرار کد (DRY) و خواناتر شدن منطق برنامه، از توابع استفاده میکنیم.
سینتکس استاندارد تعریف تابع
در Bash دو شیوه برای نگارش تابع وجود دارد، اما شیوه توصیهشده و سازگارتر به صورت زیر است:
function_name() {
# بدنه تابع
echo "This is a function"
}
محدوده متغیرها: فاجعه متغیرهای عمومی (Global)
به صورت پیشفرض در Bash، متغیرهایی که درون تابع تعریف میشوند Global هستند و میتوانند متغیرهای بیرونی را ناخواسته بازنویسی (Overwrite) کنند!
برای حفظ استقلال تابع، الزامی است از کلمه کلیدی local استفاده شود:
#!/usr/bin/env bash
# متغیر سراسری
user="root"
set_temp_user() {
# استفاده از local محدوده متغیر را درون همین تابع ایزوله میکند
local user="deploy_agent"
echo "[Inside function] User: $user"
}
set_temp_user
echo "[Outside function] User: $user"
خروجی:
[Inside function] User: deploy_agent
[Outside function] User: root
پاس دادن آرگومان به توابع و خروجیها
توابع در Bash آرگومانها را دقیقاً مشابه اسکریپت اصلی از طریق $1, $2, $@ دریافت میکنند (پارامترهای اسکریپت در زمان اجرای تابع پنهان شده و جای خود را به پارامترهای تابع میدهند):
#!/usr/bin/env bash
calculate_backup_name() {
local service_name="$1"
local timestamp
timestamp="$(date +%Y%m%d_%H%M%S)"
# بازگرداندن داده به بیرون تابع از طریق stdout
echo "${service_name}_backup_${timestamp}.tar.gz"
}
# دریافت خروجی از طریق Command Substitution
archive_file="$(calculate_backup_name "postgres")"
echo "Generated filename: $archive_file"
نکته کلیدی: دستور
returnدر توابع فقط برای ارسال «کد وضعیت» (Status Code عددی بین ۰ تا ۲۵۵) به کار میرود و رشته برنمیگرداند. برای برگرداندن داده ازechoیا متغیرهای بازتابی استفاده میشود.
۲. حالت سختگیرانه: سهگانه طلایی set -euo pipefail
به طور پیشفرض، اگر دستوری در Bash با شکست مواجه شود، اسکریپت متوقف نمیشود و خطوط بعدی را با خوشبینی اجرا میکند. این رفتار میتواند در دستوراتی مانند rm منجر به حذف اشتباهی ریشه سیستم شود!
برای تضمین رفتاری شبیه به زبانهای سطح بالا، همیشه ابتدای اسکریپتهای مهم را با این سهگانه آغاز کنید:
set -euo pipefail
بیایید دقیقاً بررسی کنیم هر بخش چه میکند:
set -e(Exit on Error):اگر دستوری کدی غیر از0برگرداند، اجرای اسکریپت فوراً متوقف میشود (مگر اینکه درون یک شرطifبررسی شده باشد).set -u(Nounset):اگر از متغیری استفاده کنید که از قبل تعریف نشده، اسکریپت را متوقف کرده و خطایunbound variableصادر میکند.مثال فاجعهبار: در صورت نبود-u، اگر متغیرtarget_dirمقداردهی نشده باشد، دستورrm -rf /$target_dirبهrm -rf /تبدیل خواهد شد!set -o pipefail:در خط لولههایی مانندcmd1 | cmd2 | cmd3، به طور معمول وضعیت موفقیت بر اساس دستور آخر (cmd3) تعیین میشود حتی اگرcmd1شکست خورده باشد. با فعالسازی این پرچم، اگر هر بخش از پایپلاین شکست بخورد، کل پایپلاین ناموفق قلمداد خواهد شد.
۳. مدیریت رویدادها، سیگنالها و پاکسازی با trap
هنگامی که اسکریپت ناگهان با فشردن کلید Ctrl+C متوقف میشود یا به دلیل خطا خاتمه مییابد، ممکن است فایلهای موقت، قفلها (Locks) و پورتهای بازی باقی بمانند. دستور trap مانند بلوک finally در برنامهنویسی مدرن عمل میکند.
پاکسازی خودکار منابع موقت
#!/usr/bin/env bash
set -euo pipefail
# ساخت مسیر موقت امن
TEMP_DIR="$(mktemp -d /tmp/tivacode_job.XXXXXX)"
cleanup() {
local exit_code=$?
echo "[*] Cleaning up temporary resources at: $TEMP_DIR"
rm -rf "$TEMP_DIR"
exit "$exit_code"
}
# به دام انداختن سیگنالهای EXIT, SIGINT, SIGTERM
trap cleanup EXIT SIGINT SIGTERM
echo "Doing heavy operations in $TEMP_DIR..."
touch "$TEMP_DIR/data.csv"
sleep 5
echo "Task finished."
حتی اگر کاربر اسکریپت را وسط کار با Ctrl+C قطع کند، تابع cleanup فراخوانی شده و پوشه موقت پاک میشود.
۴. ساخت سیستم لاگگیری استاندارد (Structured Logging)
چاپ مستقیم خروجیها بدون برچسب زمانی و سطح اهمیت (Log Levels) عیبیابی در پروداکشن را ناممکن میکند. بیایید یک موتور لاگگیری شکیل و استاندارد پیادهسازی کنیم:
#!/usr/bin/env bash
# کدهای رنگی ANSI
readonly COLOR_RESET="\033[0m"
readonly COLOR_INFO="\033[0;32m"
readonly COLOR_WARN="\033[0;33m"
readonly COLOR_ERROR="\033[0;31m"
log_info() {
printf "${COLOR_INFO}[INFO] %s - %s${COLOR_RESET}\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
log_warn() {
printf "${COLOR_WARN}[WARN] %s - %s${COLOR_RESET}\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
log_error() {
printf "${COLOR_ERROR}[ERROR] %s - %s${COLOR_RESET}\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
# تست سیستم لاگ
log_info "شروع سرویس پشتیبانگیری پایگاه داده..."
log_warn "حجم لاگها به ۷۵٪ ظرفیت رسیده است."
log_error "اتصال به پایگاه داده با خطا مواجه شد!"
۵. کار با فایلهای موقت امن و قفل کردن پروسهها (Locking)
برای جلوگیری از اجرای همزمان چند نمونه (Instance) از یک اسکریپت اتوماسیون (Race Conditions)، از قفلهای فایلی با دستور flock استفاده میکنیم:
#!/usr/bin/env bash
set -euo pipefail
LOCK_FILE="/tmp/tivacode_cron.lock"
# ایجاد و بررسی قفل پروسس
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
echo "[ERROR] Instance of this script is already running. Aborting." >&2
exit 1
fi
echo "Running mission-critical automated job with exclusive lock..."
sleep 10
echo "Finished."
۶. متغیرهای پارامتر پیشرفته (Parameter Expansion)
به جای فراخوانی ابزارهای سنگین بیرونی مانند awk یا sed برای کارهای ساده رشتهای، Bash امکانات درونی بسیار سریعی دارد:
FILENAME="tivacode_backup_2026.tar.gz"
# ۱. جایگزینی پیشفرض اگر متغیر خالی باشد:
PORT="${APP_PORT:-8080}"
# ۲. برش از انتهای رشته (حذف کوچکترین پسوند):
echo "${FILENAME%.*}" # tivacode_backup_2026.tar
# ۳. برش از انتهای رشته (حذف تمام پسوندها):
echo "${FILENAME%%.*}" # tivacode_backup_2026
# ۴. برش از ابتدای رشته (حذف پیشوند):
echo "${FILENAME#tivacode_}" # backup_2026.tar.gz
# ۵. جایگزینی متن در رشته:
APP_PATH="/var/www/site"
echo "${APP_PATH/site/blog}" # /var/www/blog
۷. پردازش مدرن دادههای JSON با ابزار jq
در سیستمهای DevOps امروزی، اسکریپتها اغلب با APIها و فرمتهای JSON سر و کار دارند. ترکیب Bash با ابزار سبک و پرقدرت jq بهترین الگو برای استخراج داده است:
فرض کنید خروجی یک API به شکل زیر است:
{
"status": "healthy",
"nodes": [
{"name": "web-01", "ip": "192.168.1.10", "active": true},
{"name": "web-02", "ip": "192.168.1.11", "active": false}
]
}
نحوه استخراج اطلاعات در اسکریپت Bash:
#!/usr/bin/env bash
set -euo pipefail
RESPONSE='{"status":"healthy","nodes":[{"name":"web-01","ip":"192.168.1.10","active":true},{"name":"web-02","ip":"192.168.1.11","active":false}]}'
# استخراج وضعیت کلی
STATUS=$(echo "$RESPONSE" | jq -r '.status')
echo "Cluster Status: $STATUS"
# فیلتر کردن و استخراج IP نودهای فعال
echo "Active Nodes IP:"
echo "$RESPONSE" | jq -r '.nodes[] | select(.active == true) | .ip'
۸. پروژه عملی جامع: اسکریپت پشتیبانگیری ماژولار و امن
بیایید تمام مفاهیم این درس (ماژولار بودن، set -euo pipefail، trap، سیستم لاگ و flock) را در یک اسکریپت عملیاتی واقعی یکپارچه کنیم:
#!/usr/bin/env bash
# ==============================================================================
# Script Name: backup_manager.sh
# Purpose: Enterprise automated directory backup with retention & error safety
# Author: TivaCode Engineering Team
# ==============================================================================
set -euo pipefail
# ----------------- پیکربندی اولیه -----------------
readonly BACKUP_SRC="${1:-/var/www/html}"
readonly BACKUP_DEST="${2:-/opt/backups}"
readonly RETENTION_DAYS=7
readonly LOCK_FILE="/tmp/tivacode_backup.lock"
# ----------------- ماژول لاگگیری -----------------
log() {
printf "[%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
error_exit() {
printf "[ERROR] [%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
exit 1
}
# ----------------- پاکسازی و مدیریت خروج -----------------
cleanup() {
local exit_code=$?
if [[ -d "${TEMP_DIR:-}" ]]; then
log "Removing temporary files..."
rm -rf "$TEMP_DIR"
fi
exit "$exit_code"
}
trap cleanup EXIT SIGINT SIGTERM
# ----------------- منطق اجرایی اصلی -----------------
main() {
# ۱. اطمینان از عدم اجرای همزمان
exec 200>"$LOCK_FILE"
if ! flock -n 200; then
error_exit "A backup process is already running. Halting."
fi
log "Validating source directory: $BACKUP_SRC"
[[ -d "$BACKUP_SRC" ]] || error_exit "Source directory does not exist!"
mkdir -p "$BACKUP_DEST"
# ۲. ایجاد محیط موقت
TEMP_DIR="$(mktemp -d /tmp/backup_archive.XXXXXX)"
local archive_name="backup_$(date +%Y%m%d_%H%M%S).tar.gz"
local temp_archive="$TEMP_DIR/$archive_name"
# ۳. فشردهسازی و تهیه آرشیو
log "Creating compressed archive..."
tar -czf "$temp_archive" -C "$BACKUP_SRC" .
# ۴. انتقال آرشیو نهایی به مقصد با تأیید صحت
log "Moving archive to destination: $BACKUP_DEST"
mv "$temp_archive" "$BACKUP_DEST/$archive_name"
# ۵. چرخش و حذف فایلهای قدیمیتر از ۷ روز
log "Applying retention policy: removing backups older than $RETENTION_DAYS days..."
find "$BACKUP_DEST" -type f -name "backup_*.tar.gz" -mtime +"$RETENTION_DAYS" -exec rm -f {} +
log "Backup completed successfully: $BACKUP_DEST/$archive_name"
}
main "$@"
جمعبندی و نکات کلیدی قسمت چهارم
در این قسمت آموختیم که چگونه کدهای شکننده Bash را به برنامههایی استوار برای محیطهای Production تبدیل کنیم:
تعریف استاندارد توابع و استفاده ضروری از
local.محافظت از اسکریپتها با سهگانه
set -euo pipefail.تضمین پایداری و آزادسازی منابع با
trapدر برابر وقفههای ناگهانی.ساخت سیستم لاگگیری ساختیافته و رنگی برای پایش دقیقتر رویدادها.
استفاده از مکانیزم Lock جهت ممانعت از اجرای همپوشان چند اسکریپت.
تجزیه سریع متن و JSON با Parameter Expansion و ابزار قدرتمند
jq.
مطالب مرتبط :
قسمت اول: مبانی لینوکس، درخت دایرکتوریها (FHS) و دستورات اصلی.
قسمت دوم: قدرت Pipeها، ورودی/خروجی استاندارد و فیلترهای
grep،sedوawk.قسمت سوم: شروع اسکریپتنویسی Bash، متغیرها، شرطها و حلقهها.
قسمت چهارم: الگوهای پیشرفته، توابع ماژولار،
set -euo pipefailو مدیریت خطایtrap.
در قسمت پنجم و پایانی این دوره جامع:
زمانبندی و اتوماسیون با Systemd Timers و Cron Jobs.
ساخت سرویسهای اختصاصی پسزمینه (Custom Systemd Daemons).
مانیتورینگ سلامت سرور و سیستم اعلان هشدار (Alerting) به تلگرام / دیسکورد / ایمیل.
اجرای ریموت اسکریپتها روی چند سرور از طریق SSH و کلیدهای امنیتی.
