كل سكريبت حقيقي يحتاج إلى اتخاذ قرارات. سكريبت النسخ الاحتياطي يجب أن يتوقف عندما يمتلئ القرص. سكريبت النشر يجب أن يرفض التشغيل على الفرع الخطأ. سكريبت فحص الصحة يجب أن ينبّه فريق الطوارئ حين يُعيد الخادم حالة غير 200. كل هذا يعتمد على بنية واحدة: الشرط. في هذا الدرس ستتقن الشروط في Bash بعمق كافٍ لكتابة منطق قرار بجودة الإنتاج.
كيف يُقيّم Bash الشروط
شروط Bash مبنية على أكواد الخروج وليس على قيم منطقية. كل أمر يُعيد عدداً صحيحاً بين 0 و 255. كود الخروج 0 يعني النجاح (صحيح)؛ وأي قيمة أخرى تعني الفشل (خطأ). الكلمة المفتاحية if تنفّذ أمراً وتتفرّع بناءً على كود خروجه.
# أبسط شرط ممكن: التفريع بناءً على كود خروج أي أمر
if ping -c1 -W1 8.8.8.8 >/dev/null 2>&1; then
echo "الشبكة متصلة"
else
echo "الشبكة منقطعة"
fi
# التحقق من كود الخروج يدوياً
grep -q "ERROR" /var/log/app.log
if [ $? -ne 0 ]; then
echo "لا توجد أخطاء"
fi
# الأسلوب الاصطلاحي — اختبار مباشر، لا حاجة لـ $?
if grep -q "ERROR" /var/log/app.log; then
echo "تم رصد أخطاء — تنبيه فريق الطوارئ"
fi
القاعدة الذهبية: اختبر دائماً أكواد الخروج لا المخرجات. تحليل مخرجات الأوامر هش؛ اختبار نجاح الأمر قوي. عندما تكتب if command; then فأنت تختبر كود الخروج مباشرة — دون subshell أو متغير أو مشاكل تقتيس.
[ ] مقابل [[ ]] — اعرف الفرق
[ ] هو أمر POSIX (/usr/bin/[). [[ ]] هو كلمة مفتاحية في Bash. في سكريبتات Bash، استخدم دائماً [[ ]]. ليست متاحة في /bin/sh، لكن للسكريبتات التي تبدأ بـ #!/usr/bin/env bash فهي الأداة الصحيحة لكل موقف.
الفروق العملية مهمة في الإنتاج:
تقسيم الكلمات:[ $var = "yes" ] ينفجر إذا احتوى $var على مسافات أو كان فارغاً. أما [[ $var == "yes" ]] فلا يُجري تقسيماً للكلمات حتى بدون تقتيس.
مطابقة الأنماط:[[ $branch == release/* ]] يدعم أنماط glob بصورة أصلية — دون حاجة لتقتيس الجانب الأيمن.
مطابقة التعبيرات النظامية:[[ $line =~ ^[0-9]+$ ]] يدعم POSIX ERE الكاملة. يجب أن يكون التعبير النظامي غير مقتَّس.
العوامل المنطقية:[[ $a == "x" && $b == "y" ]] تستخدم && و|| داخل الأقواس. مع [ ] عليك استخدام -a و-o اللتين عفا عليهما الزمن.
#!/usr/bin/env bash
set -euo pipefail
branch="release/2.4.1"
version="42"
filename="report 2024.csv"
# --- اختبارات النصوص مع [[ ]] ---
# المساواة وعدم المساواة
[[ "$branch" == "main" ]] && echo "على الفرع الرئيسي"
[[ "$branch" != "main" ]] && echo "ليس على الفرع الرئيسي"
# مطابقة glob — بدون تقتيس النمط
[[ "$branch" == release/* ]] && echo "تم رصد فرع إصدار"
# مطابقة تعبير نظامي — يجب أن يكون التعبير غير مقتَّس
[[ "$version" =~ ^[0-9]+$ ]] && echo "الإصدار رقمي"
# نص فارغ / غير فارغ
[[ -z "$branch" ]] && echo "الفرع فارغ"
[[ -n "$branch" ]] && echo "الفرع محدد"
# آمن حتى مع المسافات — [[ ]] لا يُجري تقسيماً للكلمات
[[ "$filename" == *.csv ]] && echo "ملف CSV"
# --- الدمج المنطقي ---
[[ "$branch" == release/* && -n "$version" ]] && echo "إصدار برقم إصدار"
اختبارات الملفات
سكريبتات DevOps تستجوب نظام الملفات باستمرار — فحص وجود ملف الإعداد قبل قراءته، وكتابية الدليل قبل النشر فيه، ووجود المقبس قبل إرسال حركة المرور. توفر Bash مجموعة كاملة من عوامل اختبار الملفات:
CONFIG="/etc/app/config.yaml"
LOG_DIR="/var/log/app"
SOCKET="/run/gunicorn.sock"
BINARY="/usr/local/bin/deploy-tool"
# اختبارات الوجود
[[ -e "$CONFIG" ]] && echo "موجود (ملف أو مجلد)"
[[ -f "$CONFIG" ]] && echo "ملف عادي"
[[ -d "$LOG_DIR" ]] && echo "مجلد"
[[ -L "$CONFIG" ]] && echo "رابط رمزي"
[[ -S "$SOCKET" ]] && echo "مقبس"
# اختبارات الصلاحيات
[[ -r "$CONFIG" ]] && echo "قابل للقراءة"
[[ -w "$LOG_DIR" ]] && echo "قابل للكتابة"
[[ -x "$BINARY" ]] && echo "قابل للتنفيذ"
# اختبار الحجم
[[ -s "$CONFIG" ]] && echo "ملف غير فارغ"
# حماية عملية: توقف إذا كان الإعداد مفقوداً
if [[ ! -f "$CONFIG" ]]; then
echo "خطأ: لم يُعثر على الإعداد في $CONFIG" >&2
exit 1
fi
# حماية عملية: إنشاء مجلد السجلات إذا لم يكن موجوداً
if [[ ! -d "$LOG_DIR" ]]; then
mkdir -p "$LOG_DIR"
chmod 750 "$LOG_DIR"
fi
المقارنات الرقمية
عوامل المساواة النصية (==، !=) تُجري مقارنة معجمية. للأرقام استخدم العوامل الحسابية: -eq، -ne، -lt، -le، -gt، -ge. بديلاً، استخدم سياق الحساب (( )) الذي يعامل القيم كأعداد صحيحة ويُعيد كود خروج 0 للقيم غير الصفرية (صحيح) و1 للصفر (خطأ).
disk_usage=87
max_allowed=80
http_status=503
retry_count=3
# مع [[ ]] وعوامل الحساب
if [[ "$disk_usage" -gt "$max_allowed" ]]; then
echo "تنبيه: استخدام القرص ${disk_usage}% يتجاوز الحد" >&2
fi
# مع (( )) — سياق حسابي، أكثر وضوحاً للرياضيات
if (( http_status >= 500 )); then
echo "خطأ في الخادم: $http_status"
fi
if (( retry_count == 0 )); then
echo "لا محاولات متبقية — فشل النشر"
exit 1
fi
# مركّب: القرص والعقد كلاهما بخير
used_inodes=$(df -i /var | awk 'NR==2{print $5}' | tr -d '%')
if (( disk_usage < 90 && used_inodes < 90 )); then
echo "صحة القرص جيدة"
fi
استخدم (( )) للشروط الحسابية. أوضح وأكثر إيجازاً، ويدعم عوامل أسلوب C مثل ++ و% و**، ويتجنب ضوضاء -gt/-lt. فقط انتبه أن (( 0 )) يُعيد كود خروج 1 (خطأ منطقي) — مفيد عند عدّ المحاولات نزولاً نحو الصفر.
جملة case
عندما يتحكم متغير واحد في فروع متعددة، فإن سلسلة من كتل elif تكون أصعب قراءةً وصيانةً من جملة case. تدعم case أنماط glob والتبادل باستخدام | وحالة شاملة *). هي الطريقة الاصطلاحية للتوزيع بناءً على أسماء البيئات أو مستويات السجلات أو أساليب HTTP أو الأوامر الفرعية.
#!/usr/bin/env bash
set -euo pipefail
ENV="${1:-}" # الوسيطة الأولى، فارغة إذا لم تُقدَّم
HTTP_METHOD="POST"
# --- التوزيع بناءً على اسم البيئة ---
case "$ENV" in
production|prod)
REPLICAS=10
LOG_LEVEL="warn"
;;
staging|stage)
REPLICAS=2
LOG_LEVEL="info"
;;
development|dev|"")
REPLICAS=1
LOG_LEVEL="debug"
;;
*)
echo "خطأ: بيئة غير معروفة '$ENV'" >&2
echo "الاستخدام: $0 {production|staging|development}" >&2
exit 1
;;
esac
echo "نشر بـ $REPLICAS نسخ، مستوى السجل=$LOG_LEVEL"
# --- التوزيع بناءً على أسلوب HTTP (مفيد في معالجات webhook) ---
case "$HTTP_METHOD" in
GET|HEAD) echo "طلب للقراءة فقط" ;;
POST|PUT|PATCH) echo "طلب تعديلي — التحقق من الجسم" ;;
DELETE) echo "طلب تدميري — يتطلب تأكيداً" ;;
*) echo "أسلوب غير معروف: $HTTP_METHOD"; exit 1 ;;
esac
أضف دائماً حالة شاملة *). جملة case بدون حالة شاملة تُنفّذ لا شيء بصمت حين لا تُطابق القيمةُ أيَّ نمط — مصدر شائع للأخطاء حيث تُسبّب الأخطاء المطبعية في اسم البيئة تشغيل النشر دون أي إعداد. اجعل الحالة الشاملة تُخطئ بصوت عالٍ وتخرج بكود غير صفري.
مخطط: تدفق القرار الشرطي في سكريبت نشر
يُسلسل سكريبت النشر حراساً شرطية متعددة قبل الوصول إلى توزيع البيئة.
دمج الشروط: أنماط حقيقية
سكريبتات الإنتاج نادراً ما تختبر شرطاً واحداً. إليك دالة فحص صحة واقعية تجمع اختبارات الملفات والمقارنات الرقمية واختبارات النصوص بالأسلوب الذي ستراه في كود البنية التحتية الضخم:
#!/usr/bin/env bash
set -euo pipefail
APP_PID_FILE="/run/app/app.pid"
APP_URL="http://localhost:8080/healthz"
MAX_RESPONSE_MS=500
check_health() {
local pid status elapsed
# 1. ملف PID يجب أن يكون موجوداً وغير فارغ
if [[ ! -s "$APP_PID_FILE" ]]; then
echo "حرج: ملف PID مفقود أو فارغ" >&2
return 1
fi
pid=$(cat "$APP_PID_FILE")
# 2. يجب أن تكون العملية تعمل فعلاً
if ! kill -0 "$pid" 2>/dev/null; then
echo "حرج: العملية $pid لا تعمل" >&2
return 1
fi
# 3. نقطة نهاية الصحة يجب أن تُعيد 200
read -r status elapsed <<< "$(curl -o /dev/null -s \
-w "%{http_code} %{time_total_ms}" \
--max-time 2 "$APP_URL")"
if [[ "$status" != "200" ]]; then
echo "حرج: healthz أعاد HTTP $status" >&2
return 1
fi
# 4. يجب أن يكون الرد سريعاً بما يكفي
if (( elapsed > MAX_RESPONSE_MS )); then
echo "تحذير: healthz بطيء — ${elapsed}ms > ${MAX_RESPONSE_MS}ms" >&2
return 1
fi
echo "جيد: التطبيق سليم (HTTP $status، ${elapsed}ms)"
}
check_health || { notify_oncall "فشل فحص صحة التطبيق"; exit 1; }
نمط إنتاجي — دوال حارسة لا سكريبتات. لفّ كل فحص في دالة تُعيد كود خروج ذا معنى. المُستدعي يقرر ما إذا كان سيُنبّه فريق الطوارئ أو يُعيد المحاولة أو يخرج بقوة. هذا الفصل للمخاوف هو كيفية كتابة فرق SRE للإجراءات التشغيلية القابلة للاختبار المستقل.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية