Bash Scripting Best Practices: 'n Vinnige gids tot betroubare en prestasie Bash Scripts

Bash Scripting Best Practices: 'n Vinnige gids tot betroubare en prestasie Bash Scripts
Skulppapier deur manapi

Om bash-skrifte te ontfout is soos om na 'n naald in 'n hooimied te soek, veral wanneer nuwe toevoegings in die bestaande kodebasis verskyn sonder tydige oorweging van kwessies van struktuur, logboek en betroubaarheid. Jy kan jouself in sulke situasies bevind, hetsy as gevolg van jou eie foute of wanneer jy komplekse stapels skrifte bestuur.

Span Mail.ru Wolkoplossings 'n artikel vertaal met aanbevelings wat jou sal help om jou skrifte beter te skryf, te ontfout en in stand te hou. Glo dit of nie, niks klop die bevrediging om skoon, gereed-vir-gebruik bash-kode te skryf wat elke keer werk nie.

In die artikel deel die skrywer wat hy die afgelope paar jaar geleer het, asook 'n paar algemene foute wat hom onkant gevang het. Dit is belangrik omdat elke sagteware-ontwikkelaar, op 'n sekere punt in hul loopbaan, met skrifte werk om roetine-werktake te outomatiseer.

Strikhanteerders

Die meeste bash-skrifte wat ek teëgekom het, gebruik nooit 'n effektiewe skoonmaakmeganisme wanneer iets onverwags tydens die skrifuitvoering gebeur nie.

Verrassings kan van buite af ontstaan, soos om 'n sein van die kern te ontvang. Die hantering van sulke gevalle is uiters belangrik om te verseker dat die skrifte betroubaar genoeg is om op produksiestelsels te werk. Ek gebruik dikwels afrithanteerders om op scenario's soos hierdie te reageer:

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 is 'n dop ingeboude opdrag wat jou help om 'n opruimfunksie te registreer wat geroep word in die geval van enige seine. Spesiale sorg moet egter getref word met hanteerders soos bv SIGINT, wat veroorsaak dat die skrif afbreek.

Daarbenewens moet jy in die meeste gevalle net vang EXIT, maar die idee is dat jy eintlik die gedrag van die skrif vir elke individuele sein kan aanpas.

Ingeboude stel funksies - vinnige beëindiging op fout

Dit is baie belangrik om op foute te reageer sodra dit voorkom en die uitvoering vinnig te stop. Niks kan erger wees as om voort te gaan om 'n opdrag soos hierdie uit te voer nie:

rm -rf ${directory_name}/*

Neem asseblief kennis dat die veranderlike directory_name nie bepaal nie.

Dit is belangrik om ingeboude funksies te gebruik om sulke scenario's te hanteer setsoos set -o errexit, set -o pipefail of set -o nounset aan die begin van die draaiboek. Hierdie funksies verseker dat jou skrip sal verlaat sodra dit enige nie-nul uitgang kode teëkom, gebruik van ongedefinieerde veranderlikes, ongeldige opdragte wat oor 'n pyp gestuur word, ensovoorts:

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

Let wel: ingeboude funksies soos set -o errexit, sal die skrip verlaat sodra daar 'n "rou" terugkeerkode is (behalwe nul). Daarom is dit beter om persoonlike fouthantering in te voer, byvoorbeeld:

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

Om skrifte op hierdie manier te skryf dwing jou om meer versigtig te wees oor die gedrag van al die opdragte in die skrif en die moontlikheid van 'n fout te verwag voordat dit jou verras.

ShellCheck om foute tydens ontwikkeling op te spoor

Dit is die moeite werd om iets soos Shell Check in jou ontwikkeling- en toetspyplyne om jou bash-kode teen beste praktyke na te gaan.

Ek gebruik dit in my plaaslike ontwikkelingsomgewings om verslae te kry oor sintaksis, semantiek en 'n paar foute in die kode wat ek dalk gemis het tydens die ontwikkeling. Dit is 'n statiese analise-instrument vir jou bash-skrifte en ek beveel sterk aan om dit te gebruik.

Gebruik jou eie uitgang kodes

Terugkeerkodes in POSIX is nie net nul of een nie, maar nul of 'n nie-nul waarde. Gebruik hierdie kenmerke om persoonlike foutkodes (tussen 201-254) vir verskeie foutgevalle terug te gee.

Hierdie inligting kan dan gebruik word deur ander skrifte wat joune omvou om presies te verstaan ​​watter tipe fout voorgekom het en dienooreenkomstig te reageer:

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

Let wel: wees asseblief veral versigtig met die veranderlikename wat jy definieer om te verhoed dat omgewingsveranderlikes per ongeluk oorheers word.

Teken funksies

Pragtige en gestruktureerde aantekeninge is belangrik om die resultate van jou skrif maklik te verstaan. Soos met ander hoëvlak-programmeertale, gebruik ek altyd inheemse aantekenfunksies in my bash-skrifte, soos bv. __msg_info, __msg_error en so aan.

Dit help om 'n gestandaardiseerde logstruktuur te verskaf deur veranderinge op slegs een plek aan te bring:

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

Ek probeer gewoonlik om 'n soort meganisme in my skrifte te hê __init, waar sulke logboekveranderlikes en ander stelselveranderlikes geïnisialiseer of op verstekwaardes gestel word. Hierdie veranderlikes kan ook ingestel word vanaf opdragreëlopsies tydens skripaanroeping.

Byvoorbeeld, iets soos:

$ ./run-script.sh --debug

Wanneer so 'n skrip uitgevoer word, verseker dit dat stelselwye instellings op verstekwaardes gestel word as dit vereis word, of ten minste geïnisialiseer word na iets toepaslik indien nodig.

Ek baseer gewoonlik die keuse van wat om te inisialiseer en wat om nie te doen nie op 'n afweging tussen die gebruikerskoppelvlak en die besonderhede van die konfigurasies waarin die gebruiker kan/moet delf.

Argitektuur vir hergebruik en skoon stelseltoestand

Modulêre/herbruikbare kode

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

Ek hou 'n aparte bewaarplek wat ek kan gebruik om 'n nuwe projek/bash script wat ek wil ontwikkel, te inisialiseer. Enigiets wat hergebruik kan word, kan in 'n bewaarplek gestoor word en herwin word deur ander projekte wat daardie funksionaliteit wil gebruik. Om projekte op hierdie manier te organiseer, verminder die grootte van ander skrifte aansienlik en verseker ook dat die kodebasis klein is en maklik is om te toets.

Soos in die voorbeeld hierbo, alle aanteken funksies soos __msg_info, __msg_error en ander, soos Slack-verslae, word afsonderlik vervat in common/* en dinamies verbind in ander scenario's soos daily_database_operation.sh.

Laat 'n skoon stelsel agter

As jy enige hulpbronne laai terwyl die skrip loop, word dit aanbeveel om al sulke data in 'n gedeelde gids met 'n ewekansige naam te stoor, bv. /tmp/AlRhYbD97/*. Jy kan ewekansige teksopwekkers gebruik om die gidsnaam te kies:

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

Na voltooiing van werk, kan opruiming van sulke gidse verskaf word in die haakhanteerders wat hierbo bespreek is. As daar nie na tydelike gidse omgesien word nie, versamel hulle en veroorsaak dit op 'n stadium onverwagte probleme op die gasheer, soos 'n vol skyf.

Gebruik sluitlêers

Dikwels moet jy verseker dat slegs een instansie van 'n skrip op 'n gasheer op enige gegewe tydstip loop. Dit kan gedoen word met behulp van slotlêers.

Ek skep gewoonlik sluitlêers in /tmp/project_name/*.lock en kyk vir hul teenwoordigheid aan die begin van die draaiboek. Dit help om die skrif grasieus te beëindig en onverwagte veranderinge aan die stelseltoestand te vermy deur 'n ander skrif wat parallel loop. Sluitlêers is nie nodig as jy dieselfde skrip nodig het om parallel op 'n gegewe gasheer uitgevoer te word nie.

Meet en verbeter

Ons moet dikwels met skrifte werk wat oor lang tydperke loop, soos daaglikse databasisbewerkings. Sulke bewerkings behels tipies 'n reeks stappe: laai van data, kyk vir afwykings, invoer van data, stuur statusverslae, ensovoorts.

In sulke gevalle probeer ek altyd om die skrif in afsonderlike klein skrifte op te breek en hul status en uitvoeringstyd te rapporteer deur:

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

Later kan ek die uitvoeringstyd sien met:

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

Dit help my om probleem-/stadige areas in skrifte te identifiseer wat geoptimaliseer moet word.

Sterkte!

Wat anders om te lees:

  1. Gaan en GPU-kas.
  2. 'n Voorbeeld van 'n gebeurtenisgedrewe toepassing gebaseer op webhooks in die S3-objekberging van Mail.ru Cloud Solutions.
  3. Ons telegramkanaal oor digitale transformasie.

Bron: will.com

Voeg 'n opmerking