برمجة الصدفة والأتمتة

الوسائط والخيارات والإدخال التفاعلي

18 دقيقة الدرس 9 من 28

الوسائط والخيارات والإدخال التفاعلي

السكريبت الذي يُضمّن إعداداته في الكود هو سكريبت لا يمكن إعادة استخدامه. كل أداة شل احترافية — من kubectl إلى aws إلى أتمتة النشر الخاصة بك — تقبل وسائط تغير سلوكها وقت الاستدعاء. في هذا الدرس ستتعلم مجموعة أدوات تصميم الواجهات الكاملة لسكريبتات Bash: المعاملات الموضعية، والأمر المدمج getopts للأعلام على نمط POSIX، ونمط دالة الاستخدام الكنسي، والأمر المدمج read لتلقي إدخال المشغل البشري. هذه المكونات الأربعة كافية لبناء واجهات سطر أوامر لا تختلف عن أدوات Unix القياسية.

المعاملات الموضعية

عند استدعاء Bash لسكريبت، يصل كل كلمة بعد اسم السكريبت إلى متغير مُرقَّم. $1 هو الوسيطة الأولى، و$2 الثانية، وهكذا. $0 هو اسم السكريبت نفسه. $# هو عدد الوسائط. "$@" يتوسع ليشمل جميع الوسائط ككلمات مقتبسة منفصلة — افضله دائمًا على $* عند تمرير الوسائط، لأنه يحافظ على المسافات البيضاء داخل القيم الفردية.

#!/usr/bin/env bash set -euo pipefail # $1 = البيئة (مطلوب) $2 = وسم الصورة (اختياري) ENVIRONMENT="${1:-}" IMAGE_TAG="${2:-latest}" # فشل سريع برسالة واضحة بدلًا من خطأ غامض لاحقًا if [[ -z "$ENVIRONMENT" ]]; then echo "ERROR: وسيطة البيئة مطلوبة" >&2 exit 1 fi echo "نشر الصورة ${IMAGE_TAG} إلى ${ENVIRONMENT}" # shift يزيل $1 ويُعيد ترقيم الباقي؛ مفيد بعد استهلاك الوسائط المعروفة shift echo "الوسائط المتبقية بعد shift: $*" # تكرار آمن على جميع الوسائط for arg in "$@"; do echo " وسيطة: ${arg}" done

صيغة ${1:-} تمنحك سلسلة فارغة عند غياب الوسيطة، حتى يعمل فحص -z بشكل نظيف. الصيغة الأكثر صرامة ${1:?رسالة} تجعل Bash يطبع الرسالة ويخرج فورًا — مفيدة داخل الدوال لكنها قد تُنتج مخرجات مربكة في مستوى السكريبت الأعلى.

فكرة رئيسية — تحقق دائمًا قبل الاستخدام: لا تفترض أبدًا أن المستدعي مرر العدد الصحيح من الوسائط. تحقق مبكرًا بجملة حارسة أو دالة استخدام، واخرج برمز غير صفري (عادةً 1 أو 2) ورسالة إلى stderr. هذا ما تفعله كل أداة Unix مكتوبة بجودة، وهو ما يتوقعه المشغلون عند توصيل سكريبتك في سير عمل أكبر.

نمط دالة الاستخدام

كل سكريبت يقبل وسائط يحتاج دالة usage. توثّق الواجهة، وتُطبَع عند -h/--help، وتُستدعى تلقائيًا عند فشل التحقق. هذا هو النمط المستخدم في HashiCorp وGitHub Actions runner ومعظم أدوات البنية التحتية مفتوحة المصدر:

#!/usr/bin/env bash set -euo pipefail readonly SCRIPT_NAME="$(basename "$0")" usage() { cat <<EOF الاستخدام: ${SCRIPT_NAME} [الخيارات] <البيئة> نشر التطبيق في البيئة المحددة. الوسائط: environment البيئة المستهدفة: dev | staging | prod الخيارات: -t, --tag TAG وسم صورة Docker للنشر (الافتراضي: latest) -n, --dry-run طباعة الإجراءات دون تنفيذها -v, --verbose تفعيل المخرجات التفصيلية -h, --help عرض رسالة المساعدة والخروج أمثلة: ${SCRIPT_NAME} staging ${SCRIPT_NAME} -t v2.3.1 prod ${SCRIPT_NAME} --dry-run prod EOF } # حارس: طباعة الاستخدام والخروج بـ 0 عند -h/--help كأول وسيطة if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then usage exit 0 fi

لاحظ cat <<EOF — heredoc عادي (ليس NOWDOC) حتى يتوسع ${SCRIPT_NAME} بشكل صحيح داخل نص الاستخدام. دالة الاستخدام هي أول ما يقرأه مشغل جديد؛ اجعلها كاملة ودقيقة.

تحليل الأعلام باستخدام getopts

getopts هو الأمر المدمج القياسي POSIX في Bash لتحليل الخيارات القصيرة (-v، -t TAG). يتعامل مع تجميع الخيارات (-vn)، والوسائط المطلوبة، والحارس -- لنهاية الخيارات. لا يتعامل مع الخيارات الطويلة (--verbose) بشكل أصلي؛ لهذه استخدم حلقة while/case يدوية أو getopt الخارجي. في الممارسة، تستخدم معظم سكريبتات الإنتاج في الشركات الكبيرة النمط اليدوي لأنه يتعامل مع الصيغتين القصيرة والطويلة بشكل نظيف.

#!/usr/bin/env bash set -euo pipefail # --- الافتراضيات --- IMAGE_TAG="latest" DRY_RUN=false VERBOSE=false # --- getopts: الخيارات القصيرة فقط --- # النقطتان بعد الحرف تعنيان أن الخيار يتطلب وسيطة. # النقطتان في البداية تُفعّل معالجة الأخطاء الصامتة. while getopts ":t:nvh" opt; do case "$opt" in t) IMAGE_TAG="$OPTARG" ;; n) DRY_RUN=true ;; v) VERBOSE=true ;; h) usage; exit 0 ;; :) echo "ERROR: الخيار -${OPTARG} يتطلب وسيطة" >&2; exit 2 ;; \?) echo "ERROR: خيار مجهول -${OPTARG}" >&2; exit 2 ;; esac done # بعد getopts، انقل الخيارات المُعالَجة حتى يصبح $1 أول وسيطة موضعية shift $(( OPTIND - 1 )) ENVIRONMENT="${1:?$(usage; echo 'ERROR: البيئة مطلوبة')}" echo "الوسم=${IMAGE_TAG} جاف=${DRY_RUN} مفصل=${VERBOSE} البيئة=${ENVIRONMENT}"
getopts parsing flow Script argv -t v2.3 -n prod while getopts reads one flag per iteration sets $opt + $OPTARG case -t IMAGE_TAG=$OPTARG case -n DRY_RUN=true case \? / : error + exit 2 shift $((OPTIND-1)) $1 = "prod" (positional) next flag
كيف يعالج getopts الأعلام واحدًا تلو الآخر، يُوزّع إلى فرع case، ثم يترك الوسائط الموضعية للاستخدام بعد shift.

التعامل مع الخيارات الطويلة بحلقة while/case

للسكريبتات التي يستخدمها البشر بكثرة، تُحسّن الخيارات الطويلة (--dry-run، --tag) القراءة بشكل كبير. النمط القياسي هو حلقة while true; do case "$1" in ... esac; done يدوية:

#!/usr/bin/env bash set -euo pipefail IMAGE_TAG="latest" DRY_RUN=false VERBOSE=false while [[ $# -gt 0 ]]; do case "$1" in -t|--tag) IMAGE_TAG="${2:?--tag يتطلب قيمة}" shift 2 ;; -n|--dry-run) DRY_RUN=true shift ;; -v|--verbose) VERBOSE=true shift ;; -h|--help) usage exit 0 ;; --) shift # حارس صريح لنهاية الخيارات break ;; -*) echo "ERROR: خيار مجهول: $1" >&2 usage exit 2 ;; *) break # أول وسيطة غير-خيار؛ توقف عن تحليل الأعلام ;; esac done ENVIRONMENT="${1:?ERROR: وسيطة البيئة مطلوبة}" shift || true echo "نشر ${IMAGE_TAG} إلى ${ENVIRONMENT} | جاف=${DRY_RUN} مفصل=${VERBOSE}"
ممارسة احترافية — رمز الخروج 2 لأخطاء الاستخدام: اتفاقية POSIX هي رمز الخروج 1 لأخطاء وقت التشغيل ورمز الخروج 2 لـ"الاستخدام الخاطئ" (وسائط خاطئة، علم مجهول). كثير من أنظمة CI وسكريبتات الشل تختبر $? ويمكنها التمييز بينهما. اخرج دائمًا بـ2 عندما يمرر المستدعي وسائط سيئة، واطبع الاستخدام أو مؤشرًا إلى --help.

الإدخال التفاعلي باستخدام read

لا ينبغي أبدًا أن تتطلب خطوط الأنابيب الآلية إدخالًا تفاعليًا — لكن السكريبتات التي يُشغّلها مشغل بشري (التوفير، وترحيل قواعد البيانات، وتدوير الأسرار) تحتاج أحيانًا إلى تأكيد أو قيمة لا يمكن للسكريبت اشتقاقها. يتعامل الأمر المدمج read مع هذا بشكل نظيف.

#!/usr/bin/env bash set -euo pipefail # موجه أساسي — يخزن الإجابة في REPLY افتراضيًا read -r -p "أدخل البيئة المستهدفة [dev/staging/prod]: " ENVIRONMENT # قراءة بقيمة افتراضية — اضغط Enter للقبول read -r -p "وسم الصورة [latest]: " IMAGE_TAG IMAGE_TAG="${IMAGE_TAG:-latest}" # قراءة كلمة مرور دون ظهور الأحرف على الطرفية read -r -s -p "كلمة مرور قاعدة البيانات: " DB_PASSWORD echo "" # سطر جديد بعد الإدخال المخفي # قراءة بمهلة — تُعيد قيمة غير صفرية إذا انتهت المهلة if ! read -r -t 30 -p "متابعة النشر؟ [y/N]: " CONFIRM; then echo "" echo "انتهت المهلة. إلغاء." >&2 exit 1 fi # حارس تأكيد بمطابقة regex if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then echo "تم إلغاء النشر بواسطة المشغل." exit 0 fi echo "المتابعة: البيئة=${ENVIRONMENT} الوسم=${IMAGE_TAG}"

أعلام read الرئيسية التي يجب معرفتها: -r تُعطّل تفسير الشرطة المائلة للخلف (استخدمها دائمًا)، -p تطبع موجهًا، -s تُخفي الصدى (كلمات المرور)، -t N تضبط مهلة بالثواني، و-a تقرأ كلمات مفصولة بمسافات إلى مصفوفة.

فخ الإنتاج — الإدخال التفاعلي في CI: عند تشغيل سكريبت يحتوي على read داخل خط أنابيب CI (GitHub Actions، Jenkins، GitLab CI)، لا يكون stdin طرفيةً ويُعيد read فورًا سلسلة فارغة أو تنتهي مهلته. تحقق دائمًا من [[ -t 0 ]] (هل stdin طرفية؟) قبل الموجه، أو وفّر علمًا --yes / -y يتخطى جميع الموجهات للاستخدام غير التفاعلي. دمج المسارين التفاعلي وغير التفاعلي في نفس السكريبت هو النمط القياسي المستخدم في أدوات مثل helm upgrade وkubectl apply.

تجميع كل شيء: هيكل سكريبت جاهز للإنتاج

الجمع بين كل ما تعلمناه في هذا الدرس ينتج الهيكل المستخدم في سكريبتات البنية التحتية الحقيقية في شركات التقنية الكبرى. النمط هو: دالة الاستخدام، والافتراضيات، وحلقة تحليل الأعلام، والتحقق من الموضعيات، وموجه تأكيد مُدرك لـCI، ثم العمل الفعلي:

#!/usr/bin/env bash # rollback.sh — التراجع عن خدمة إلى وسم صورة سابق # الاستخدام: ./rollback.sh [الخيارات] <الخدمة> <الوسم> set -euo pipefail readonly SCRIPT_NAME="$(basename "$0")" usage() { cat <<EOF الاستخدام: ${SCRIPT_NAME} [الخيارات] <الخدمة> <الوسم> التراجع عن <الخدمة> إلى صورة Docker <الوسم> في الكلستر الحالي. الخيارات: -n, --namespace NS مساحة اسم Kubernetes (الافتراضي: default) -y, --yes تخطي موجه التأكيد (للـCI/الأتمتة) -v, --verbose تفعيل مخرجات kubectl التفصيلية -h, --help عرض المساعدة EOF } # --- الافتراضيات --------------------------------------------- NAMESPACE="default" YES=false VERBOSE=false # --- تحليل الأعلام ------------------------------------------ while [[ $# -gt 0 ]]; do case "$1" in -n|--namespace) NAMESPACE="${2:?--namespace يتطلب قيمة}"; shift 2 ;; -y|--yes) YES=true; shift ;; -v|--verbose) VERBOSE=true; shift ;; -h|--help) usage; exit 0 ;; --) shift; break ;; -*) echo "ERROR: خيار مجهول $1" >&2; usage; exit 2 ;; *) break ;; esac done # --- التحقق من الموضعيات ------------------------------------ SERVICE="${1:?$(usage; echo 'ERROR: وسيطة الخدمة مطلوبة')}" TAG="${2:?$(usage; echo 'ERROR: وسيطة الوسم مطلوبة')}" # --- بوابة التأكيد (تُتخطى في CI) -------------------------- if [[ "$YES" == false ]]; then read -r -p "التراجع عن ${SERVICE} إلى ${TAG} في المساحة ${NAMESPACE}؟ [y/N]: " CONFIRM [[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "تم الإلغاء."; exit 0; } fi # --- العمل الفعلي ------------------------------------------- KUBECTL_FLAGS=() [[ "$VERBOSE" == true ]] && KUBECTL_FLAGS+=(-v=6) echo "التراجع عن ${SERVICE} إلى وسم الصورة ${TAG} ..." kubectl set image deployment/"${SERVICE}" \ "${SERVICE}=${SERVICE}:${TAG}" \ --namespace "${NAMESPACE}" \ "${KUBECTL_FLAGS[@]}" echo "تم. راقب الطرح باستخدام:" echo " kubectl rollout status deployment/${SERVICE} -n ${NAMESPACE}"

هذا الهيكل — دالة الاستخدام، والافتراضيات، وحلقة الأعلام، وحارس الموضعيات، والتأكيد المُدرك لـCI، ثم العمل — هو القالب الذي يجب أن يُكرره فريقك في كل سكريبت بنية تحتية جديد. يعمل بشكل صحيح سواء استدعاه إنسان أو خط أنابيب CI أو سكريبت آخر.