Modul 4 — Skriptování a automatizace
17. Pokročilé Bash skriptování a error handling
Funkce v Bashi, bezpečné zpracování chyb pomocí "set -euo pipefail", trap pro úklid a práce s poli a asociativními poli.
Úvod a kontext
Předchozí lekce ukázala základní stavební prvky Bash skriptů. Skripty
používané v provozu (deploy skripty, zálohovací úlohy, automatizace
spouštěná z CI pipeline) ale musí být odolnější — musí se bezpečně
chovat, když něco selže uprostřed běhu, musí umět uklidit po sobě i
v případě chyby a musí jít snadno testovat a udržovat. Tato lekce
navazuje na základy a ukazuje techniky, které oddělují "skript, co
funguje, když všechno jde dobře" od skriptu bezpečného pro produkční
použití.
Teorie
Výchozí chování Bashe je nebezpečné
Bez dalšího nastavení Bash ve výchozím stavu:
- pokračuje v běhu i po selhání příkazu (chyba se jen tiše ztratí,
pokud výsledek nikdo nekontroluje), - nehlásí chybu při použití nedefinované proměnné (typicky kvůli
překlepu v názvu), - v rouře (
pipe) bere jako výsledný návratový kód jen kód
posledního příkazu, i když ten uprostřed selhal.
To vede k tichým chybám — skript "doběhne", ale s neúplným nebo
špatným výsledkem. Proto se v produkčních skriptech doporučuje hned
na začátek přidat:
set -euo pipefail
-e— skript se okamžitě ukončí, jakmile libovolný příkaz
vrátí nenulový návratový kód (s několika výjimkami, např. uvnitř
podmínkyif).-u— použití nedefinované proměnné je chyba, skript se
ukončí, místo aby ji tiše nahradil prázdným řetězcem.-o pipefail— návratový kód roury (příkaz1 | příkaz2) je
nenulový, pokud selže kterýkoliv příkaz v ní, ne jen poslední.
Funkce
Funkce umožňují rozdělit skript na pojmenované, znovupoužitelné
bloky. Definují se jednoduše, bez klíčového slova pro parametry —
uvnitř se k argumentům funkce přistupuje stejně jako k argumentům
skriptu ($1, $2, $#):
zaloguj() {
echo "[$(date +%H:%M:%S)] $1"
}
Funkce může vracet číselný stav přes return N (jde do $?, stejně
jako u skriptu) nebo "vracet" textový výstup přes echo, který
volající zachytí substitucí $(funkce).
trap — reakce na signály a úklid
trap zaregistruje příkaz nebo funkci, která se spustí při zachycení
signálu nebo při ukončení skriptu — bez ohledu na to, jestli skript
skončil úspěchem, chybou, nebo byl přerušen (Ctrl+C). Nejčastější
využití je úklid dočasných souborů, i když skript selže uprostřed:
docasny_soubor=$(mktemp)
trap 'rm -f "$docasny_soubor"' EXIT
EXIT je pseudo-signál, který Bash spustí vždy při ukončení skriptu
(ať už normálním, nebo kvůli set -e). Díky tomu se dočasný soubor
smaže i v případě, že skript spadne na chybě uprostřed zpracování.
Kontrola chyb u konkrétních příkazů
I se zapnutým set -e je někdy žádoucí selhání jednoho konkrétního
příkazu ošetřit ručně (např. abyste vypsali smysluplnější chybovou
hlášku), místo aby skript rovnou spadl:
if ! curl -sf https://api.example.com/health > /tmp/health.json; then
echo "Chyba: healthcheck endpoint neodpověděl." >&2
exit 1
fi
Přesměrování >&2 posílá chybovou hlášku na standardní chybový výstup
(stderr), ne na standardní výstup (stdout) — důležitá konvence, díky
které lze skutečný výstup skriptu (stdout) přesměrovat do souboru
odděleně od chybových hlášek.
Pole a asociativní pole
Kromě běžných (indexovaných) polí z předchozí lekce podporuje Bash i
asociativní pole (klíč–hodnota), užitečná např. pro mapování
prostředí na konfiguraci:
declare -A porty
porty[web]=80
porty[api]=8080
porty[db]=5432
for sluzba in "${!porty[@]}"; do
echo "$sluzba naslouchá na portu ${porty[$sluzba]}"
done
"${!porty[@]}" vrátí seznam klíčů asociativního pole.
Praktický příklad
Robustnější verze zálohovacího skriptu s set -euo pipefail,
funkcemi a úklidem přes trap:
#!/bin/bash
set -euo pipefail
ZDROJ="/var/www/data"
CIL="/backup"
zaloguj() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
uklid() {
local kod=$?
if [ -n "${docasny_archiv:-}" ] && [ -f "$docasny_archiv" ]; then
rm -f "$docasny_archiv"
zaloguj "Dočasný soubor smazán (úklid při ukončení)."
fi
if [ "$kod" -ne 0 ]; then
zaloguj "Skript skončil s chybou (kód $kod)."
fi
exit "$kod"
}
trap uklid EXIT
if [ ! -d "$ZDROJ" ]; then
zaloguj "Chyba: zdrojový adresář $ZDROJ neexistuje." >&2
exit 1
fi
docasny_archiv=$(mktemp /tmp/zaloha-XXXXXX.tar.gz)
zaloguj "Vytvářím archiv $docasny_archiv..."
tar -czf "$docasny_archiv" -C "$ZDROJ" .
zaloguj "Přesouvám archiv do $CIL..."
mv "$docasny_archiv" "$CIL/zaloha-$(date +%Y%m%d-%H%M%S).tar.gz"
zaloguj "Záloha dokončena úspěšně."
Klíčové body v tomto skriptu:
set -euo pipefailzajistí, že selhánítarnebomvskript
okamžitě ukončí, místo aby pokračoval s neúplnou zálohou.trap uklid EXITzaručí, že sedocasny_archivuklidí (pokud ještě
existuje) i při chybě uprostřed běhu, a zaloguje výsledný stav.local kod=$?uvnitř funkce zachytí návratový kód posledního
příkazu předtím, než ho samotné příkazy uvnitř funkce (rm,if)
přepíší — proto se čte hned na prvním řádku funkceuklid."${docasny_archiv:-}"je bezpečný zápis i se zapnutýmset -u—
pokud proměnná ještě není nastavená (skript spadl dřív, než se
vytvořila),:-dosadí prázdný řetězec místo chyby.
Shrnutí
Produkční Bash skripty by měly začínat set -euo pipefail, aby
tiché chyby (selhání příkazu, nedefinovaná proměnná, selhání uprostřed
roury) vedly k okamžitému a viditelnému ukončení místo pokračování s
poškozeným stavem. Funkce umožňují rozdělit logiku na znovupoužitelné
bloky, trap ... EXIT zajišťuje spolehlivý úklid (dočasné soubory,
logování výsledku) bez ohledu na to, jak skript skončil, a
asociativní pole (declare -A) umožňují přehledně pracovat s
mapováním klíč–hodnota přímo v shellu.
Kontrolní otázky
- Co konkrétně dělají přepínače
-e,-ua-o pipefailv příkazu
set -euo pipefail? - Proč se
trap ... EXITtypicky používá pro úklid dočasných
souborů, a ne jen úklidový kód na konec skriptu? - K čemu slouží přesměrování
>&2a proč je důležité oddělovat
chybové hlášky od běžného výstupu skriptu? - Upravte ukázkový zálohovací skript tak, aby při chybějícím
cílovém adresáři$CIL(nikoliv zdrojovém) vypsal srozumitelnou
chybu na stderr a skončil s návratovým kódem 1 ještě předtím, než
se vytvoří dočasný archiv.
Lekce na sebe nejsou zamčené — libovolnou lekci můžete otevřít i označit jako hotovou v jakémkoliv pořadí.