Bash Scripting Best Practices: A Quick Guide to Reliable and Performance Bash Scripts

Bash Scripting Best Practices: A Quick Guide to Reliable and Performance Bash Scripts
Wallpaper Shell da manapi

Debugging bash scripts hè cum'è a ricerca di una agulla in un panu di paglia, soprattuttu quandu i novi aghjunti appariscenu in a basa di codice esistente senza cunsiderà puntuale di prublemi di struttura, logging è affidabilità. Pudete truvà in tali situazioni sia per via di i vostri propri sbagli, sia per gestisce cumuli cumplessi di scripts.

squadra Mail.ru Soluzioni Cloud traduttu un articulu cù cunsiglii chì vi aiuterà à scrive, debug è mantene i vostri scripts megliu. Cridite o micca, nunda ùn batte a satisfaczione di scrive un codice bash pulitu, prontu à aduprà chì travaglia ogni volta.

In l'articulu, l'autore sparte ciò ch'ellu hà amparatu in l'ultimi anni, è ancu parechji sbagli cumuni chì l'anu chjapputu. Questu hè impurtante perchè ogni sviluppatore di software, in un certu puntu di a so carriera, travaglia cù scripts per automatizà e attività di travagliu di rutina.

Manipulatori di trappule

A maiò parte di i scripts bash chì aghju scontru ùn utilizanu mai un mecanismu di pulizia efficace quandu qualcosa inesperu succede durante l'esekzione di u script.

Surprises ponu esse da l'esternu, cum'è riceve un signalu da u core. A gestione di tali casi hè estremamente impurtante per assicurà chì i scripts sò abbastanza affidabili per eseguisce in sistemi di produzzione. Aghju spessu aduprà i gestori di uscita per risponde à scenarii cum'è questu:

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 hè un cumandamentu integratu di cunchiglia chì vi aiuta à registrà una funzione di pulizia chì hè chjamata in casu di qualsiasi signali. In ogni casu, una cura particulari deve esse pigliatu cù i manichini cum'è SIGINT, chì provoca l'abortu di u script.

Inoltre, in a maiò parte di i casi, duvete solu catturà EXIT, ma l'idea hè chì pudete veramente persunalizà u cumpurtamentu di u script per ogni signale individuale.

Funzioni di set integrate - terminazione rapida per errore

Hè assai impurtante per risponde à l'errori appena si verificanu è cessà l'esecuzione rapidamente. Nunda puderia esse peghju chè cuntinuà à eseguisce un cumandamentu cum'è questu:

rm -rf ${directory_name}/*

Per piacè nutate chì a variabile directory_name micca determinatu.

Hè impurtante d'utilizà funzioni integrate per trattà tali scenarii set, cum'è set -o errexit, set -o pipefail o set -o nounset à u principiu di u script. Queste funzioni assicuranu chì u vostru script esce appena scontru qualsiasi codice di uscita non zero, usu di variàbili indefiniti, cumandamenti invalidi passati nantu à una pipa, è cusì:

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

Nutate bè: funzioni integrate cum'è set -o errexit, esce da u script appena ci hè un codice di ritornu "bruttu" (altru chè zero). Dunque, hè megliu intruduce a gestione di l'errore persunalizata, per esempiu:

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

Scrivite scripts in questu modu vi forza à esse più attenti à u cumpurtamentu di tutti i cumandamenti in u script è anticipate a pussibilità di un errore prima di piglià per sorpresa.

ShellCheck per detectà errori durante u sviluppu

Vale a pena integrà qualcosa cum'è Cuntrolla di Shell in i vostri pipeline di sviluppu è di prova per verificà u vostru codice bash cù e migliori pratiche.

L'aghju utilizatu in i mo ambienti di sviluppu lucale per uttene rapporti nantu à a sintassi, a semantica è certi errori in u codice chì puderia avè missu durante u sviluppu. Questu hè un strumentu di analisi statica per i vostri scripts bash è vi cunsigliu assai di aduprà.

Utilizendu i vostri codici di uscita

I codici di ritornu in POSIX ùn sò micca solu cero o unu, ma cero o un valore micca zero. Aduprate queste funzioni per rinvià codici d'errore persunalizati (trà 201-254) per parechji casi d'errore.

Questa infurmazione pò esse aduprata da altri script chì impacchendu u vostru per capisce esattamente quale tipu d'errore hè accadutu è reagisce in cunseguenza:

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

Nutate bè: per piacè esse particularmente attenti cù i nomi di variabile chì definisce per evità di annullà accidentalmente variabili di l'ambiente.

Funzioni di logging

Logging bellu è strutturatu hè impurtante per capisce facilmente i risultati di u vostru script. Cum'è cù altre lingue di prugrammazione d'altu livellu, aghju sempre aduprà funzioni di logging nativu in i mo script bash, cum'è __msg_info, __msg_error e accussì on.

Questu aiuta à furnisce una struttura di logu standardizata facendu cambiamenti in un solu locu:

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

Di solitu pruvà à avè qualchì tipu di mecanismu in i mo script __init, induve tali variabili di logger è altre variabili di u sistema sò inizializzate o stabilite à i valori predeterminati. Queste variabili ponu ancu esse stabilite da l'opzioni di linea di cummanda durante l'invucazione di script.

Per esempiu, qualcosa cum'è:

$ ./run-script.sh --debug

Quandu un tali script hè eseguitu, assicura chì i paràmetri di u sistema sò stabiliti à i valori predeterminati se sò necessarii, o almenu inizializzati à qualcosa appropritatu se necessariu.

Di solitu basu l'scelta di ciò chì inizializza è ciò chì ùn deve micca fà nantu à un scambiu trà l'interfaccia d'utilizatore è i dettagli di e cunfigurazioni chì l'utilizatore pò / deve sfondà.

Architettura per a riutilizazione è u statu di sistema pulitu

Codice modulare/riutilizzabile

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

Mantene un repositoriu separatu chì possu aduprà per inizializà un novu prughjettu / script bash chì vogliu sviluppà. Tuttu ciò chì pò esse riutilizatu pò esse guardatu in un repository è ricuperatu da altri prughjetti chì volenu aduprà sta funziunalità. L'urganizazione di prughjetti in questu modu riduce significativamente a dimensione di l'altri script è assicura ancu chì a basa di codice hè chjuca è faciule da pruvà.

Cum'è in l'esempiu sopra, tutte e funzioni di logging cum'è __msg_info, __msg_error è altri, cum'è i rapporti Slack, sò cuntenuti separatamente in common/* è cunnetta dinamicamente in altri scenarii cum'è daily_database_operation.sh.

Lascià un sistema pulitu

Sè vo caricate qualsiasi risorse mentre u script hè in esecuzione, hè cunsigliatu di almacenà tutti tali dati in un repertoriu spartutu cù un nome aleatoriu, p.e. /tmp/AlRhYbD97/*. Pudete utilizà generatori di testu aleatoriu per selezziunà u nome di u cartulare:

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

Dopu à a fine di u travagliu, a pulizia di tali repertorii pò esse furnita in i manichi di ganciu discututi sopra. Se i cartulari tempuranee ùn sò micca curati, s'accumulanu è in un certu stadiu causanu prublemi inespettati nantu à l'ospiti, cum'è un discu sanu.

Utilizà i schedarii di serratura

Spessu avete bisognu di assicurà chì una sola istanza di un script hè in esecuzione in un òspite in ogni mumentu. Stu pò esse fattu cù i schedari serratura.

Di solitu creanu i schedarii di serratura /tmp/project_name/*.lock è verificate a so prisenza à u principiu di u script. Questu aiuta u script à finisce graziamente è evità cambiamenti inespettati à u statu di u sistema da un altru script in parallelu. I schedarii di serratura ùn sò micca necessariu s'ellu avete bisognu di u listessu script per esse eseguitu in parallelu nantu à un òspite datu.

Misura è migliurà

Avemu spessu bisognu di travaglià cù scripts chì currenu longu periudi di tempu, cum'è operazioni di basa di dati di ogni ghjornu. Tali operazioni generalmente implicanu una sequenza di passi: carica di dati, verificazione di anomalie, importazione di dati, invià rapporti di statutu, etc.

In tali casi, sempre pruvate di rompe u script in picculi scripts separati è rappurtate u so statutu è u tempu di esecuzione usendu:

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

In seguitu possu vede u tempu di esecuzione cù:

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

Questu m'aiuti à identificà e zone problematiche / lente in scripts chì necessitanu ottimisazione.

Bona furtuna!

Cosa altru à leghje:

  1. Vai è cache GPU.
  2. Un esempiu di una applicazione guidata da l'avvenimentu basatu annantu à i webhooks in l'almacenamiento d'uggetti S3 di Mail.ru Cloud Solutions.
  3. U nostru canale di telegramma nantu à a trasfurmazioni digitale.

Source: www.habr.com

Add a comment