Najbolji primjeri Bash skriptiranja: Kratki vodič za pouzdane i učinkovite Bash skripte

Najbolji primjeri Bash skriptiranja: Kratki vodič za pouzdane i učinkovite Bash skripte
Pozadina školjke autora manapija

Otklanjanje pogrešaka u bash skriptama je poput traženja igle u plastu sijena, pogotovo kada se pojavljuju novi dodaci u postojećoj bazi koda bez pravovremenog razmatranja pitanja strukture, zapisivanja i pouzdanosti. U takvim situacijama možete se naći ili zbog vlastitih pogrešaka ili prilikom upravljanja složenom hrpom skripti.

Momčad Mail.ru Cloud rješenja preveo je članak s preporukama koje će vam pomoći da bolje pišete, otklanjate pogreške i održavate svoje skripte. Vjerovali ili ne, ništa nije bolje od zadovoljstva pisanja čistog bash koda spremnog za korištenje koji radi svaki put.

U članku autor iznosi ono što je naučio u proteklih nekoliko godina, kao i neke uobičajene pogreške koje su ga zatekle. Ovo je važno jer svaki programer softvera, u nekom trenutku svoje karijere, radi sa skriptama za automatizaciju rutinskih radnih zadataka.

Rukovatelji zamkama

Većina bash skripti s kojima sam se susreo nikada ne koristi učinkovit mehanizam čišćenja kada se nešto neočekivano dogodi tijekom izvođenja skripte.

Iznenađenja mogu nastati izvana, kao što je primanje signala iz jezgre. Rješavanje takvih slučajeva iznimno je važno kako bi se osiguralo da su skripte dovoljno pouzdane za rad na proizvodnim sustavima. Često koristim rukovatelje izlaza da odgovorim na ovakve scenarije:

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 je naredba ugrađena u ljusku koja vam pomaže registrirati funkciju čišćenja koja se poziva u slučaju bilo kakvih signala. Međutim, treba biti posebno oprezan s rukovateljima kao što su SIGINT, što uzrokuje prekid skripte.

Osim toga, u većini slučajeva trebali biste samo uhvatiti EXIT, ali ideja je da zapravo možete prilagoditi ponašanje skripte za svaki pojedinačni signal.

Ugrađene skupne funkcije - brzo prekidanje u slučaju pogreške

Vrlo je važno reagirati na pogreške čim se pojave i brzo zaustaviti izvršenje. Ništa ne može biti gore od nastavka pokretanja naredbe poput ove:

rm -rf ${directory_name}/*

Imajte na umu da varijabla directory_name nije utvrđeno.

Važno je koristiti ugrađene funkcije za rješavanje takvih scenarija set, Kao set -o errexit, set -o pipefail ili set -o nounset na početku scenarija. Ove funkcije osiguravaju da će vaša skripta izaći čim naiđe na bilo koji izlazni kod različit od nule, upotrebu nedefiniranih varijabli, nevažeće naredbe proslijeđene preko cijevi i tako dalje:

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

Napomena: ugrađene funkcije kao što su set -o errexit, izaći će iz skripte čim postoji "neobrađeni" povratni kod (osim nule). Stoga je bolje uvesti prilagođeno rukovanje pogreškama, na primjer:

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

Pisanje skripti na ovaj način tjera vas da budete oprezniji u pogledu ponašanja svih naredbi u skripti i da predvidite mogućnost greške prije nego što vas iznenadi.

ShellCheck za otkrivanje grešaka tijekom razvoja

Vrijedno je integrirati nešto poput ShellCheck u svoje cjevovode za razvoj i testiranje kako biste provjerili svoj bash kod u odnosu na najbolju praksu.

Koristim ga u svojim lokalnim razvojnim okruženjima za dobivanje izvješća o sintaksi, semantici i nekim pogreškama u kodu koje sam možda propustio tijekom razvoja. Ovo je alat za statičku analizu vaših bash skripti i toplo preporučujem njegovu upotrebu.

Korištenje vlastitih izlaznih kodova

Povratni kodovi u POSIX-u nisu samo nula ili jedan, već nula ili različita vrijednost. Koristite ove značajke za vraćanje prilagođenih kodova pogrešaka (između 201-254) za različite slučajeve pogrešaka.

Ove informacije zatim mogu koristiti druge skripte koje obavijaju vašu kako biste točno shvatili koja se vrsta pogreške dogodila i reagirali u skladu s tim:

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

Napomena: budite posebno pažljivi s imenima varijabli koje definirate kako biste izbjegli slučajno nadjačavanje varijabli okruženja.

Funkcije zapisivanja

Lijepo i strukturirano bilježenje važno je za jednostavno razumijevanje rezultata vaše skripte. Kao i kod drugih programskih jezika visoke razine, uvijek koristim izvorne funkcije bilježenja u svojim bash skriptama, kao što je __msg_info, __msg_error i tako dalje.

Ovo pomaže u pružanju standardizirane strukture bilježenja unošenjem promjena na samo jednom mjestu:

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

Obično pokušavam imati nekakav mehanizam u svojim skriptama __init, gdje su takve varijable zapisivača i druge sistemske varijable inicijalizirane ili postavljene na zadane vrijednosti. Ove se varijable također mogu postaviti iz opcija naredbenog retka tijekom pozivanja skripte.

Na primjer, nešto poput:

$ ./run-script.sh --debug

Kada se takva skripta izvrši, ona osigurava da su postavke cijelog sustava postavljene na zadane vrijednosti ako su potrebne ili barem inicijalizirane na nešto prikladno ako je potrebno.

Obično temeljim izbor onoga što inicijalizirati, a što ne učiniti na kompromisu između korisničkog sučelja i detalja konfiguracija u koje korisnik može/treba proniknuti.

Arhitektura za ponovnu upotrebu i čisto stanje sustava

Modularni kod za višekratnu upotrebu

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

Držim zasebno spremište koje mogu koristiti za pokretanje novog projekta/bash skripte koju želim razviti. Sve što se može ponovno upotrijebiti može se pohraniti u repozitorij i dohvatiti drugi projekti koji žele koristiti tu funkcionalnost. Organiziranje projekata na ovaj način značajno smanjuje veličinu drugih skripti i također osigurava da je baza koda mala i laka za testiranje.

Kao u gornjem primjeru, sve funkcije zapisivanja kao što su __msg_info, __msg_error a drugi, kao što su Slack izvješća, nalaze se zasebno u common/* i dinamički se povezivati ​​u drugim scenarijima kao što su daily_database_operation.sh.

Ostavite za sobom čist sustav

Ako učitavate bilo kakve resurse dok skripta radi, preporuča se pohraniti sve takve podatke u zajednički direktorij s nasumičnim nazivom, npr. /tmp/AlRhYbD97/*. Možete koristiti generatore nasumičnog teksta za odabir naziva direktorija:

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

Nakon završetka rada, čišćenje takvih imenika može se osigurati u rukovateljima kukicama o kojima smo gore govorili. Ako se ne vodi računa o privremenim imenicima, oni se nakupljaju i u nekoj fazi uzrokuju neočekivane probleme na glavnom računalu, kao što je pun disk.

Korištenje zaključanih datoteka

Često morate osigurati da samo jedna instanca skripte radi na glavnom računalu u bilo kojem trenutku. To se može učiniti pomoću datoteka zaključavanja.

Obično stvaram zaključane datoteke u /tmp/project_name/*.lock i provjerite njihovu prisutnost na početku skripte. To pomaže skripti da elegantno završi i izbjegne neočekivane promjene stanja sustava od strane druge skripte koja se izvodi paralelno. Datoteke za zaključavanje nisu potrebne ako je potrebno da se ista skripta izvodi paralelno na određenom glavnom računalu.

Mjerite i poboljšavajte

Često moramo raditi sa skriptama koje se izvode tijekom dugih vremenskih razdoblja, kao što su dnevne operacije baze podataka. Takve operacije obično uključuju slijed koraka: učitavanje podataka, provjeru anomalija, uvoz podataka, slanje izvješća o statusu i tako dalje.

U takvim slučajevima uvijek pokušavam razdvojiti skriptu u zasebne male skripte i prijaviti njihov status i vrijeme izvršenja koristeći:

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

Kasnije mogu vidjeti vrijeme izvršenja s:

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

To mi pomaže identificirati problematična/spora područja u skriptama koja trebaju optimizaciju.

Sretno!

Što još pročitati:

  1. Go i GPU predmemorije.
  2. Primjer aplikacije vođene događajima koja se temelji na web dojavljivačima u S3 objektnoj pohrani Mail.ru Cloud Solutions.
  3. Naš telegram kanal o digitalnoj transformaciji.

Izvor: www.habr.com

Dodajte komentar