بازگشت به لیست مقالات

آموزش جامع لینوکس و Bash؛ قسمت چهارم برنامه‌نویسی پیشرفته در Bash: توابع ماژولار، مدیریت خطای حرفه‌ای و کدهای آماده استقرار (Production-Ready)

#Bash پیشرفته#مدیریت خطا در Bash#اتوماسیون لینوکس
آموزش جامع لینوکس و Bash؛ قسمت چهارم  برنامه‌نویسی پیشرفته در Bash: توابع ماژولار، مدیریت خطای حرفه‌ای و کدهای آماده استقرار (Production-Ready)

برنامه‌نویسی پیشرفته در 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

بیایید دقیقاً بررسی کنیم هر بخش چه می‌کند:

  1. set -e (Exit on Error):اگر دستوری کدی غیر از 0 برگرداند، اجرای اسکریپت فوراً متوقف می‌شود (مگر اینکه درون یک شرط if بررسی شده باشد).

  2. set -u (Nounset):اگر از متغیری استفاده کنید که از قبل تعریف نشده، اسکریپت را متوقف کرده و خطای unbound variable صادر می‌کند.مثال فاجعه‌بار: در صورت نبود -u، اگر متغیر target_dir مقداردهی نشده باشد، دستور rm -rf /$target_dir به rm -rf / تبدیل خواهد شد!

  3. 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.

مطالب مرتبط :

در قسمت پنجم و پایانی این دوره جامع:

  • زمان‌بندی و اتوماسیون با Systemd Timers و Cron Jobs.

  • ساخت سرویس‌های اختصاصی پس‌زمینه (Custom Systemd Daemons).

  • مانیتورینگ سلامت سرور و سیستم اعلان هشدار (Alerting) به تلگرام / دیسکورد / ایمیل.

  • اجرای ریموت اسکریپت‌ها روی چند سرور از طریق SSH و کلیدهای امنیتی.

محتوای ویژه
💬

نظرات کاربران

نظرات کاربران

برای ثبت نظر، لطفاً ابتدا وارد حساب کاربری خود شوید.

ورود / ثبت نام

در حال بارگذاری نظرات...

محتوای ویژه

محتوای ویژه