
A bash szkriptek hibakeresése olyan, mint a tűkeresés a szénakazalban, különösen akkor, amikor új kiegészítések kerülnek a meglévő kódalapba anélkül, hogy időben foglalkoznánk a struktúra, naplózás és megbízhatóság kérdéseivel. Ilyen helyzetekben akár a saját hibáink, akár a bonyolult szkripthalmok kezelése miatt is meglepetések érhetnek minket.
A parancs A cikk egy olyan ajánlást tartalmaz, amely segít jobban írni, hibakeresni és fenntartani a szkripteket. Higgyen benne, vagy ne, de semmi sem hasonlítható ahhoz az elégedettséghez, amely abból fakad, hogy tiszta, használatra kész bash kódot írunk, amely minden alkalommal működik.
A szerző megosztja, amit az elmúlt néhány év alatt tanult, valamint néhány gyakori hibát, amelyek váratlanul érték. Ez fontos, mivel minden szoftverfejlesztő karrierje során bizonyos időpontokban szkriptekkel foglalkozik, amelyek automatizálják a rutin munkahelyi feladatokat.
Kivételkezelők
A legtöbb bash szkript, amellyel találkoztam, soha nem használt hatékony tisztítási mechanizmust, amikor a szkript futása során valami váratlan történik.
A meglepetések kívülről is érkezhetnek, például jelet kaphatunk az kerneltől. Az ilyen esetek kezelése rendkívül fontos annak érdekében, hogy a szkriptek eléggé megbízhatóak legyenek a termelési rendszerekben történő futtatásra. Gyakran használok kilépési kezelőket az ilyen forgatókönyvek kezelésére:
function handle_exit() {
// Tisztítási kód hozzáadása
// például rm -f "/tmp/${lock_file}.lock"
// kilépés megfelelő státusz kóddal
}
// trap
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
trap — egy beépített parancs a shellben, amely segít regisztrálni egy tisztítási funkciót, amelyet bizonyos jelek esetén hívnak meg. Az ilyen kezelők esetén azonban különös óvatosságot kell tanúsítani, mint Néhány szó a parancsok kimenetéről, amely megszakítja a szkript futását.
Ezenkívül a legtöbb esetben csak EXITc, de az ötlet az, hogy valóban testre szabhatja a szkript viselkedését minden egyes jel számára.
A beépített set funkciók — gyors leállás hiba esetén
Nagyon fontos reagálni a hibákra, amint felmerülnek, és gyorsan leállítani a végrehajtást. Nincs más, ami rosszabb lenne, mint folytatni egy olyan parancs végrehajtását, mint ez:
rm -rf ${directory_name}/*
Kérjük, vegye figyelembe, hogy a változó directory_name nincs definiálva.
Az ilyen forgatókönyvek kezeléséhez fontos a beépített funkciók használata setBayotBase set -o errexit, set -o pipefail vagy set -o nounset a script elején. Ezek a funkciók biztosítják, hogy a script azonnal leálljon, amikor bármilyen nem nullás kilépési kódot, nem definiált változók használatát, rossz parancsokat, amelyeket csatornán továbbítottak, és így tovább észlel.
#!/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
Megjegyzés: A beépített funkciók, mint például set -o errexit, kilépnek a scriptből, amint megjelenik egy „kezeletlen” visszakód (nullán kívül). Ezért jobb felhasználói hibakezelést alkalmazni, például:
#!/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
Az ilyen típusú scriptírás arra kényszerít, hogy figyelmesebben kezelje a script összes parancsának viselkedését, és előrelátja a hibák lehetőségét, mielőtt meglepnék.
ShellCheck a hibák észlelésére fejlesztés közben
Érdemes integrálni valamit, mint például , a fejlesztési és tesztelési csatornáihoz, hogy ellenőrizzék a bash kódját a legjobb gyakorlatok alkalmazása szempontjából.
Használom a helyi fejlesztési környezetemben, hogy jelentéseket kapjak a szintaxiról, a szemantikáról és néhány olyan hibáról a kódban, amelyeket elfelejtettem a fejlesztés során. Ez egy statikus elemzőeszköz a bash scriptjeidhez, és erősen ajánlom a használatát.
A saját kilépési kódjaid használata
A POSIX kilépési kódok nem csupán nulla vagy egy, hanem nulla vagy nem nulla értékek. Ezeket a lehetőségeket használja fel a felhasználói hibakódok visszaadására (201-254 között) különböző hibás esetekhez.
Ez az információ később felhasználható más forgatókönyvek által, amelyek a tiédet burkolják, hogy pontosan megértsék, milyen hiba történt, és megfelelően reagáljanak:
#!/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
}
Megjegyzés: kérjük, legyen különösen óvatos a változónevekkel, amelyeket definiál, hogy elkerülje a környezeti változók véletlenszerű felülírását.
Logger funkciók
A szép és jól strukturált naplózás fontos ahhoz, hogy könnyen megérthessük a szkriptjeink végrehajtásának eredményeit. Mint más magas szintű programozási nyelvekben, a bash szkriptjeimben is mindig használok saját naplózási funkciókat, mint például __msg_info, __msg_error és így tovább.
Ez segít biztosítani a standardizált naplózási struktúrát, mivel a változtatásokat csak egy helyen kell elvégezni:
#!/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"
Általában próbálok valamilyen mechanizmust beépíteni a szkriptjeimbe __init, ahol az ilyen naplózó változók és egyéb rendszer változók inicializálódnak vagy alapértelmezett értékekre állítódnak be. Ezek a változók parancssori paraméterekből is beállíthatók a szkript hívásakor.
Például valami ilyesmi:
$ ./run-script.sh --debug
Amikor egy ilyen szkriptet végrehajtanak, garantált, hogy a rendszerbeállítások alapértelmezett értékekre vannak állítva, ha kötelezőek, vagy legalábbis valami megfelelőt inicializálnak, ha ez szükséges.
Általában a választásomat arra alapozom, hogy mit inicializáljak, és mit ne, a felhasználói élmény és a konfigurációs részletek közötti kompromisszumban, amelyekben a felhasználónak bele kell merülnie.
Újra használható és tiszta állapotú rendszerarchitektúra
Moduláris / újrahasználható kód
├── framework
│ ├── common
│ │ ├── loggers.sh
│ │ ├── mail_reports.sh
│ │ └── slack_reports.sh
│ └── daily_database_operation.sh
Külön tárolót tartok fenn, amelyet új projekt / bash szkript kezdeményezésére lehet használni, amelyet szeretnék kifejleszteni. Minden újrahasználható dolog tárolható a tárolóban, és más projektekhez is beszerezhető, amelyek ilyen funkciókat szeretnének használni. Ez a projektorganizáció jelentősen csökkenti a többi szkript méretét, és biztosítja, hogy a kód alapja kicsi és könnyen tesztelhető.
Ahogyan a fenti példában is, minden naplózási funkció, mint például __msg_info, __msg_error és mások, például Slack jelentések, külön tárolódnak a common/* és dinamikusan csatlakoznak más szkriptekhez, mint például daily_database_operation.sh.
Hagyjon maga után egy tiszta rendszert
Ha bármilyen erőforrást betölt a szkript futása közben, ajánlott minden ilyen adatot egy véletlenszerű nevű közös könyvtárban tárolni, például /tmp/AlRhYbD97/*Használhat véletlen szöveggenerátorokat a könyvtár neveinek kiválasztásához:
rand_dir_name="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)"
A munka befejezése után az ilyen könyvtárak tisztítását a fenti csapdakezelők segíthetnek biztosítani. Ha nem gondoskodunk a törölhetetlen ideiglenes könyvtárakról, azok felhalmozódhatnak, és egy idő után váratlan problémákat okozhatnak a hoszton, például megtelt lemez.
Lock fájlok használata
Gyakran szükséges biztosítani, hogy a hoszton bármikor csak egy példányban fusson a szkript. Ezt lock fájlokkal lehet megvalósítani.
Általában lock fájlokat hozok létre itt: /tmp/project_name/*.lock és a szkript elején ellenőrzöm a létezésüket. Ez segít a szkript megfelelő befejezésében és elkerüli, hogy más párhuzamosan futó szkript váratlanul megváltoztassa a rendszer állapotát. Lock fájlok nem szükségesek, ha ugyanazt a szkriptet párhuzamosan kell futtatni a hoszton.
Mérni és javítani
Gyakran olyan szkriptekkel kell dolgoznunk, amelyek hosszú ideig futnak, például napi adatbázis-műveletek. Ezek általában egy lépésről lépésre történő folyamatot tartalmaznak: adatok betöltése, anomáliák ellenőrzése, adatok importálása, állapotjelentések küldése stb.
Ilyen esetekben mindig igyekszem a szkriptet külön kisebb szkriptekre bontani, és az állapotukat és futási idejüket az alábbiak szerint jelenteni:
time source "${filepath}" "${args}" >> "${LOG_DIR}/RUN_LOG" 2>&1
Később meg tudom nézni a futási időt az alábbi paranccsal:
tac "${LOG_DIR}/RUN_LOG.txt" | grep -m1 "real"
Ez segít nekem azonosítani a szkriptek problémás/szűk keresztmetszeti területeit, amelyek optimalizálásra szorulnak.
Sok szerencsét!
Mit érdemes még olvasni:
Forrás: habr.com
