السكريبتات الإنتاجية نادرًا ما تنفذ عملية واحدة مرة واحدة. فهي تنشر التحديثات على عشرة خوادم، وتدور ثلاثين ملف سجل، وتتحقق من كل سطر في ملف إعدادات، أو تعيد تشغيل خدمة حتى تستعيد عافيتها. الحلقات هي ما يحوّل أمرًا منفردًا إلى أتمتة شاملة. يغطي هذا الدرس أربعة أنماط للحلقات ستستخدمها يوميًا: حلقة for-in للقوائم، وحلقة while القائمة على شرط، وقراءة الملفات سطرًا بسطر، وحلقة الـ glob لمعالجة محتويات المجلدات بأمان.
حلقة for-in: التكرار على قائمة
أكثر حلقات Bash شيوعًا هي التي تتكرر على قائمة كلمات مفصولة بمسافات. صيغتها for variable in list; do ... done. يمكن أن تكون القائمة كلمات حرفية، أو استبدال أمر، أو توسيع بالأقواس، أو نمط glob.
#!/usr/bin/env bash
set -euo pipefail
# --- قائمة حرفية ---
ENVS=(staging canary production)
for env in "${ENVS[@]}"; do
echo "Deploying to: ${env}"
# ./deploy.sh "${env}"
done
# --- توسيع بالأقواس (تسلسل) ---
for i in {1..5}; do
echo "Attempt ${i}"
done
# --- استبدال أمر: التكرار على نطاقات kubectl ---
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
echo "Namespace: ${ns}"
done
فخ تقسيم الكلمات: لا تستخدم أبدًا for item in $(command) عندما يمكن أن يحتوي الناتج على مسافات أو أسطر جديدة داخل عنصر منطقي واحد. فأسماء الملفات التي تحتوي على مسافات ستُقسَّم إلى تكرارات منفصلة. استخدم while IFS= read -r (المُغطّى أدناه) أو mapfile عندما يكون المدخل مفصولًا بأسطر جديدة.
حلقة while: التكرار المبني على شرط
تعمل حلقة while طالما يُقيَّم شرطها إلى true (حالة خروج صفر). هي الأداة المناسبة حين لا تعرف مسبقًا عدد التكرارات — كانتظار استعداد خدمة، أو استطلاع واجهة برمجية، أو تفريغ طابور انتظار.
#!/usr/bin/env bash
set -euo pipefail
# --- انتظار حتى تصبح الخدمة جاهزة (نمط إنتاجي) ---
SERVICE_URL="http://localhost:8080/health"
MAX_WAIT=120 # ثوانٍ
INTERVAL=5
elapsed=0
echo "Waiting for service at ${SERVICE_URL}..."
while ! curl -sf "${SERVICE_URL}" >/dev/null; do
if (( elapsed >= MAX_WAIT )); then
echo "ERROR: service did not become healthy within ${MAX_WAIT}s" >&2
exit 1
fi
echo " not ready yet — retrying in ${INTERVAL}s (${elapsed}s elapsed)"
sleep "${INTERVAL}"
(( elapsed += INTERVAL ))
done
echo "Service is healthy after ${elapsed}s."
تفصيلان مهمان هنا: أولًا، ! تعكس حالة خروج curl -sf، فتستمر الحلقة طالما الخدمة غير مستجيبة. ثانيًا، حارس المهلة مع exit 1 الصريح يمنع الحلقة من الدوران إلى ما لا نهاية إذا كان ثمة عطل حقيقي — وهو شبكة أمان أساسية لأنابيب CI وأتمتة المناوبات.
نمط احترافي — التراجع الأسي: في حلقات إعادة المحاولة لطلبات الشبكة، استخدم تراجعًا أسيًا بدلًا من فترة ثابتة: sleep $(( INTERVAL * 2 ** attempt )). يمنع هذا إغراق خدمة متعثرة بالطلبات. مكتبات مثل retry (دالة shell صغيرة تلصقها في سكريبتك) تُنفّذ هذا النمط بشكل نظيف.
قراءة الملفات سطرًا بسطر
من أشيع مهام DevOps معالجة قائمة مخزّنة في ملف: قائمة خوادم، قائمة كائنات S3، ملف CSV لأسماء مستخدمين. النمط الآمن الموحّد هو while IFS= read -r line. لا تستخدم for line in $(cat file) — فهو يتعطل مع المسافات والمسافات البادئة/اللاحقة.
#!/usr/bin/env bash
set -euo pipefail
HOSTS_FILE="./hosts.txt"
# القراءة الآمنة سطرًا بسطر
# IFS= يمنع تجريد المسافات البادئة/اللاحقة
# -r يمنع تفسير الشرطة المائلة العكسية
while IFS= read -r host; do
# تجاهل الأسطر الفارغة وأسطر التعليقات
[[ -z "${host}" || "${host}" == \#* ]] && continue
echo "Checking SSH on: ${host}"
ssh -o ConnectTimeout=5 -o BatchMode=yes "${host}" "uptime" \
&& echo " OK" \
|| echo " FAILED — adding to alert queue"
done < "${HOSTS_FILE}"
إعادة التوجيه < "${HOSTS_FILE}" تُغذّي الملف إلى stdin حلقة while. هذا أكفأ من توصيله عبر cat (الذي ينشئ عملية فرعية) ويُبقي الحلقة في الـ shell الحالي، فتبقى تعيينات المتغيرات داخل الحلقة مرئية بعد انتهائها — فرق دقيق لكنه مهم.
مشكلة نطاق العملية الفرعية: حين تكتب cat file | while read -r line; do VAR=something; done، يعمل جسم الحلقة في shell فرعي بسبب الأنبوب. أي متغير تضبطه بداخله يضيع حين تنتهي الحلقة. إعادة التوجيه while ... done < file تتجنب هذا لأنه لا يوجد أنبوب.
حلقات الـ Glob: معالجة الملفات بأمان
حين تحتاج إلى العمل على كل ملف يطابق نمطًا — كل ملفات .log في مجلد، كل إعدادات *.yaml — يعدّ توسيع glob في Bash أكثر أمانًا وسرعةً من تحليل ناتج ls. الـ shell يوسّع الـ glob قبل تشغيل الحلقة، فيكون كل اسم ملف كلمةً منفصلة ومُقتبسة بشكل صحيح حتى لو احتوى على مسافات.
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="/var/log/myapp"
ARCHIVE_DIR="/mnt/cold-storage/logs"
CUTOFF_DAYS=7
mkdir -p "${ARCHIVE_DIR}"
# حلقة glob على جميع ملفات .log المعدَّلة منذ أكثر من CUTOFF_DAYS
# 'shopt -s nullglob' يجعل الحلقة تتخطى إذا لم تُطابق أي ملفات (ضروري!)
shopt -s nullglob
for logfile in "${LOG_DIR}"/*.log; do
if [[ $(find "${logfile}" -mtime "+${CUTOFF_DAYS}" 2>/dev/null) ]]; then
echo "Archiving: ${logfile}"
gzip --best --keep "${logfile}"
mv "${logfile}.gz" "${ARCHIVE_DIR}/"
rm -f "${logfile}"
fi
done
shopt -u nullglob # استعادة السلوك الافتراضي
سطر shopt -s nullglob بالغ الأهمية. بدونه، إذا لم تطابق أي ملفات النمط، يمرر Bash النص الحرفي /var/log/myapp/*.log كتكرار أول (ووحيد) — مما يجعل سكريبتك يحاول أرشفة ملف غير موجود. مع تفعيل nullglob، تجعل مجموعة المطابقات الصفرية جسم الحلقة لا ينفذ أبدًا. اضبطه دائمًا قبل حلقة glob قد لا تطابق شيئًا.
أربعة أنماط حلقات Bash — حالة الاستخدام المثالية لكل منها والفخ الذي يحمله إن استُخدم بشكل خاطئ.
التحكم في الحلقة: break وcontinue وحالات الخروج
كلمتان مدمجتان تُعدّلان تنفيذ الحلقة. break تخرج من الحلقة الداخلية فورًا؛ continue تتخطى بقية التكرار الحالي وتبدأ التالي. كلتاهما تقبلان وسيطة عدد صحيح اختيارية للاستهداف في الحلقات المتداخلة.
#!/usr/bin/env bash
set -euo pipefail
# معالجة طابور من المهام؛ التوقف إذا فشلت مهمة حرجة
declare -a JOBS=("migrate-db" "seed-cache" "warm-cdn" "deploy-app")
for job in "${JOBS[@]}"; do
echo "Running job: ${job}"
if [[ "${job}" == "seed-cache" ]]; then
echo " Skipping non-critical seed in prod"
continue # تخطي هذا التكرار فقط
fi
# محاكاة تشغيل المهمة (استبدل بأمر حقيقي)
./run-job.sh "${job}" || {
echo "CRITICAL: ${job} failed — halting pipeline" >&2
break # إيقاف كل المهام المتبقية
}
echo " ${job} completed successfully"
done
حالات خروج الحلقة: حالة خروج الحلقة هي حالة خروج آخر أمر نُفّذ بداخلها. إذا احتجت إلى نشر فشل تم اصطياده داخل break، اضبط متغير علامة قبل الكسر — PIPELINE_FAILED=1 — ثم تحقق منه بعد الحلقة ونفّذ exit 1 إذا كان مضبوطًا. مع تفعيل set -e هذا التمييز مهم: الأمر الفاشل في حلقة مع || true لاحق لا يوقف السكريبت، مما يمنحك تحكمًا في الإخفاقات الحرجة.
مثال إنتاجي عملي: تدوير السجلات على خوادم متعددة
دمج كل ما في هذا الدرس: سكريبت يقرأ قائمة خوادم من ملف، يتكرر على كل خادم، يستخدم حلقة glob عن بُعد لأرشفة السجلات القديمة، ويُعيد المحاولة على الخوادم الفاشلة قبل إرسال التنبيه.
#!/usr/bin/env bash
set -euo pipefail
HOSTS_FILE="${1:-/etc/myapp/hosts.txt}"
LOG_DIR="/var/log/myapp"
FAILED_HOSTS=()
shopt -s nullglob
while IFS= read -r host; do
[[ -z "${host}" || "${host}" == \#* ]] && continue
echo "=== ${host} ==="
if ! ssh -o ConnectTimeout=5 -o BatchMode=yes "${host}" \
"find ${LOG_DIR} -name '*.log' -mtime +7 -exec gzip -f {} \;"; then
echo " WARNING: log rotation failed on ${host}" >&2
FAILED_HOSTS+=("${host}")
fi
done < "${HOSTS_FILE}"
shopt -u nullglob
if (( ${#FAILED_HOSTS[@]} > 0 )); then
echo "ALERT: rotation failed on: ${FAILED_HOSTS[*]}" >&2
# ./notify.sh "log-rotation" "${FAILED_HOSTS[*]}"
exit 1
fi
echo "Log rotation complete on all hosts."
هذا السكريبت جاهز للإنتاج في هيكله: يقرأ المدخلات بأمان، يتخطى الأسطر الفارغة وأسطر التعليقات، يجمع الإخفاقات بدلًا من التوقف عند أول خطأ، ويخرج بحالة خروج غير صفرية فقط إذا فشل أي خادم — وهو بالضبط السلوك الذي تتوقعه أنظمة المراقبة وأنابيب CI.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية