Nejlepší praktiky bash skriptů: stručný průvodce pro spolehlivé a výkonné bash skripty

Nejlepší praktiky bash skriptů: stručný průvodce pro spolehlivé a výkonné bash skripty
Pozadí terminálu od manapi

Ladění skriptů bash je jako hledání jehly v kupce sena, tím spíše když se v existující kódové základně objevují nové dodatky bez řádného posouzení struktury, logování a spolehlivosti. V takových situacích můžete narazit na problémy jak kvůli vlastním chybám, tak i při zvládání složitých chaosů skriptů.

Tým Mail.ru Cloud Solutions přeložila článek s doporučeními, díky kterým budete moci lépe psát, ladit a udržovat své skripty. Chcete věřit, chcete ne, ale nic se nevyrovná pocitu ze psaní čistého, připraveného k použití bash kódu, který funguje pokaždé.

V článku autor sdílí to, co se naučil za posledních několik let, stejně jako některé běžné chyby, které ho překvapily. To je důležité, protože každý vývojář softwaru se v určitém okamžiku své kariéry setká se skripty pro automatizaci rutinních pracovních úloh.

Zpracovatelé signálů

Většina bash skriptů, se kterými jsem se setkal, nikdy nepoužívala efektivní mechanismus úklidu, když během provádění skriptu dojde k něčemu neočekávanému.

Neočekávané události mohou nastat zvenčí, například při obdržení signálu z jádra. Řešení takových případů je zásadní pro to, aby byly skripty dostatečně spolehlivé pro nasazení v produkčních systémech. Často používám zpracovatele výstupů, abych reagoval na takové scénáře:

function handle_exit() {
  
  // Přidejte kód pro úklid zde
  
  // například rm -f "/tmp/${lock_file}.lock"
  
  // výstup s odpovídajícím stavovým kódem
}
  
// zachytit  
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

trap – je vestavěný příkaz shellu, který vám pomáhá registrovat funkci úklidu, která bude zavolána v případě jakýchkoli signálů. Je však třeba věnovat zvláštní pozornost takovým zpracovatelům jako SIGINT, který způsobuje přerušení skriptu.

Navíc je většinou lepší zachycovat pouze EXIT, ale myšlenka je, že můžete skutečně přizpůsobit chování skriptu pro každý jednotlivý signál.

Vestavěné funkce set – rychlé ukončení při chybě

Je velmi důležité reagovat na chyby, jakmile se objeví, a rychle ukončit provádění. Nic není horšího, než pokračovat v provádění příkazu jako je tento:

rm -rf ${directory_name}/*

Všimněte si, že proměnná directory_name není definována.

Pro obsluhu takových scénářů je důležité používat vestavěné funkce set, jako jsou set -o errexit, set -o pipefail nebo set -o nounset na začátku skriptu. Tyto funkce zaručují, že váš skript skončí, jakmile narazí na jakýkoli nenulový kód návratu, použití nedefinovaných proměnných, nesprávné příkazy předávané kanálem atd.:

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

Poznámka: vestavěné funkce, jako jsou set -o errexit, ukončují skript, jakmile se objeví 'nezpracovaný' návratový kód (kromě nuly). Proto je lepší zavést vlastní zpracování chyb, například:

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

Takové psaní skriptů vás nutí pečlivěji věnovat pozornost chování všech příkazů ve skriptu a předvídat možnost vzniku chyby dříve, než vás překvapí.

ShellCheck pro identifikaci chyb během vývoje

Stojí za to integrovat něco jako ShellCheck do vašich vývojových a testovacích pipeline, abyste kontrolovali váš bash kód na dodržování osvědčených praktik.

Používám ho ve svých místních vývojových prostředích k získávání zpráv o syntaxi, sémantice a některých chybách v kódu, které jsem mohl během vývoje přehlédnout. Je to nástroj pro statickou analýzu vašich bash skriptů a vřele doporučuji jeho použití.

Používání vlastních výstupních kódů

Kódy návratu v POSIX jsou více než jen nula nebo jedna, ale nula nebo nenulová hodnota. Využijte těchto možností k vracení vlastních kódů chyb (mezi 201-254) pro různé případy chyb.

Tyto informace mohou být následně použity jinými skripty, které obalují váš, aby přesně pochopily, jaký typ chyby nastal, a reagovaly odpovídajícím způsobem:

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

Poznámka: prosím, buďte obzvláště opatrní s názvy proměnných, které definujete, abyste zabránili neúmyslnému přepisu proměnných prostředí.

Funkce loggerů

Krásné a strukturované vedení logů je důležité pro snadné porozumění výsledkům běhu vašeho skriptu. Stejně jako v jiných programovacích jazycích na vysoké úrovni, vždy používám ve svých bash skriptech své vlastní funkce pro logování, jako například __msg_info, __msg_error a tak dále.

To pomáhá zajistit standardizovanou strukturu vedení logů, přičemž změny se provádějí pouze na jednom místě:

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

Obvykle se snažím mít ve svých skriptech nějaký mechanismus __init, kde jsou inicializovány takové proměnné loggeru a další systémové proměnné, nebo se nastavují na výchozí hodnoty. Tyto proměnné mohou být také nastavovány z parametru příkazové řádky při volání skriptu.

Například něco jako:

$ ./run-script.sh --debug

Když se takový skript vykonává, je zaručeno, že systémová nastavení jsou nastavena na výchozí hodnoty, pokud jsou povinná, nebo alespoň inicializována něčím odpovídajícím, pokud je to nutné.

Obvykle zakládám výběr toho, co inicializovat a co nikoli, na kompromisu mezi uživatelským rozhraním a podrobnostmi konfigurací, do kterých se uživatel může/měl by ponořit.

Architektura pro opětovné využití a čistý stav systému

Modulární / znovu použitelné kódy

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

Mám samostatný repozitář, který může být použit pro inicializaci nového projektu/bash skriptu, který chci vyvinout. Vše, co může být znovu použito, může být uloženo v repozitáři a získáno v jiných projektech, které chtějí využívat takové funkcionality. Taková organizace projektů výrazně snižuje velikost jiných skriptů a také zajišťuje, že kódová základna je malá a snadno testovatelná.

Jak je uvedeno výše, všechny funkce logování, jako například __msg_info, __msg_error a další, například zprávy na Slacku, jsou uloženy samostatně v common/* a dynamicky se načítají v jiných scénářích, jako je daily_database_operation.sh.

Zanechte po sobě čistý systém

Pokud během běhu skriptu načítáte nějaké zdroje, doporučuje se uchovávat všechna taková data ve sdíleném adresáři s náhodným názvem, například /tmp/AlRhYbD97/*Můžete použít generátory náhodného textu k výběru názvu adresáře:

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

Po dokončení práce může být čištění takových adresářů zajištěno zpracovateli událostí, o kterých jsme mluvili výše. Pokud se nepostaráte o odstranění dočasných adresářů, hromadí se a v určitém okamžiku způsobují neočekávané problémy na hostiteli, například zaplněný disk.

Použití lock souborů

Často je potřeba zajistit, aby byl na hostiteli spuštěn vždy pouze jeden instance skriptu. Toho lze dosáhnout pomocí lock souborů.

Obvykle vytvářím lock soubory v /tmp/project_name/*.lock a kontroluji jejich přítomnost na začátku skriptu. To pomáhá správně ukončit práci skriptu a vyhnout se neočekávaným změnám ve stavu systému jinými skripty, běžícími paralelně. Lock soubory nejsou potřeba, pokud potřebujete, aby se stejný skript spouštěl paralelně na tomto hostiteli.

Měřit a zlepšovat

Často musíme pracovat se skripty, které běží po delší dobu, například s denními operacemi databáze. Takové operace obvykle zahrnují sekvenci kroků: načítání dat, kontrolu na anomálie, import dat, odesílání zpráv o stavu a tak dále.

V takových případech se vždy snažím rozdělit skript na jednotlivé malé skripty a informovat o jejich stavu a době běhu pomocí:

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

Později mohu zkontrolovat čas běhu pomocí:

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

To mi pomáhá identifikovat problematické/pomale oblasti ve skriptech, které potřebují optimalizaci.

Hodně štěstí!

Co dalšího si přečíst:

  1. Go a GPU cache.
  2. Příklad aplikace řízené událostmi založené na webhooks v objektovém S3 úložišti Mail.ru Cloud Solutions.
  3. Náš telegramový kanál o digitální transformaci.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster