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
Shell Tapete vum manapi

Debugging Bash Scripten ass wéi no enger Nadel an engem Heustapel ze sichen, besonnesch wann nei Ergänzunge an der existéierender Codebase erschéngen ouni rechtzäiteg Iwwerleeung vu Problemer vu Struktur, Logbicher an Zouverlässegkeet. Dir kënnt Iech an esou Situatiounen fannen entweder wéinst Ären eegene Feeler oder wann Dir komplex Koup Skripte verwalten.

Equipe Mail.ru Cloud Léisunge en Artikel mat Empfehlungen iwwersat, déi Iech hëllefen, Är Scripte besser ze schreiwen, debuggen an z'erhalen. Gleeft et oder net, näischt schléit d'Zefriddenheet fir propper ze schreiwen, prett-ze-benotzen Bash Code deen all Kéier funktionnéiert.

Am Artikel deelt den Auteur dat wat hien an de leschte Jore geléiert huet, wéi och e puer gemeinsame Feeler, déi hien iwwerwaacht hunn. Dëst ass wichteg well all Softwareentwéckler, iergendwann an hirer Carrière, mat Skripte schafft fir routinesch Aarbechtsaufgaben ze automatiséieren.

Trap Handler

Déi meescht Bash Scripten, déi ech begéint hunn, benotzen ni en effektive Botzmechanismus wann eppes Onerwaart während der Skript Ausféierung geschitt.

Iwwerraschungen kënne vu baussen entstoen, wéi zum Beispill e Signal vum Kär ze kréien. D'Handhabung vun esou Fäll ass extrem wichteg fir sécherzestellen datt d'Skripte zouverlässeg genuch sinn fir op Produktiounssystemer ze lafen. Ech benotzen dacks Exit Handler fir op Szenarie wéi dës ze reagéieren:

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 ass e Shell agebaute Kommando deen Iech hëlleft eng Botzfunktioun z'registréieren déi am Fall vun Signaler genannt gëtt. Besonnesch Suergfalt sollt awer mat Handler wéi z.B SIGINT, wouduerch de Skript ofgebrach gëtt.

Zousätzlech, am meeschte Fäll sollt Dir nëmmen fänken EXIT, awer d'Iddi ass datt Dir tatsächlech d'Behuele vum Skript fir all eenzel Signal personaliséiere kënnt.

Built-in Set Funktiounen - séier Ofschloss op Feeler

Et ass ganz wichteg op Feeler z'äntwerten soubal se optrieden an d'Ausféierung séier stoppen. Näischt kéint méi schlëmm sinn wéi weider e Kommando wéi dëst auszeféieren:

rm -rf ${directory_name}/*

Maacht weg datt d'Variabel directory_name net bestëmmt.

Et ass wichteg agebaute Funktiounen ze benotzen fir sou Szenarie ze handhaben set, wéi zum Beispill set -o errexit, set -o pipefail oder set -o nounset am Ufank vum Skript. Dës Funktiounen garantéieren datt Äre Skript erausgeet soubal et all net-null Ausgangscode begéint, d'Benotzung vun ondefinéierte Variablen, ongëlteg Kommandoen iwwer eng Päif passéiert, a sou weider:

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

Opgepasst: agebaute Funktiounen wéi z set -o errexit, wäert de Skript erausgoen soubal et e "raw" Retourcode gëtt (ausser Null). Dofir ass et besser fir personaliséiert Fehlerhandhabung anzeféieren, zum Beispill:

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

Schrëften op dës Manéier schreiwen zwéngt Iech méi virsiichteg iwwer d'Behuele vun all de Kommandoen am Skript ze sinn an d'Méiglechkeet vun engem Feeler virzegoen ier et Iech iwwerrascht.

ShellCheck fir Feeler während der Entwécklung z'entdecken

Et ass derwäert esou eppes z'integréieren ShellCheck an Är Entwécklung an Tester Pipelines fir Äre Bash Code géint beschten Praktiken ze kontrolléieren.

Ech benotzen et a mengem lokalen Entwécklungsëmfeld fir Berichter iwwer Syntax, Semantik an e puer Feeler am Code ze kréien, déi ech beim Entwécklung verpasst hunn. Dëst ass e statesche Analyseinstrument fir Är Bash Scripten an ech recommandéieren et ze benotzen.

Benotzen Är eege Sortie Coden

Retour Coden an POSIX sinn net nëmmen null oder eent, mee null oder engem Net-Null Wäert. Benotzt dës Fonctiounen fir personaliséiert Feelercoden (tëscht 201-254) fir verschidde Feelerfäll zréckzekommen.

Dës Informatioun kann dann vun anere Skripte benotzt ginn, déi Är wéckelen fir genee ze verstoen wéi eng Zort Feeler geschitt ass an deementspriechend reagéieren:

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

Opgepasst: Sief w.e.g. besonnesch virsiichteg mat de Variabelennimm déi Dir definéiert fir ze vermeiden datt zoufälleg Ëmfeldvariablen iwwerschreiden.

Logging Funktiounen

Schéin a strukturéiert Logbicher ass wichteg fir d'Resultater vun Ärem Skript einfach ze verstoen. Wéi mat anere Programméierungssproochen op héijem Niveau benotzen ech ëmmer gebierteg Logbicherfunktiounen a menge Bash Scripten, wéi z. __msg_info, __msg_error an sou op.

Dëst hëlleft eng standardiséiert Logstruktur ze bidden andeems Dir Ännerungen op nëmmen enger Plaz maacht:

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

Ech probéieren normalerweis eng Zort Mechanismus a mengem Scripten ze hunn __init, wou esou Loggervariablen an aner Systemvariablen initialiséiert oder op Standardwäerter gesat ginn. Dës Variablen kënnen och aus Kommandozeiloptioune während der Skript-Aufruffung gesat ginn.

Zum Beispill, eppes wéi:

$ ./run-script.sh --debug

Wann esou e Skript ausgefouert gëtt, garantéiert et datt systembreet Astellungen op Standardwäerter gesat ginn wa se erfuerderlech sinn, oder op d'mannst initialiséiert op eppes passend wann néideg.

Ech baséieren normalerweis d'Wiel vu wat fir initialiséieren a wat net ze maachen op engem Austausch tëscht der User-Interface an d'Detailer vun den Konfiguratiounen, déi de Benotzer ka/soll verdéiwen.

Architektur fir weiderbenotzen a propper System Staat

Modular / reusable Code

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

Ech halen e separaten Repository deen ech benotze kann fir en neie Projet / Bash Skript ze initialiséieren deen ech wëll entwéckelen. Alles wat erëmbenotzt ka ginn, kann an engem Repository gespäichert ginn an duerch aner Projeten erëmgewielt ginn, déi dës Funktionalitéit benotze wëllen. D'Organisatioun vun Projeten op dës Manéier reduzéiert d'Gréisst vun anere Skripte wesentlech a garantéiert och datt d'Codebasis kleng ass an einfach ze testen.

Wéi am Beispill hei uewen, all Logfunktioune wéi z __msg_info, __msg_error an anerer, sou wéi Slack Berichter, sinn separat an common/* an dynamesch Verbindung an anere Szenarie wéi daily_database_operation.sh.

Verloossen hannert engem propper System

Wann Dir Ressourcen lued wärend de Skript leeft, ass et recommandéiert all dës Donnéeën an engem gemeinsame Verzeechnes mat engem zoufällegem Numm ze späicheren, z.B. /tmp/AlRhYbD97/*. Dir kënnt zoufälleg Textgeneratoren benotze fir den Numm vum Verzeichnis ze wielen:

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

Nom Ofschloss vun der Aarbecht kann d'Botzen vun esou Verzeichnisser an den Hook Handleren uewe diskutéiert ginn. Wann temporär Verzeichnisser net gekëmmert ginn, accumuléieren se a verursaache iergendwann onerwaart Probleemer um Host, sou wéi eng voll Disk.

Benotzt Spär Dateien

Dacks musst Dir suergen datt nëmmen eng Instanz vun engem Skript op engem Host zu all Moment leeft. Dëst kann mat Sperrdateien gemaach ginn.

Ech erstellen normalerweis Spärdateien an /tmp/project_name/*.lock a kontrolléiert hir Präsenz am Ufank vum Skript. Dëst hëlleft dem Skript graziéis ofzeschléissen an onerwaart Ännerungen am Systemzoustand vun engem anere Skript parallel ze vermeiden. Spär Dateien sinn net néideg wann Dir dee selwechte Skript braucht fir parallel op engem bestëmmte Host auszeféieren.

Moossen a verbesseren

Mir mussen dacks mat Skripte schaffen, déi iwwer laang Zäitperioden lafen, sou wéi alldeeglech Datebankoperatiounen. Esou Operatiounen involvéieren typesch eng Sequenz vu Schrëtt: Lueden vun Donnéeën, Iwwerpréiwung vun Anomalien, Datenimport, Statusberichter schécken, asw.

An esou Fäll probéieren ech ëmmer de Skript an getrennten kleng Scripten ze briechen an hir Status an Ausféierungszäit ze berichten mat:

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

Méi spéit kann ech d'Ausféierungszäit gesinn mat:

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

Dëst hëlleft mir Problem / lues Beräicher an Scripten z'identifizéieren déi Optimisatioun brauchen.

Vill Gléck!

Wat soss ze liesen:

  1. Go an GPU Cache.
  2. E Beispill vun enger Event-Undriff Applikatioun baséiert op Webhooks an der S3 Objektlagerung vu Mail.ru Cloud Solutions.
  3. Eisen Telegramkanal iwwer digital Transformatioun.

Source: will.com

Setzt e Commentaire