Bash սցենարների լավագույն փորձը. արագ ուղեցույց հուսալի և կատարողական Bash սցենարների համար

Bash սցենարների լավագույն փորձը. արագ ուղեցույց հուսալի և կատարողական Bash սցենարների համար
Shell պաստառ 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"

Սա օգնում է ինձ բացահայտել խնդրահարույց/դանդաղ տարածքները սցենարներում, որոնք օպտիմալացման կարիք ունեն:

Good luck!

Էլ ի՞նչ կարդալ.

  1. Գնացեք և GPU-ի քեշերը:
  2. Mail.ru Cloud Solutions-ի S3 օբյեկտների պահեստում վեբկեռիկների վրա հիմնված իրադարձությունների վրա հիմնված հավելվածի օրինակ:
  3. Մեր հեռագրային ալիքը թվային փոխակերպման մասին։

Source: www.habr.com

Добавить комментарий