يشبه Shell wallpaper بواسطة manapi
Debugging bash scripts البحث عن إبرة في كومة قش ، خاصةً عند ظهور إضافات جديدة في قاعدة بيانات موجودة دون النظر في الوقت المناسب في مسائل البنية والتسجيل والموثوقية. يمكنك أن تجد نفسك في مثل هذه المواقف بسبب أخطائك وعند إدارة مزيج معقدة من النصوص.
قام فريق Mail.ru Cloud Solutions بترجمة مقالة تحتوي على إرشادات ستساعدك على كتابة البرامج النصية وتصحيحها وصيانتها بشكل أفضل. صدق أو لا تصدق ، لا شيء يتفوق على الرضا عن كتابة كود bash نظيف وجاهز للاستخدام يعمل في كل مرة.
في هذا المقال ، يشارك المؤلف ما تعلمه خلال السنوات القليلة الماضية ، بالإضافة إلى بعض الأخطاء الشائعة التي فاجعته. هذا مهم لأن كل مطور برامج ، في مرحلة ما من حياته المهنية ، يعمل مع البرامج النصية لأتمتة مهام العمل الروتينية.
مناولات المصائد
معظم نصوص bash التي صادفتها لم تستخدم أبدًا آلية تنظيف فعالة عندما يحدث شيء غير متوقع أثناء تنفيذ البرنامج النصي.
يمكن أن تنشأ أشياء غير متوقعة من الخارج ، على سبيل المثال ، تلقي إشارة من النواة. يعتبر التعامل مع مثل هذه الحالات في غاية الأهمية لضمان أن تكون البرامج النصية قوية بما يكفي لتشغيلها على أنظمة الإنتاج. غالبًا ما أستخدم معالجات الخروج للرد على سيناريوهات مثل هذا:
function handle_exit() {
// Add cleanup code here
// for eg. rm -f "/tmp/${lock_file}.lock"
// exit with an appropriate status code
}
// trap <HANDLER_FXN> <LIST OF SIGNALS TO TRAP>
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
trap
هو أمر shell مضمن يساعدك على تسجيل وظيفة تنظيف ليتم استدعاؤها في حالة وجود أي إشارات. ومع ذلك ، يجب توخي الحذر بشكل خاص مع المعالجات مثل تلك SIGINT
التي تقاطع البرنامج النصي.
أيضًا ، في معظم الحالات ، يجب أن تلتقطها فقط
EXIT
، ولكن الفكرة هي أنه يمكنك بالفعل تخصيص سلوك البرنامج النصي لكل إشارة فردية.
اضبط الوظائف المضمنة - خروج سريع عند الخطأ
من المهم جدًا الرد على الأخطاء بمجرد حدوثها ووقف التنفيذ بسرعة. لا شيء يمكن أن يكون أسوأ من الاستمرار في أمر مثل هذا:
rm -rf ${directory_name}/*
يرجى ملاحظة أن المتغير
directory_name
غير محدد.
للتعامل مع مثل هذه السيناريوهات ، من المهم استخدام الوظائف المضمنة
set
مثل set -o errexit
، set -o pipefail
أو set -o nounset
في بداية البرنامج النصي. تضمن هذه الوظائف خروج البرنامج النصي الخاص بك بمجرد أن يواجه أي كود خروج غير صفري ، ومتغيرات غير محددة ، وأوامر أنابيب غير صالحة ، وما إلى ذلك:
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
function print_var() {
echo "${var_value}"
}
print_var
$ ./sample.sh
./sample.sh: line 8: var_value: unbound variable
ملاحظة: الوظائف المضمنة مثل
set -o errexit
ستخرج من البرنامج النصي بمجرد ظهور رمز إرجاع "خام" (بخلاف الصفر). لذلك من الأفضل تقديم معالجة الأخطاء المخصصة مثل:
#!/bin/bash
error_exit() {
line=$1
shift 1
echo "ERROR: non zero return code from line: $line -- $@"
exit 1
}
a=0
let a++ || error_exit "$LINENO" "let operation returned non 0 code"
echo "you will never see me"
# run it, now we have useful debugging output
$ bash foo.sh
ERROR: non zero return code from line: 9 -- let operation returned non 0 code
تجبرك البرمجة النصية مثل هذا على أن تكون أكثر حرصًا بشأن سلوك جميع الأوامر في البرنامج النصي وتوقع احتمال حدوث خطأ قبل أن يفاجأ.
ShellCheck لاكتشاف الأخطاء أثناء التطوير
يجدر دمج شيء مثل ShellCheck في خطوط أنابيب التطوير والاختبار للتحقق من صحة كود bash الخاص بك للحصول على أفضل الممارسات.
أستخدمه في بيئات التطوير المحلية الخاصة بي للحصول على تقارير حول بناء الجملة والدلالات وبعض أخطاء التعليمات البرمجية التي ربما فاتنيها في التطوير. إنها أداة تحليل ثابتة لنصوص bash الخاصة بك وأنا أوصي بشدة باستخدامها.
باستخدام رموز الخروج الخاصة بك
رموز إرجاع POSIX ليست صفرًا أو واحدًا فقط ، ولكنها صفر أو غير صفرية. استخدم هذه الميزات لإرجاع رموز الخطأ المخصصة (بين 201-254) لحالات الخطأ المختلفة.
يمكن بعد ذلك استخدام هذه المعلومات بواسطة البرامج النصية الأخرى التي تغلف نصوصك لفهم نوع الخطأ الذي حدث بالضبط والتفاعل وفقًا لذلك:
#!/usr/bin/env bash
SUCCESS=0
FILE_NOT_FOUND=240
DOWNLOAD_FAILED=241
function read_file() {
if ${file_not_found}; then
return ${FILE_NOT_FOUND}
fi
}
ملاحظة: يرجى توخي الحذر بشكل خاص مع أسماء المتغيرات التي تحددها لتجنب تجاوز متغيرات البيئة عن طريق الخطأ.
وظائف المسجل
يعد التسجيل الجيد والمنظم مهمًا لفهم نتائج تنفيذ النص البرمجي بسهولة. كما هو الحال مع لغات البرمجة عالية المستوى الأخرى ، أستخدم دائمًا وظائف التسجيل الخاصة بي في نصوص bash النصية الخاصة بي مثل
__msg_info
، __msg_error
وما إلى ذلك.
يساعد هذا في توفير بنية تسجيل موحدة من خلال إجراء تغييرات في مكان واحد فقط:
#!/usr/bin/env bash
function __msg_error() {
[[ "${ERROR}" == "1" ]] && echo -e "[ERROR]: $*"
}
function __msg_debug() {
[[ "${DEBUG}" == "1" ]] && echo -e "[DEBUG]: $*"
}
function __msg_info() {
[[ "${INFO}" == "1" ]] && echo -e "[INFO]: $*"
}
__msg_error "File could not be found. Cannot proceed"
__msg_debug "Starting script execution with 276MB of available RAM"
عادةً ما أحاول الحصول على نوع من الآلية في البرامج النصية الخاصة بي
__init
حيث يتم تهيئة متغيرات المسجل ومتغيرات النظام الأخرى أو تعيينها على القيم الافتراضية. يمكن أيضًا تعيين هذه المتغيرات من معلمات سطر الأوامر أثناء استدعاء البرنامج النصي.
على سبيل المثال ، شيء مثل:
$ ./run-script.sh --debug
عند تنفيذ مثل هذا البرنامج النصي ، فمن المضمون أن يتم ضبط الإعدادات على مستوى النظام على الإعدادات الافتراضية ، إذا لزم الأمر ، أو على الأقل تهيئتها بشيء مناسب ، إذا لزم الأمر.
عادةً ما أقوم باختياري لما يجب تهيئته وما لا يجب أن يكون مقايضة بين واجهة المستخدم وتفاصيل التكوين التي يمكن / ينبغي للمستخدم الخوض فيها.
العمارة لإعادة الاستخدام وحالة النظام النظيفة
كود معياري / قابل لإعادة الاستخدام
├── framework
│ ├── common
│ │ ├── loggers.sh
│ │ ├── mail_reports.sh
│ │ └── slack_reports.sh
│ └── daily_database_operation.sh
احتفظ بمستودع منفصل يمكنني استخدامه لتهيئة مشروع / نص برمجي جديد أرغب في تطويره. يمكن تخزين أي شيء يمكن إعادة استخدامه في المستودع واسترداده في مشاريع أخرى تريد استخدام هذه الوظيفة. يقلل تنظيم المشاريع هذا بشكل كبير من حجم البرامج النصية الأخرى ويضمن أيضًا أن تكون قاعدة التعليمات البرمجية صغيرة ويمكن اختبارها بسهولة.
كما في المثال أعلاه ، يتم الاحتفاظ بجميع وظائف التسجيل ، مثل
__msg_info
، __msg_error
وغيرها ، مثل التقارير بواسطة Slack ، بشكل منفصل في common/*
سيناريوهات أخرى وتتصل ديناميكيًا بها ، مثل daily_database_operation.sh
.
اترك خلفك نظامًا نظيفًا
إذا قمت بتحميل بعض الموارد أثناء تشغيل البرنامج النصي ، فمن المستحسن تخزين كل هذه البيانات في دليل مشترك باسم عشوائي ، على سبيل المثال
/tmp/AlRhYbD97/*
. يمكنك استخدام مولدات النص العشوائي لاختيار اسم دليل:
rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
عند الانتهاء من العمل ، يمكن توفير تنظيف هذه الأدلة في معالجات الخطاف التي تمت مناقشتها أعلاه. إذا لم تهتم بحذف الأدلة المؤقتة ، فإنها تتراكم وتتسبب في مرحلة ما في حدوث مشكلات غير متوقعة على المضيف ، مثل القرص الممتلئ.
استخدام ملفات القفل
غالبًا ما تحتاج إلى التأكد من تشغيل مثيل واحد فقط من البرنامج النصي على مضيف في أي وقت. يمكن القيام بذلك باستخدام ملفات القفل.
عادةً ما أقوم بإنشاء ملفات قفل
/tmp/project_name/*.lock
والتحقق من وجودها في بداية البرنامج النصي. يساعد هذا في إنهاء البرنامج النصي بشكل صحيح وتجنب التغييرات غير المتوقعة في حالة النظام بواسطة برنامج نصي آخر يعمل بالتوازي. ليست هناك حاجة إلى ملفات القفل إذا كنت بحاجة إلى نفس البرنامج النصي للتشغيل بالتوازي على مضيف معين.
القياس والتحسين
غالبًا ما يتعين علينا العمل مع البرامج النصية التي يتم تشغيلها على مدار فترة زمنية طويلة ، مثل عمليات قاعدة البيانات اليومية. تتضمن هذه العمليات عادةً سلسلة من الخطوات: تحميل البيانات ، والتحقق من الأخطاء ، واستيراد البيانات ، وإرسال تقارير الحالة ، وما إلى ذلك.
في مثل هذه الحالات ، أحاول دائمًا تقسيم البرنامج النصي إلى نصوص صغيرة منفصلة والإبلاغ عن حالتها ووقت التنفيذ باستخدام:
time source "${filepath}" "${args}">> "${LOG_DIR}/RUN_LOG" 2>&1
يمكنني مشاهدة وقت التشغيل لاحقًا باستخدام:
tac "${LOG_DIR}/RUN_LOG.txt" | grep -m1 "real"
يساعدني هذا في تحديد المشكلات / المناطق البطيئة في البرامج النصية التي تحتاج إلى تحسين.
حظا سعيدا!
ماذا تقرأ: