Bash for Oracle DBAs Part 11: Error Handling, Strict Mode, and Robust Scripting

Bash for Oracle DBAs — قسمت ۱۱: Error Handling، Strict Mode و اسکریپت‌نویسی تاب‌آور

در بخش‌های قبلی این مجموعه، با ساختارها و ابزارهای مختلف Bash آشنا شدیم و در قسمت ۷ نیز آموختیم که چگونه کدهای خود را در قالب توابع (Functions) بازنویسی و سازماندهی کنیم. اما یک اسکریپت در محیط‌های حساس عملیاتی Oracle، فراتر از اجرای موفق دستورات در شرایط عادی، باید بتواند شرایط غیرعادی و خطاها را مدیریت کند.

وقتی اسکریپت‌های Hardening و Remediation را اجرا می‌کنیم، وقوع خطا (مانند نبود دسترسی به یک فایل سیستم، قطع اتصال شبکه، یا شکست یکی از دستورات میانی در یک Pipeline) بدون مدیریت صحیح، می‌تواند فاجعه‌بار باشد. در این مقاله به بررسی عمیق روش‌های مدیریت خطا، رفتارهای پیش‌فرض پوسته Bash، حالت Strict Mode و مکانیزم‌های بازگردانی تغییرات در صورت شکست می‌پردازیم.

۱) رفتار پیش‌فرض Bash در مواجهه با خطا

به‌طور پیش‌فرض، وقتی یکی از دستورات درون اسکریپت Bash با خطا مواجه می‌شود (یعنی Exit Status غیر صفر برمی‌گرداند)، مفسر Bash اجرای اسکریپت را متوقف نمی‌کند، بلکه بدون توجه به خطای رخ‌داده، سراغ خط بعدی می‌رود.

این رفتار در اسکریپت‌های DBA بسیار خطرناک است. به عنوان مثال، فرض کنید می‌خواهید از یک فایل پیکربندی حیاتی مانند /etc/ssh/sshd_config نسخه پشتیبان تهیه کنید و سپس مقدار یک Directive را تغییر دهید:

cp /etc/ssh/sshd_config /var/backup/sshd_config.bak
sed -i 's/PermitRootLogin yes/PermitRootLogin no/g' /etc/ssh/sshd_config

اگر دایرکتوری /var/backup وجود نداشته باشد یا پر شده باشد، دستور اول شکست می‌خورد. اما Bash بدون هیچ هشداری دستور دوم را اجرا می‌کند. در نتیجه، شما بدون داشتن نسخه پشتیبان، فایل اصلی را ویرایش کرده‌اید.

۲) پیاده‌سازی حالت سخت‌گیرانه (Bash Strict Mode)

برای تغییر این رفتار پیش‌فرض و بالا بردن امنیت اسکریپت‌ها، از پیکربندی معروف به Strict Mode استفاده می‌کنیم. این کار با قرار دادن دستور زیر در ابتدای اسکریپت انجام می‌شود:

set -euo pipefail

این دستور ترکیبی از سه سوییچ بسیار مهم است:

  • set -e (Exit on Error): به محض اینکه هر دستوری در اسکریپت با کد خروجی غیر صفر (خطا) پایان یابد، کل اسکریپت فوراً متوقف می‌شود.
  • set -u (Nounset): اگر اسکریپت تلاش کند از متغیری استفاده کند که قبلاً تعریف یا مقداردهی نشده است، به عنوان خطا در نظر گرفته شده و اسکریپت متوقف می‌شود (جلوگیری از رفتارهای پیش‌بینی‌نشده به دلیل اشتباه تایپی در نام متغیرها).
  • set -o pipefail: به‌طور پیش‌فرض، وضعیت خروجی یک Pipeline (مثلا cmd1 | cmd2) برابر با وضعیت آخرین دستور (cmd2) است. با فعال کردن این گزینه، اگر هرکدام از دستورات درون زنجیره پایپ با خطا مواجه شوند، کل پایپ لاین با شکست مواجه شده و اسکریپت متوقف می‌شود.

۳) استثنائات در set -e (چگونه از توقف ناخواسته جلوگیری کنیم؟)

گاهی اوقات شکست یک دستور به معنای وقوع یک خطای بحرانی نیست و می‌خواهیم در صورت شکست، اسکریپت به کار خود ادامه دهد یا خطا را مدیریت کند. در حالت فعال بودن set -e، برای جلوگیری از توقف اسکریپت می‌توانیم از اپراتورهای منطقی یا شرط‌ها استفاده کنیم:

# روش ۱: استفاده از OR منطقی برای مهار خطای یک دستور مشخص
grep -q "PermitRootLogin no" /etc/ssh/sshd_config || echo "Setting is missing!"

# روش ۲: استفاده در ساختارهای شرطی (در این حالت شکست دستور متوقف‌کننده نیست)
if grep -q "Banner" /etc/ssh/sshd_config; then
  echo "Banner is configured."
fi

۴) مدیریت سیگنال‌ها و شرایط خروج با trap

یکی از قوی‌ترین مکانیزم‌های پایداری در Bash، دستور trap است. با استفاده از این ابزار می‌توانیم ثبت کنیم که در صورت خروج اسکریپت (موفق یا ناموفق)، یا در صورت وقوع خطاهای غیرمنتظره، کارهای مشخصی (مانند پاک‌سازی فایل‌های موقت، بازگرداندن فایل‌های پشتیبان یا ثبت لاگ نهایی) انجام شود.

سیگنال‌های رایج برای ثبت با trap:

  • EXIT: هر زمان که اسکریپت به هر دلیلی (اتمام کار، خطای داخلی یا دریافت سیگنال خروج) پایان یابد.
  • ERR: هر زمان که دستوری با کد خروجی غیر صفر (خطا) اجرا شود (زمانی که نیاز به بررسی دقیق‌تر خطا داریم).
#!/bin/bash
set -euo pipefail

# تعریف تابع پاک‌سازی فایل‌های موقت
cleanup() {
  echo "Performing cleanup..."
  rm -f /tmp/audit_temp_*.txt
}

# ثبت تابع cleanup برای اجرا در هنگام خروج از اسکریپت
trap cleanup EXIT

# بدنه اصلی اسکریپت
touch /tmp/audit_temp_output.txt
# دستورات بررسی و حسابرسی دیتابیس...

۵) مدیریت تراکنش‌ها و مکانیزم Rollback در زمان تغییرات (Remediation)

هنگامی که یک اسکریپت Remediation می‌نویسید که قرار است فایل‌های پیکربندی سیستم‌عامل یا سرویس‌ها را تغییر دهد، ایجاد یک مکانیزم بازگشت (Rollback) در صورت بروز خطا بسیار حیاتی است. ساختار زیر نشان می‌دهد چطور با ترکیب توابع، trap و متغیرها یک فرآیند تغییر ایمن بسازیم:

#!/bin/bash
set -euo pipefail

TARGET_FILE="/etc/ssh/sshd_config"
BACKUP_FILE="/etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)"
TRANSACTION_SUCCESS=false

rollback() {
  if [[ "$TRANSACTION_SUCCESS" = false ]]; then
    if [[ -f "$BACKUP_FILE" ]]; then
      echo "An error occurred. Rolling back changes to $TARGET_FILE..."
      cp "$BACKUP_FILE" "$TARGET_FILE"
      rm -f "$BACKUP_FILE"
    fi
  else
    # در صورت موفقیت، فقط بکاپ موقت را نگهداری یا بایگانی می‌کنیم
    echo "Remediation completed successfully. Backup saved at: $BACKUP_FILE"
  fi
}

# در صورت بروز هرگونه خطا یا خروج، تابع rollback فراخوانی می‌شود
trap rollback EXIT ERR

# ۱. تهیه پشتیبان قبل از اعمال هرگونه تغییر
cp "$TARGET_FILE" "$BACKUP_FILE"

# ۲. اعمال تغییرات با استفاده از sed (تغییر پورت SSH به عنوان مثال)
sed -i 's/^#\?Port 22/Port 2222/g' "$TARGET_FILE"

# ۳. اعتبارسنجی سینتکس فایل پیکربندی جدید
sshd -t

# ۴. اگر به این مرحله برسیم، یعنی همه چیز بدون خطا انجام شده است
TRANSACTION_SUCCESS=true

۶) تفکیک جریان‌های لاگ‌گیری (Stdout vs Stderr)

همان‌طور که در قسمت ۸ یاد گرفتیم، خروجی‌های استاندارد و خطا باید متمایز باشند. در اسکریپت‌های بزرگ، تمام پیام‌های مربوط به خطا و وضعیت‌های غیرعادی باید به خروجی استاندارد خطا (stderr) هدایت شوند. این کار اجازه می‌دهد تا خروجی‌های عادی اسکریپت (مثلاً لیستSIDها یا گزارش‌ها) را بدون تداخل با پیام‌های خطای سیستم مانیتور کنیم.

log_error() {
  local msg="$1"
  echo -e "\e[31m[ERROR]\e[0m $msg" >&2
}

log_warning() {
  local msg="$1"
  echo -e "\e[33m[WARNING]\e[0m $msg" >&2
}

۷) خلاصه و چک‌لیست پایداری اسکریپت برای DBAها

  • همیشه در ابتدای اسکریپت‌های عملیاتی از set -euo pipefail استفاده کنید.
  • قبل از ویرایش مستقیم فایل‌ها با دستوراتی مثل sed، حتماً با cp یک نسخه پشتیبان با درج زمان ایجاد کنید.
  • برای پاک‌سازی فایل‌های موقت در تمام سناریوهای خروج (موفقیت یا شکست)، بلاک trap ... EXIT بنویسید.
  • خروجی دستورات حساس و ابزارهای اعتبارسنجی (مانند sshd -t یا visudo -c) را پس از تغییرات بررسی کنید.
  • برای لاگ‌های سیستمی و پیام‌های بحرانی خطا، حتما خروجی را با استفاده از >&2 به مجرای خطا منتقل کنید.

۸) جمع‌بندی

نوشتن یک اسکریپت پایدار و ضد خطا، تفاوت اصلی میان یک مدیر سیستم یا DBA تازه‌کار با یک متخصص با تجربه است. با یادگیری تکنیک‌های کنترل خطا و ترکیب صحیح آن‌ها با دستوراتی نظیر trap، می‌توانیم با اطمینان خاطر اسکریپت‌های خودکارسازی را روی دیتابیس‌ها و سیستم‌های عملیاتی توزیع و اجرا کنیم.

با اتمام این بخش، تمامی مبانی فنی و دستورات مورد نیاز برای درک و نوشتن اسکریپت‌های مدیریت سیستم و Hardening را فرا گرفته‌ایم. در قسمت پایانی (قسمت ۱۲)، به سراغ کالبدشکافی یک پروژه واقعی خواهیم رفت و یک اسکریپت جامع فرآیند حسابرسی (Audit) و اصلاح (Remediation) را بررسی می‌کنیم.


مقاله قبلی:
Bash for Oracle DBAs — Part 10: Arrays, case, and Script Arguments in Bash

مقاله بعدی:
Bash for Oracle DBAs — Part 12: Final Project — Hardening Script Case Study