أفضل ممارسات البرمجة النصية لـ Bash: دليل سريع لبرمجة Bash النصية الموثوقة والأداء

أفضل ممارسات البرمجة النصية لـ Bash: دليل سريع لبرمجة Bash النصية الموثوقة والأداء
خلفية شل بواسطة مانابي

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

فريق حلول سحابة Mail.ru قم بترجمة مقالة تحتوي على توصيات ستساعدك على كتابة البرامج النصية وتصحيح أخطائها وصيانتها بشكل أفضل. صدق أو لا تصدق، لا شيء يفوق متعة كتابة كود باش نظيف وجاهز للاستخدام ويعمل في كل مرة.

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

معالجات الفخ

معظم نصوص 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 لاكتشاف الأخطاء أثناء التطوير

يجدر دمج شيء من هذا القبيل شيلتشيك في مسارات التطوير والاختبار الخاصة بك للتحقق من كود 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

أحتفظ بمستودع منفصل يمكنني استخدامه لتهيئة مشروع جديد/برنامج نصي bash أرغب في تطويره. يمكن تخزين أي شيء يمكن إعادة استخدامه في مستودع واستعادته بواسطة المشاريع الأخرى التي ترغب في استخدام هذه الوظيفة. يؤدي تنظيم المشاريع بهذه الطريقة إلى تقليل حجم البرامج النصية الأخرى بشكل كبير ويضمن أيضًا أن تكون قاعدة التعليمات البرمجية صغيرة وسهلة الاختبار.

كما في المثال أعلاه، جميع وظائف التسجيل مثل __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"

يساعدني هذا في تحديد مناطق المشاكل/البطء في البرامج النصية التي تحتاج إلى التحسين.

حظا سعيدا!

ماذا تقرأ:

  1. انتقل وذاكرة التخزين المؤقت GPU.
  2. مثال لتطبيق يستند إلى حدث يعتمد على خطافات الويب في مخزن الكائنات S3 الخاص بـ Mail.ru Cloud Solutions.
  3. قناتنا على التليجرام تتحدث عن التحول الرقمي.

المصدر: www.habr.com

إضافة تعليق