A bash-scriptek legjobb gyakorlatai: rövid útmutató a megbízható és teljesítményorientált bash scriptekhez

A bash-scriptek legjobb gyakorlatai: rövid útmutató a megbízható és teljesítményorientált bash scriptekhez
Shell háttérkép manapi által

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 Mail.ru Cloud Solutions 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 ShellCheck , 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:

  1. Go és GPU cache-ek.
  2. Példa esemény-vezérelt alkalmazásra webhook-okkal az S3 tárhely objektumainál a Mail.ru Cloud Solutions platformon.
  3. Telegram csatornánk a digitális átalakulásról.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster