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

المتغيرات والاقتباس والاستبدال

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

المتغيرات والاقتباس والاستبدال

أخطاء الاقتباس هي المصدر الأكثر شيوعاً للأعطال في سكريبتات الشل على جميع مستويات الخبرة — من المبتدئ حتى المهندس الأول. حتى مهندس SRE متمرس في شركة تقنية كبرى يراجع سكريبتاته بحثاً عن المتغيرات غير المقتبسة قبل أي نشر في الإنتاج. هذا الدرس يعلمك لماذا يهم الاقتباس على مستوى أساسي، وكيف يحلل الشل مدخلاتك، والأنماط الدقيقة المستخدمة في سكريبتات الأتمتة الاحترافية.

تعيين المتغيرات والنطاق

تُعيَّن المتغيرات باستخدام الصيغة NAME=value — بدون مسافات حول علامة =. الشل حساس للمسافات هنا؛ فالكتابة NAME = value تُفسَّر كأمر تنفيذي لا كتعيين. يمكنك الإشارة للقيمة بـ$NAME أو الصيغة الأكثر أماناً ${NAME}. الصيغة مع الأقواس ضرورية عندما يتبع اسمَ المتغير مباشرةً محرفٌ أبجدي رقمي قد يُدمج مع الاسم.

# التعيين — بدون مسافات حول = ENVIRONMENT="production" REPLICAS=3 LOG_DIR="/var/log/myapp" # الإشارة للمتغير echo "Deploying to: $ENVIRONMENT" echo "Replicas: ${REPLICAS}" # الصيغة مع الأقواس ضرورية هنا — بدونها يبحث الشل عن $LOG_DIRarchive ARCHIVE_DIR="${LOG_DIR}archive" # تصبح /var/log/myapparchive — على الأرجح خطأ ARCHIVE_DIR="${LOG_DIR}_archive" # تصبح /var/log/myapp_archive — صحيح
قاعدة النطاق: المتغيرات محلية للجلسة الحالية. العمليات الفرعية (الشل الفرعي، السكريبتات التي تستدعيها) لا ترثها إلا إذا صدّرتها بـexport. استخدم export VARNAME أو دمج التعيين والتصدير في خطوة واحدة: export VARNAME="value". متغيرات البيئة التي تُمرَّر إلى Docker وأنظمة CI ومديري العمليات تتبع هذه القاعدة.

أوضاع الاقتباس الثلاثة

يمتلك الشل ثلاثة أوضاع للاقتباس، يختلف كل منها في طريقة تفسير المحتوى. إساءة فهم هذا هو منشأ أغلب الأخطاء في الإنتاج.

Shell Quoting Modes Comparison No Quotes Word splitting ON Glob expansion ON Variable expansion ON Danger: $var with spaces splits into multiple args echo $FILE Double Quotes Word splitting OFF Glob expansion OFF Variable expansion ON Safe for variables preserves whitespace echo "$FILE" Single Quotes Word splitting OFF Glob expansion OFF Variable expansion OFF Literal everything no interpretation at all echo '$FILE' تجنب مع المسارات والأسماء الاختيار الافتراضي للمتغيرات للسلاسل الحرفية والتعابير النمطية
أوضاع الاقتباس الثلاثة وما يسمح كل منها بتوسيعه.

القاعدة الإنتاجية بسيطة: اقتبس مراجع المتغيرات دائماً بعلامتي اقتباس مزدوجتين إلا إذا كان لديك سبب محدد للعكس. الملفات على الخوادم الفعلية غالباً ما تحتوي على مسافات وجدوَلة وسطور جديدة في أسمائها. متغير $filename غير مقتبس في أمر rm أو cp سيُجزّأ الكلمات ويستهدف عدة ملفات — كارثة في الإنتاج.

FILENAME="my report 2025.csv" # خطأ: يتجزأ إلى ثلاث وسائط: my, report, 2025.csv cp $FILENAME /backup/ # صحيح: يُعامَل كوسيطة واحدة cp "$FILENAME" /backup/ # علامات اقتباس مفردة: $USER لا يُوسَّع — يطبع "$USER" حرفياً echo 'Hello $USER' # علامات اقتباس مزدوجة: $USER يُوسَّع — يطبع "Hello alice" echo "Hello $USER" # إفلات محرف خاص داخل علامات اقتباس مزدوجة echo "The cost is \$50" # يطبع: The cost is $50

استبدال الأوامر

استبدال الأوامر يلتقط الإخراج القياسي لأمر ما ويضخّه في تعبير. الصيغة الحديثة هي $(command). الصيغة القديمة تستخدم backticks — تجنبها في الكود الجديد لأنها لا تدعم التداخل وتقرأ بصعوبة.

# التقاط اسم المضيف والتاريخ لتسمية ملفات السجل (مستخدم في كل سكريبت تدوير سجلات حقيقي) HOSTNAME=$(hostname -s) DATESTAMP=$(date +%Y-%m-%d) LOGFILE="/var/log/deploy-${HOSTNAME}-${DATESTAMP}.log" echo "Writing log to: $LOGFILE" # استبدال متداخل — الصيغة الحديثة تتعامل مع هذا بنظافة KERNEL_MAJOR=$(echo "$(uname -r)" | cut -d. -f1) echo "Kernel major version: $KERNEL_MAJOR" # عد الأسطر في ملف LINE_COUNT=$(wc -l < /etc/passwd) echo "Users in passwd: $LINE_COUNT" # التقاط فرع Git في سكريبت نشر BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") echo "Deploying branch: $BRANCH"
اقتبس إخراج استبدال الأوامر دائماً. استخدم "$(command)" لا $(command). إخراج الأوامر قد يحتوي على مسافات وأسطر جديدة. $(find . -name "*.log") غير مقتبس سيجزّئ كل اسم ملف يحتوي على مسافة. الاقتباس يمنع هذا التجزئ.

توسيع العمليات الحسابية

يدعم الشل الحسابات الصحيحة أصلاً عبر $(( expression )). هذا أسرع من استدعاء expr (نمط قديم شائع) ويتعامل مع أولوية العمليات القياسية بشكل صحيح. للعمليات الحسابية بالأرقام العشرية، فوّض إلى bc أو python3.

# الحساب الصحيح — المسافات اختيارية لكنها تحسّن القراءة TOTAL_INSTANCES=6 INSTANCES_PER_AZ=2 AZ_COUNT=$(( TOTAL_INSTANCES / INSTANCES_PER_AZ )) echo "Availability zones needed: $AZ_COUNT" # 3 # باقي القسمة — التحقق من أن رقماً زوجي BATCH_SIZE=10 REMAINDER=$(( BATCH_SIZE % 3 )) echo "Remainder: $REMAINDER" # 1 # زيادة عداد (مستخدم في حلقات إعادة المحاولة) RETRY=0 RETRY=$(( RETRY + 1 )) # أو باستخدام صيغة الاختصار داخل (( )) (( RETRY++ )) echo "Retry count: $RETRY" # 2 # الأرقام العشرية عبر bc FREE_PERCENT=$(echo "scale=2; 34 * 100 / 128" | bc) echo "Free memory: ${FREE_PERCENT}%" # 26.56%

المتغيرات الخاصة التي يضبطها الشل

يُهيئ الشل عدة متغيرات تلقائياً. معرفتها تُغني عن تمرير وسائط زائدة إلى السكريبتات وهي معرفة متوقعة على مستوى SRE.

  • $0 — اسم السكريبت نفسه، مفيد في رسائل الاستخدام
  • $? — كود خروج آخر أمر (0 = نجاح)
  • $$ — PID الشل الحالي، مفيد لإنشاء أسماء ملفات مؤقتة فريدة
  • $! — PID آخر عملية خلفية
  • $IFS — فاصل الحقول الداخلي المستخدم في تجزئة الكلمات (افتراضي: مسافة، جدولة، سطر جديد)
فخ إنتاجي — تعديل IFS. إذا غيّر سكريبتك قيمة IFS مؤقتاً لتحليل بيانات CSV أو التقسيم على النقطتين، استعدها دائماً بعد ذلك. النمط الشائع هو حفظ القيمة الأصلية: OLD_IFS="$IFS"، ثم تغييرها، ثم استعادتها بـIFS="$OLD_IFS". نسيان استعادة IFS يجعل كل عملية تجزئة لاحقة في السكريبت تتصرف بشكل خاطئ صامت — خطأ يصعب تتبعه جداً.

القيم الافتراضية للمتغيرات والأنماط الدفاعية

يجب أن تتحمل سكريبتات الإنتاج المتغيرات المفقودة أو الفارغة دون أن تتسبب في فقدان بيانات. يوفر Bash مجموعة من معاملات توسيع المعامل لهذا الغرض. في الشركات الكبرى تظهر هذه الأنماط في كل سكريبت أتمتة لأن المتغيرات غير المضبوطة في أمر rm -rf أتلفت بيانات إنتاجية بالفعل.

# ${VAR:-default} — استخدم القيمة الافتراضية إذا كان VAR غير مضبوط أو فارغاً ENVIRONMENT="${ENVIRONMENT:-staging}" # ${VAR:?رسالة خطأ} — أوقف بخطأ إذا كان VAR غير مضبوط أو فارغاً # هذا أهم شبكة أمان في السكريبتات التدميرية TARGET_DIR="${TARGET_DIR:?TARGET_DIR must be set — aborting to prevent disaster}" # ${VAR:+بديل} — استخدم البديل فقط إذا كان VAR مضبوطاً وغير فارغ DEBUG_FLAG="${DEBUG:+--verbose}" # سلسلة فارغة إذا لم يكن DEBUG مضبوطاً curl $DEBUG_FLAG https://api.example.com/health # تحويل إلى أحرف كبيرة (Bash 4+) SERVICE_NAME="my-service" echo "${SERVICE_NAME^^}" # MY-SERVICE # تحويل إلى أحرف صغيرة REGION="US-EAST-1" echo "${REGION,,}" # us-east-1 # طول المتغير CONFIG_PATH="/etc/myapp/config.yaml" echo "Path length: ${#CONFIG_PATH}" # 24

الجمع بين ${VAR:?message} في أعلى السكريبت التدميري وset -euo pipefail (مُغطى في الدرس 8) يُشكّل شبكة الأمان التي تميز السكريبتات الجاهزة للإنتاج عن الأوامر السريعة. استوعب كليهما قبل كتابة أي أتمتة تلمس بيانات.