Praktikat më të mira të skriptimit të Bash: Një udhëzues i shpejtë për skriptet Bash të besueshme dhe me performancë

Praktikat më të mira të skriptimit të Bash: Një udhëzues i shpejtë për skriptet Bash të besueshme dhe me performancë
Letër-muri i guaskës nga manapi

Korrigjimi i skripteve bash është si të kërkosh një gjilpërë në një kashtë, veçanërisht kur shtesat e reja shfaqen në bazën ekzistuese të kodeve pa shqyrtimin në kohë të çështjeve të strukturës, prerjeve dhe besueshmërisë. Ju mund ta gjeni veten në situata të tilla ose për shkak të gabimeve tuaja ose kur menaxhoni grumbuj komplekse skenarësh.

Ekip Mail.ru Cloud Solutions përktheu një artikull me rekomandime që do t'ju ndihmojnë të shkruani, korrigjoni dhe mbani më mirë skriptet tuaja. Besoni apo jo, asgjë nuk e kalon kënaqësinë e shkrimit të një kodi bash të pastër dhe të gatshëm për përdorim, që funksionon çdo herë.

Në artikull, autori ndan atë që ka mësuar gjatë viteve të fundit, si dhe disa gabime të zakonshme që e kanë zënë në befasi. Kjo është e rëndësishme sepse çdo zhvillues softuerësh, në një moment në karrierën e tij, punon me skriptet për të automatizuar detyrat rutinë të punës.

Trajtuesit e kurthit

Shumica e skripteve bash që kam hasur nuk përdorin kurrë një mekanizëm efektiv pastrimi kur ndodh diçka e papritur gjatë ekzekutimit të skriptit.

Surprizat mund të lindin nga jashtë, siç është marrja e një sinjali nga bërthama. Trajtimi i rasteve të tilla është jashtëzakonisht i rëndësishëm për të siguruar që skriptet janë mjaftueshëm të besueshëm për të ekzekutuar në sistemet e prodhimit. Unë shpesh përdor mbajtësit e daljes për t'iu përgjigjur skenarëve si ky:

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 është një komandë e integruar në guaskë që ju ndihmon të regjistroni një funksion pastrimi që thirret në rast të ndonjë sinjali. Megjithatë kujdes i veçantë duhet treguar me mbajtësit si p.sh SIGINT, gjë që shkakton ndërprerjen e skenarit.

Përveç kësaj, në shumicën e rasteve duhet të kapni vetëm EXIT, por ideja është që ju mund të personalizoni sjelljen e skriptit për çdo sinjal individual.

Funksionet e vendosura të integruara - përfundimi i shpejtë në rast gabimi

Është shumë e rëndësishme t'u përgjigjeni gabimeve sapo ato të ndodhin dhe të ndaloni ekzekutimin shpejt. Asgjë nuk mund të jetë më e keqe sesa të vazhdosh të ekzekutosh një komandë si kjo:

rm -rf ${directory_name}/*

Ju lutemi vini re se ndryshorja directory_name nuk është përcaktuar.

Është e rëndësishme të përdoren funksione të integruara për të trajtuar skenarë të tillë set, Të tilla si set -o errexit, set -o pipefail ose set -o nounset në fillim të skenarit. Këto funksione sigurojnë që skripti juaj do të dalë sapo të ndeshet me ndonjë kod daljeje jo-zero, përdorimin e variablave të papërcaktuar, komandat e pavlefshme të kaluara mbi një tub, e kështu me radhë:

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

Shenim: funksionet e integruara si p.sh set -o errexit, do të dalë nga skripti sapo të ketë një kod kthimi "të papërpunuar" (përveç zeros). Prandaj është më mirë të prezantohet trajtimi i gabimeve me porosi, për shembull:

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

Shkrimi i skripteve në këtë mënyrë ju detyron të jeni më të kujdesshëm për sjelljen e të gjitha komandave në skript dhe të parashikoni mundësinë e një gabimi përpara se ai t'ju befasojë.

ShellCheck për të zbuluar gabimet gjatë zhvillimit

Ia vlen të integrosh diçka të tillë ShellCheck në tubacionet tuaja të zhvillimit dhe testimit për të kontrolluar kodin tuaj bash kundrejt praktikave më të mira.

Unë e përdor atë në mjediset e mia të zhvillimit lokal për të marrë raporte mbi sintaksën, semantikën dhe disa gabime në kod që mund të kisha humbur gjatë zhvillimit. Ky është një mjet analize statike për skriptet tuaja bash dhe unë rekomandoj shumë përdorimin e tij.

Duke përdorur kodet tuaja të daljes

Kodet e kthimit në POSIX nuk janë vetëm zero ose një, por zero ose një vlerë jozero. Përdorni këto veçori për të kthyer kodet e gabimit të personalizuar (midis 201-254) për raste të ndryshme gabimesh.

Ky informacion mund të përdoret më pas nga skriptet e tjera që mbështjellin tuajin për të kuptuar saktësisht se çfarë lloj gabimi ka ndodhur dhe për të reaguar në përputhje me rrethanat:

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

Shenim: ju lutemi, jini veçanërisht të kujdesshëm me emrat e variablave që përcaktoni për të shmangur mbizotërimin aksidental të ndryshoreve të mjedisit.

Funksionet e regjistrimit

Regjistrimi i bukur dhe i strukturuar është i rëndësishëm për të kuptuar lehtësisht rezultatet e skenarit tuaj. Ashtu si me gjuhët e tjera të programimit të nivelit të lartë, unë përdor gjithmonë funksionet e regjistrimit vendas në skriptet e mia bash, si p.sh. __msg_info, __msg_error dhe kështu me radhë.

Kjo ndihmon në sigurimin e një strukture të standardizuar të regjistrimit duke bërë ndryshime vetëm në një vend:

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

Zakonisht përpiqem të kem një lloj mekanizmi në skenarët e mi __init, ku variabla të tillë logger dhe variabla të tjerë të sistemit inicializohen ose vendosen në vlerat e paracaktuara. Këto variabla mund të vendosen gjithashtu nga opsionet e linjës së komandës gjatë thirrjes së skriptit.

Për shembull, diçka si:

$ ./run-script.sh --debug

Kur ekzekutohet një skript i tillë, ai siguron që cilësimet në të gjithë sistemin të vendosen në vlerat e paracaktuara nëse ato kërkohen, ose të paktën të inicializohen në diçka të përshtatshme nëse është e nevojshme.

Unë zakonisht e bazoj zgjedhjen se çfarë të inicializohet dhe çfarë të mos bëhet në një shkëmbim ndërmjet ndërfaqes së përdoruesit dhe detajeve të konfigurimeve në të cilat përdoruesi mund/duhet të gërmojë.

Arkitekturë për ripërdorim dhe gjendje të pastër të sistemit

Kodi modular/i ripërdorshëm

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

Unë mbaj një depo të veçantë që mund ta përdor për të inicializuar një skript të ri të projektit/bash që dua të zhvilloj. Çdo gjë që mund të ripërdoret mund të ruhet në një depo dhe të merret nga projekte të tjera që duan të përdorin atë funksionalitet. Organizimi i projekteve në këtë mënyrë zvogëlon ndjeshëm madhësinë e skripteve të tjera dhe gjithashtu siguron që baza e kodit të jetë e vogël dhe e lehtë për t'u testuar.

Si në shembullin e mësipërm, të gjitha funksionet e logimit si p.sh __msg_info, __msg_error dhe të tjera, të tilla si raportet Slack, janë të përfshira veçmas në common/* dhe lidheni në mënyrë dinamike në skenarë të tjerë si daily_database_operation.sh.

Lëreni pas një sistem të pastër

Nëse jeni duke ngarkuar ndonjë burim gjatë ekzekutimit të skriptit, rekomandohet të ruani të gjitha këto të dhëna në një drejtori të përbashkët me një emër të rastësishëm, p.sh. /tmp/AlRhYbD97/*. Ju mund të përdorni gjeneratorë të rastësishëm teksti për të zgjedhur emrin e drejtorisë:

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

Pas përfundimit të punës, pastrimi i drejtorive të tilla mund të sigurohet në mbajtësit e grepave të diskutuar më sipër. Nëse drejtoritë e përkohshme nuk kujdesen, ato grumbullohen dhe në një fazë shkaktojnë probleme të papritura në host, si p.sh. një disk i plotë.

Përdorimi i skedarëve të kyçjes

Shpesh ju duhet të siguroheni që vetëm një shembull i një skripti të funksionojë në një host në çdo kohë të caktuar. Kjo mund të bëhet duke përdorur skedarët e kyçjes.

Unë zakonisht krijoj skedarë të kyçur në /tmp/project_name/*.lock dhe kontrolloni praninë e tyre në fillim të skenarit. Kjo e ndihmon skriptin të përfundojë me hijeshi dhe të shmangë ndryshimet e papritura në gjendjen e sistemit nga një skrip tjetër që funksionon paralelisht. Skedarët e kyçjes nuk nevojiten nëse keni nevojë që i njëjti skenar të ekzekutohet paralelisht në një host të caktuar.

Matni dhe përmirësoni

Shpesh na duhet të punojmë me skriptet që funksionojnë për periudha të gjata kohore, siç janë operacionet ditore të bazës së të dhënave. Operacione të tilla zakonisht përfshijnë një sekuencë hapash: ngarkimin e të dhënave, kontrollin për anomali, importimin e të dhënave, dërgimin e raporteve të statusit, etj.

Në raste të tilla, unë gjithmonë përpiqem ta ndaj skriptin në skripta të vegjël të veçantë dhe të raportoj statusin e tyre dhe kohën e ekzekutimit duke përdorur:

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

Më vonë mund të shoh kohën e ekzekutimit me:

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

Kjo më ndihmon të identifikoj zonat problematike/të ngadalta në skriptet që kanë nevojë për optimizim.

Good luck!

Çfarë tjetër për të lexuar:

  1. Shkoni dhe memoriet e GPU-së.
  2. Një shembull i një aplikacioni të drejtuar nga ngjarje të bazuara në uebhooks në ruajtjen e objekteve S3 të Mail.ru Cloud Solutions.
  3. Kanali ynë telegram për transformimin dixhital.

Burimi: www.habr.com

Shto një koment