Praktikat më të mira të skriptit bash: një udhëzues i shkurtër për skriptet bash të besueshme dhe me performancë të lartë

Praktikat më të mira të skriptit bash: një udhëzues i shkurtër për skriptet bash të besueshme dhe me performancë të lartë
Wallpaperi i shell nga manapi

Debugging skriptet bash është si kërkimi i një gjilpër në një kashtë, sidomos kur plotësime të reja shfaqen në një bazë kodi ekzistuese pa një shqyrtim të saktë të çështjeve të strukturës, regjistrimit dhe besueshmërisë. Në këto situata, mund të haseni si për shkak të gabimeve tuaja ashtu edhe kur menaxhoni grumbuj të komplikuar skriptesh.

Ekipa Zgjidhjet Cloud të Mail.ru përktheu një artikull me rekomandime që do t'ju ndihmojnë të shkruani më mirë, të debugoni dhe të mbani skriptet tuaja. Besoni ose jo, por asgjë nuk mund të krahasohet me kënaqësinë e shkrimit të kodit bash të pastër, të gatshëm për përdorim, që punon çdo herë.

Në artikull, autori ndan atë që ka mësuar gjatë viteve të fundit, si dhe disa gabime të zakonshme që e kanë kapur të papërgatitur. Kjo është e rëndësishme sepse çdo zhvillues softueri, në një moment të caktuar të karrierës së tij, punon me skripte për të automatizuar detyrat e përditshme të punës.

Menaxherët e grumbujve

Shumica e skripteve bash, me të cilat kam hasur, kurrë nuk kanë përdorur një mekanizëm efektiv pastrimi kur ndodh diçka e papritur gjatë ekzekutimit të skriptit.

Papriturit mund të vijnë nga jashtë, siç është marrja e një sinjali nga bërthama. Trajtimi i këtyre rasteve është jashtëzakonisht i rëndësishëm për të siguruar që skriptet të jenë mjaft të besueshme për t'u ekzekutuar në sistemet prodhuese. Unë shpesh përdor trajtues të daljes për të reaguar në këto skenarë:

function handle_exit() {
  // Shtoni kodin e pastrimit këtu
  // për shembull. rm -f "/tmp/${lock_file}.lock"
  // dilni me një kod të përshtatshëm statusi
}
  
// kap <HANDLER_FXN> <LISTA E SINJALEVE PËR TË KAPUR>
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

trap — Ă«shtĂ« njĂ« komandĂ« e integruar e shell-it, qĂ« ju ndihmon tĂ« regjistroni njĂ« funksion pastrimi qĂ« thirret nĂ« rast tĂ« sinjaleve tĂ« caktuara. MegjithatĂ«, duhet tĂ« jeni veçanĂ«risht tĂ« kujdesshĂ«m me trajtuesit e tillĂ« si SIGINT, i cili shkakton njĂ« ndĂ«rprerje tĂ« skriptit.

Për më tepër, në shumicën e rasteve duhet të kapni vetëm EXIT, por ideja është se ju në të vërtetë mund të konfiguroni sjelljen e skriptit për çdo sinjal të veçantë.

Funksionet e integruara set — ndalimi i shpejtĂ« nĂ« rast gabimi

ËshtĂ« shumĂ« e rĂ«ndĂ«sishme tĂ« reagoni ndaj gabimeve sapo ato ndodhin dhe tĂ« ndaloni menjĂ«herĂ« ekzekutimin. Nuk ka gjĂ« mĂ« tĂ« keqe se sa tĂ« vazhdoni ekzekutimin e njĂ« komande si kjo:

rm -rf ${directory_name}/*

Vini re se variabël directory_name nuk është e përcaktuar.

Për të trajtuar skenarë të tillë, është e rëndësishme të përdorni funksione të integruara set, siç janë set -o errexit, set -o pipefail ose set -o nounset në fillim të skriptit. Këto funksione garantojnë që skripti juaj do të ndalojë ekzekutimin sa herë që has një kod përfundimi që nuk është zero, përdorimin e variablave të pa përcaktuar, komandat e gabuara të vendosura në kanale dhe 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

Shënim: funksione të integruara, të tilla si set -o errexit, do të dalin nga skripti sa herë që të shfaqet një kod kthimi "i pa trajtuar" (përveç zeros). Prandaj, është më mirë të vendosni përpunimin e gabimeve nga ana e përdoruesit, 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

Një shkrim i tillë skripti ju bën të jeni më të kujdesshëm ndaj sjelljes së të gjitha komandave në skript dhe të parashikoni mundësinë e shfaqjes së gabimeve përpara se ato t'ju kapin në befasi.

ShellCheck për identifikimin e gabimeve gjatë zhvillimit

Vlen të integrohet diçka si ShellCheck në kanalet tuaja të zhvillimit dhe testimit, për të kontrolluar kodin tuaj bash në përdorimin e praktikave më të mira.

E përdor në ambientet e mia lokale të zhvillimit, për të marrë raporte mbi sintaksën, semantikën dhe disa gabime në kod, që mund të kem humbur gjatë zhvillimit. Ky është një mjet analize statike për skenaret tuaj bash, dhe unë e rekomandoj fort ta përdorni.

Përdorimi i kodit tuaj exit

Kodet e kthimit në POSIX nuk janë vetëm zero ose një, por zero ose një vlerë jo-zero. Përdorni këto mundësi për të kthyer kode gabimi të përshtatura (midis 201-254) për raste të ndryshme gabimesh.

Kjo informacion mund të përdoret pastaj nga skenarë të tjerë që e mbështesin tuajin, për të kuptuar saktësisht cilin lloj gabimi ndodhi, dhe për t'u përgjigjur përkatësisht:

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

Shënim: lutem, tregoni kujdes të veçantë me emrat e variablave që definoni, për të mos rrezikuar përmbysjen rastësore të variablave të mjedisit.

Funksionet e regjistrimit

Një regjistrim i bukur dhe i strukturuar i logëve është i rëndësishëm për të kuptuar lehtësisht rezultatet e ekzekutimit të skriptit tuaj. Ashtu si në gjuhët e tjera të programimit të nivelit të lartë, gjithmonë përdor në skriptet e mia bash funksione të mia të regjistrimit, si p.sh. __msg_info, __msg_error etj.

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 mundohem të kem një mekanizëm në skriptet e mia __init, ku variablat e regjistruesit dhe variablat e tjerë sistemorë inicializohen ose vendosen në vlera parazgjedhur. Këto variabla gjithashtu mund të vendosen nga parametrat e linjës së komandës gjatë thirrjes së skriptit.

Për shembull, diçka si:

$ ./run-script.sh --debug

Kur një skript i tillë ekzekutohet, garanton që konfigurimet e përgjithshme të sistemit të jenë vendosur në vlera parazgjedhur, nëse janë të detyrueshme, ose, të paktën, të inicializohen me diçka përkatëse, nëse është e nevojshme.

Zakonisht e bazoj zgjedhjen se çfarë të inicializoj dhe çfarë jo në një kompromis midis ndërfaqes së përdoruesit dhe detajeve të konfigurimeve që përdoruesi mund/duhet të kuptojë.

Arkitektura për ripërdorim dhe gjendjen e pastër të sistemit

Kodi modular / i ripërdorshëm

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

Mbaj njĂ« repozitor tĂ« veçantĂ« qĂ« mund tĂ« pĂ«rdoret pĂ«r tĂ« inicializuar njĂ« projekt tĂ« ri/scenar bash qĂ« dua tĂ« zhvilloj. Çdo gjĂ« qĂ« mund tĂ« pĂ«rdoret pĂ«rsĂ«ri mund tĂ« ruhet nĂ« repo dhe tĂ« merret nĂ« projekte tĂ« tjera qĂ« dĂ«shirojnĂ« tĂ« pĂ«rdorin ato funksionalitete. Kjo organizim projektesh e ul 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ç është në shembullin e mësipërm, të gjitha funksionet e regjistrimit, si __msg_info, __msg_error dhe të tjera, si raportet për Slack, ruhen veçmas në common/* dhe lidhen dinamikisht në skenarë të tjerë, siç është daily_database_operation.sh.

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

Nëse po ngarkoni burime gjatë ekzekutimit të skriptit, rekomandohet që të ruani të gjithë këto të dhëna në një katalog të përbashkët me një emër të rastësishëm, për shembull /tmp/AlRhYbD97/*. Mund të përdorni gjeneratorë të teksteve rastësore 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 këtyre katalogëve mund të sigurohet në trajtuesit e kapjes, të diskutuar më sipër. Nëse nuk merret parasysh heqja e drejtorive të përkohshme, ato grumbullohen dhe në njëfarë momenti shkaktojnë probleme të papritura në host, si për shembull mbushja e diskut.

Përdorimi i skedarëve lock

Shpesh është e nevojshme të sigurohet që vetëm një instancë e skriptit të ekzekutohet në host në çdo moment. Kjo mund të arrihet me anë të skedarëve lock.

Unë zakonisht krijoj skedarë lock në /tmp/project_name/*.lock dhe kontrolloj praninë e tyre në fillim të skriptit. Kjo ndihmon në përfundimin e duhur të skriptit dhe parandalon ndryshime të papritura të gjendjes së sistemit nga një skript tjetër që funksionon paralelisht. Skedarët lock nuk janë të nevojshëm nëse ju nevojitet që i njëjti skript të ekzekutohet paralelisht në këtë host.

Mësojeni dhe përmirësoni

Na ndonjëherë na nevojitet të punojmë me skenarë që sillen për një periudhë të gjatë, si operacionet përditshme me bazat e të dhënave. Këto operacione zakonisht përfshijnë një rend të hapave: ngarkimi i të dhënave, kontrolli për anomalitë, importi i të dhënave, dërgimi i raporteve mbi gjendjen dhe kështu me radhë.

Në këto raste, unë gjithmonë përpiqem të ndaj skenarin në skripa të vegjël dhe të raportoj mbi gjendjen dhe kohën e ekzekutimit duke përdorur:

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

Më vonë, mund ta shoh kohën e ekzekutimit duke përdorur:

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

Kjo më ndihmon të identifikoj zonat problematike/të ngadalta në skripte që kërkojnë optimizim.

Fat të mirë!

ÇfarĂ« tjetĂ«r mund tĂ« lexoni:

  1. Go dhe cache të GPU.
  2. Shembulli i një aplikacioni me ngjarje të drejtuar nga webhooks në objektin S3 në Mail.ru Cloud Solutions.
  3. Kanali ynë në Telegram për transformimin digjital.

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster