بهترین روش های اسکریپت Bash: راهنمای سریع برای اسکریپت های Bash قابل اعتماد و عملکرد

بهترین روش های اسکریپت Bash: راهنمای سریع برای اسکریپت های Bash قابل اعتماد و عملکرد
کاغذ دیواری پوسته توسط manapi

اشکال زدایی اسکریپت های bash مانند جستجوی یک سوزن در انبار کاه است، به خصوص زمانی که اضافات جدید در پایگاه کد موجود بدون در نظر گرفتن به موقع مسائل مربوط به ساختار، ورود به سیستم و قابلیت اطمینان ظاهر می شوند. شما می توانید خود را در چنین موقعیت هایی یا به دلیل اشتباهات خود یا هنگام مدیریت انبوهی از اسکریپت های پیچیده پیدا کنید.

تیم 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 یک فرمان داخلی پوسته است که به شما کمک می کند یک تابع پاکسازی را ثبت کنید که در صورت وجود سیگنال فراخوانی می شود. با این حال، باید مراقبت های ویژه ای با هندلرهایی مانند 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

من یک مخزن جداگانه نگه می دارم که می توانم از آن برای مقداردهی اولیه اسکریپت پروژه/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

اضافه کردن نظر