السكريبتات المتينة: الأخطاء والسلامة
السكريبتات المتينة: الأخطاء والسلامة
الفارق بين سكريبت شل هاوٍ وآخر يعمل دون رقابة في بيئة الإنتاج في الثالثة صباحاً ليس الذكاء — بل الهندسة الدفاعية. السلوك الافتراضي لـ Bash خطير في تساهله: يواصل التنفيذ بعد فشل الأوامر، ويوسّع المتغيرات غير المُعرَّفة إلى سلاسل فارغة بصمت، ويخفي فشل الأوامر داخل الأنابيب خلف كود الخروج للأمر الأخير. لا شيء من هذا مقبول حين يحذف سكريبتك نسخ احتياطية قديمة، أو يُدير أسراراً، أو ينشر إلى الإنتاج. يغطي هذا الدرس المجموعة القياسية من الحراس التي يجب أن يمتلكها كل سكريبت شل احترافي، والأدوات التي تفرضها تلقائياً.
الثلاثة الكبار: set -euo pipefail
تحوّل هذه الأعلام الثلاث مجتمعةً على سطر واحد Bash من مُفسِّر متساهل إلى مُفسِّر صارم. ضعها مباشرةً بعد سطر الشيبانغ — دون استثناءات.
هذا ما يفعله كل علم ولماذا يهمّ:
-e(errexit): الخروج فوراً حين يُعيد أي أمر حالة غير صفرية. بدون هذا العلم، يواصل Bash بهجة تنفيذrm -rf /var/dataحتى لو فشلmkdir /var/dataالسابق بصمت. مع-e، يتوقف السكريبت عند نقطة الفشل بدلاً من التفاقم إلى حالة أسوأ.-u(nounset): معاملة أي إشارة إلى متغير غير مُعرَّف كخطأ. الكارثة الكلاسيكية تبدو هكذا:rm -rf "${DEPLOY_DIR}/"حيثDEPLOY_DIRخطأ إملائي أو لم يُصدَّر قط. بدون-u، يتوسع هذا بصمت إلىrm -rf "/". مع-u، يُجهض السكريبت برسالة DEPLOY_DIR: unbound variable.-o pipefail: يجعل الأنبوب يُعيد حالة خروج أوّل أمر فشل فيه من اليمين، بدلاً من دائماً إعادة حالة الأمر الأخير. بدونه،false | trueتخرج بـ 0 — فشل صامت يبتلعه الأنبوب. مع pipefail، تخرج بـ 1.
-e والشيلات الفرعية: لا ينتشر set -e إلى الشيلات الفرعية المولّدة بـ ( ) أو استبدال الأوامر إلا إذا ضبطته فيها أيضاً. اختبر دائماً مسارات الفشل، لا فقط المسار السعيد.
الفخاخ: تنظيف مضمون
الفخّ (trap) هو معالج يُنفّذه الشيل عند حدوث إشارة أو شبه إشارة محددة. الفخّان اللذان يحتاجهما كل سكريبت إنتاجي هما EXIT وERR.
EXIT يُطلَق كلما انتهى السكريبت — سواء انتهى بشكل طبيعي، أو اصطدم بفشل set -e، أو تلقى إشارة. استخدمه لحذف الملفات المؤقتة، وتحرير الأقفال، أو تسجيل الاكتمال. ERR يُطلَق تحديداً حين يفشل أمر ما (يعمل بالتنسيق مع set -e)، مما يجعله المكان المناسب لإصدار رسالة خطأ منظمة.
عدة نقاط تستحق الانتباه. أولاً، تلتقط cleanup قيمة $? فوراً — أي أمر لاحق قد يُعيد كتابتها. ثانياً، تتحقق الدالة من أن متغير الدليل المؤقت غير فارغ وأن المسار موجود فعلاً قبل محاولة الحذف؛ حراسة ضد الحالة التي يفشل فيها السكريبت قبل تشغيل mktemp. ثالثاً، تخرج cleanup بكود الخروج الأصلي حتى يتلقى المُستدعي (مشغّل CI، systemd، cron) الحالة الصحيحة.
flock أو بكتابة PID إلى /var/run/myscript.pid؛ احذفه في فخ EXIT. هذا النمط مستخدم في الإنتاج على نطاق واسع من أدوات مثل mysqld_safe وnginx.
التعامل الدفاعي مع المتغيرات
إضافةً إلى -u، يوفر Bash مُشغّلات التوسع التي تتيح لك التعبير عن النية بدقة والفشل السريع برسالة واضحة.
الصيغة :? هي الحراسة الاصطلاحية في أعلى أي سكريبت يعتمد على متغيرات البيئة المُضخَّة من نظام CI أو مدير الأسرار. حين تغيب DATABASE_URL، يتوقف السكريبت فوراً برسالة واضحة بدلاً من تمرير سلسلة فارغة إلى أمر لاحق ينتج خطأً غامضاً لاحقاً.
الملفات المؤقتة الآمنة
المسارات المؤقتة المُضمَّنة في الكود مثل /tmp/my-script.tmp هي ثغرة أمنية (هجمات الروابط الرمزية) وخطأ تزامن (تصادم نسختين). استخدم mktemp دائماً.
التحليل الساكن مع ShellCheck
ShellCheck هو أداة تحليل ساكن تكتشف الأخطاء في سكريبتات الشيل قبل تشغيلها. تكتشف المتغيرات غير المقتبسة، وبنية الشرط الخاطئة، وعدم التوافق بين POSIX وbash، وعشرات الأخطاء الشائعة الأخرى. في شركات التقنية الكبرى، يعمل ShellCheck في خط أنابيب CI كبوابة lint إلزامية — سكريبت الشيل الذي لا يجتاز ShellCheck لا يُدمج.
ثبّت ShellCheck وشغّله محلياً قبل الحفظ:
يتكامل ShellCheck مع VS Code (إضافة shellcheck) وVim (ALE) وGitHub Actions (ludeeus/action-shellcheck). خطوة CI نموذجية تبدو هكذا:
set -euo pipefail، وفخّ التنظيف، وفخّ الخطأ، والمتغيرات المطلوبة المُتحقق منها. احتفظ بمثل هذا القالب في صندوق أدوات فريقك الداخلي وافرضه عبر الفاحص. السكريبتات التي تنحرف تتطلب مبرراً كتابياً صريحاً، ليس مجرد تعليق.
الجمع بين كل شيء: هيكل سكريبت آمن
إليك الهيكل القياسي الذي يجمع جميع التقنيات في هذا الدرس. انسخه كنقطة بداية لكل سكريبت إنتاجي جديد.
نمط main "$@" — وضع كل المنطق في دالة main واستدعاؤها في النهاية — يضمن تحليل السكريبت بأكمله قبل تشغيل أي كود، مما يمنع الأخطاء الخفية الناجمة عن استدعاء دالة قبل تعريفها. يجعل السكريبت أيضاً أسهل للاختبار بمعزل عن غيره، وللاستيراد الآمن من سكريبتات أخرى.