„Bash Scripting“ geriausia praktika: trumpas patikimų ir našių „Bash“ scenarijų vadovas

„Bash Scripting“ geriausia praktika: trumpas patikimų ir našių „Bash“ scenarijų vadovas
Manapi apvalkalo tapetai

„Bash“ scenarijų derinimas yra tarsi adatos ieškojimas šieno kupetoje, ypač kai esamoje kodų bazėje atsiranda naujų priedų, laiku neatsižvelgus į struktūros, registravimo ir patikimumo klausimus. Tokiose situacijose galite atsidurti dėl savo klaidų arba tvarkydami sudėtingas scenarijų krūvas.

Komanda Mail.ru debesų sprendimai išvertė straipsnį su rekomendacijomis, kurios padės geriau rašyti, derinti ir prižiūrėti scenarijus. Tikėkite ar ne, niekas nepralenkia pasitenkinimo rašant švarų, paruoštą naudoti bash kodą, kuris veikia kiekvieną kartą.

Straipsnyje autorius dalijasi tuo, ko išmoko per pastaruosius kelerius metus, taip pat kai kuriomis dažnomis klaidomis, kurios jį užklupo netikėtai. Tai svarbu, nes kiekvienas programinės įrangos kūrėjas tam tikru savo karjeros momentu dirba su scenarijais, kad automatizuotų įprastas darbo užduotis.

Spąstų prižiūrėtojai

Dauguma bash scenarijų, su kuriais susidūriau, niekada nenaudoja veiksmingo valymo mechanizmo, kai scenarijaus vykdymo metu nutinka kažkas netikėto.

Iš išorės gali kilti netikėtumų, pavyzdžiui, gauti signalą iš branduolio. Tokių atvejų tvarkymas yra labai svarbus siekiant užtikrinti, kad scenarijai būtų pakankamai patikimi, kad galėtų veikti gamybinėse sistemose. Aš dažnai naudoju išėjimo tvarkykles, kad reaguočiau į tokius scenarijus:

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 yra apvalkalo įtaisyta komanda, padedanti užregistruoti valymo funkciją, kuri iškviečiama esant bet kokiems signalams. Tačiau ypač atsargiai reikia elgtis su tokiais tvarkytojais kaip SIGINT, dėl ko scenarijus nutrūksta.

Be to, daugeliu atvejų reikėtų tik gaudyti EXIT, bet idėja ta, kad iš tikrųjų galite tinkinti scenarijaus veikimą kiekvienam atskiram signalui.

Integruotos nustatytos funkcijos - greitas nutraukimas klaidos atveju

Labai svarbu reaguoti į klaidas, kai tik jos atsiranda, ir greitai sustabdyti vykdymą. Nieko negali būti blogiau nei toliau vykdyti tokią komandą:

rm -rf ${directory_name}/*

Atkreipkite dėmesį, kad kintamasis directory_name nenustatyta.

Norint valdyti tokius scenarijus, svarbu naudoti integruotas funkcijas set, toks kaip set -o errexit, set -o pipefail arba set -o nounset scenarijaus pradžioje. Šios funkcijos užtikrina, kad jūsų scenarijus bus išjungtas, kai tik aptiks bet kokį nulinį išėjimo kodą, neapibrėžtų kintamųjų naudojimą, netinkamas komandas, perduodamas vamzdžiu ir pan.:

#!/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

Pastaba: įmontuotos funkcijos, pvz set -o errexit, išeis iš scenarijaus, kai tik bus „neapdorotas“ grąžinimo kodas (išskyrus nulį). Todėl geriau įdiegti pasirinktinį klaidų tvarkymą, pavyzdžiui:

#!/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

Tokiu būdu rašydami scenarijus verčia jus būti atsargesniems dėl visų scenarijaus komandų veikimo ir numatyti klaidos galimybę, kol ji jus nustebins.

„ShellCheck“, kad aptiktų klaidas kūrimo metu

Verta integruoti kažką panašaus „ShellCheck“ į savo kūrimo ir testavimo vamzdynus, kad patikrintumėte savo bash kodą pagal geriausią praktiką.

Naudoju jį vietinėse kūrimo aplinkose, kad gaučiau ataskaitas apie sintaksę, semantiką ir kai kurias kodo klaidas, kurias galbūt praleidau kurdamas. Tai yra jūsų bash scenarijų statinės analizės įrankis ir labai rekomenduoju jį naudoti.

Naudodami savo išėjimo kodus

Grąžinimo kodai POSIX yra ne tik nulis arba vienas, bet ir nulis arba ne nulis. Naudokite šias funkcijas norėdami grąžinti pasirinktinius klaidų kodus (tarp 201–254) įvairiems klaidų atvejams.

Tada šią informaciją gali naudoti kiti scenarijai, apimantys jūsų, kad tiksliai suprastų, kokio tipo klaida įvyko, ir atitinkamai reaguotų:

#!/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
}

Pastaba: Būkite ypač atsargūs su savo apibrėžtais kintamųjų pavadinimais, kad netyčia nepaisytumėte aplinkos kintamųjų.

Registravimo funkcijos

Gražus ir struktūrinis registravimas yra svarbus norint lengvai suprasti scenarijaus rezultatus. Kaip ir kitose aukšto lygio programavimo kalbose, savo bash scenarijuose visada naudoju vietines registravimo funkcijas, pvz. __msg_info, __msg_error ir taip toliau.

Tai padeda sukurti standartizuotą registravimo struktūrą, atliekant pakeitimus tik vienoje vietoje:

#!/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"

Paprastai savo scenarijuose stengiuosi turėti kažkokį mechanizmą __init, kur tokie registravimo kintamieji ir kiti sistemos kintamieji inicijuojami arba nustatomos numatytosios vertės. Šiuos kintamuosius taip pat galima nustatyti iš komandinės eilutės parinkčių scenarijaus iškvietimo metu.

Pavyzdžiui, kažkas panašaus:

$ ./run-script.sh --debug

Kai toks scenarijus vykdomas, jis užtikrina, kad visos sistemos parametrai būtų nustatyti į numatytąsias reikšmes, jei jų reikia, arba bent jau inicijuoti į kažką tinkamo, jei reikia.

Pasirinkdamas, ką inicijuoti ir ko nedaryti, dažniausiai grindžiu kompromisu tarp vartotojo sąsajos ir konfigūracijos detalių, į kurias vartotojas gali/turėtų įsigilinti.

Pakartotinio naudojimo ir švarios sistemos būsenos architektūra

Modulinis / daugkartinis kodas

├── framework
│   ├── common
│   │   ├── loggers.sh
│   │   ├── mail_reports.sh
│   │   └── slack_reports.sh
│   └── daily_database_operation.sh

Turiu atskirą saugyklą, kurią galiu naudoti inicijuodamas naują projektą/bash scenarijų, kurį noriu sukurti. Viskas, ką galima pakartotinai panaudoti, gali būti saugoma saugykloje ir gauti kitų projektų, kurie nori naudoti tą funkciją. Tokiu būdu organizuojant projektus žymiai sumažėja kitų scenarijų dydis, taip pat užtikrinama, kad kodo bazė būtų maža ir lengvai išbandoma.

Kaip ir aukščiau pateiktame pavyzdyje, visos registravimo funkcijos, pvz __msg_info, __msg_error ir kiti, pvz., „Slack“ ataskaitos, pateikiami atskirai common/* ir dinamiškai prisijungti pagal kitus scenarijus, pvz daily_database_operation.sh.

Palikite švarią sistemą

Jei įkeliate kokius nors resursus, kol veikia scenarijus, rekomenduojama visus tokius duomenis saugoti bendrame kataloge atsitiktiniu pavadinimu, pvz. /tmp/AlRhYbD97/*. Norėdami pasirinkti katalogo pavadinimą, galite naudoti atsitiktinio teksto generatorius:

rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"

Baigus darbą, tokių katalogų valymas gali būti pateiktas aukščiau aptartuose kabliukų tvarkytuvėse. Jei laikinais katalogais nesirūpinama, jie kaupiasi ir tam tikru etapu sukelia netikėtų problemų pagrindiniame kompiuteryje, pavyzdžiui, pilnas diskas.

Užrakinimo failų naudojimas

Dažnai reikia užtikrinti, kad pagrindiniame kompiuteryje vienu metu būtų paleistas tik vienas scenarijaus egzempliorius. Tai galima padaryti naudojant užrakto failus.

Paprastai kuriu užrakinimo failus /tmp/project_name/*.lock ir patikrinkite, ar jie yra scenarijaus pradžioje. Tai padeda scenarijui gražiai užbaigti ir išvengti netikėtų sistemos būsenos pakeitimų, kai kitas lygiagrečiai veikiantis scenarijus. Užrakinimo failai nereikalingi, jei reikia, kad tas pats scenarijus būtų vykdomas lygiagrečiai tam tikrame pagrindiniame kompiuteryje.

Išmatuoti ir tobulinti

Mums dažnai reikia dirbti su scenarijais, kurie veikia ilgą laiką, pavyzdžiui, su kasdienėmis duomenų bazės operacijomis. Tokios operacijos paprastai apima veiksmų seką: duomenų įkėlimas, nukrypimų tikrinimas, duomenų importavimas, būsenos ataskaitų siuntimas ir pan.

Tokiais atvejais visada stengiuosi suskaidyti scenarijų į atskirus mažus scenarijus ir pranešti apie jų būseną bei vykdymo laiką naudodamas:

time source "${filepath}" "${args}">> "${LOG_DIR}/RUN_LOG" 2>&1

Vėliau galiu pamatyti vykdymo laiką su:

tac "${LOG_DIR}/RUN_LOG.txt" | grep -m1 "real"

Tai padeda man nustatyti problemas / lėtas sritis scenarijuose, kurias reikia optimizuoti.

Sėkmės!

Ką dar skaityti:

  1. Eiti ir GPU talpyklos.
  2. Įvykiais pagrįstos programos, pagrįstos žiniatinklio kabliukais, esančios Mail.ru Cloud Solutions S3 objektų saugykloje, pavyzdys.
  3. Mūsų telegramos kanalas apie skaitmeninę transformaciją.

Šaltinis: www.habr.com

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