A Bash Scripting bevált gyakorlatai: Gyors útmutató a megbízható és teljesítményű Bash szkriptekhez

A Bash Scripting bevált gyakorlatai: Gyors útmutató a megbízható és teljesítményű Bash szkriptekhez
Shell tapéta a manapitól

A bash szkriptek hibakeresése olyan, mintha tűt keresnénk a szénakazalban, különösen akkor, ha új kiegészítések jelennek meg a meglévő kódbázisban anélkül, hogy időben figyelembe vennénk a struktúra, a naplózás és a megbízhatóság kérdéseit. Ilyen helyzetekbe kerülhet akár saját hibái miatt, akár bonyolult szkripthalmok kezelése során.

Csapat Mail.ru Cloud Solutions lefordított egy cikket olyan ajánlásokkal, amelyek segítenek a szkriptek jobb megírásában, hibakeresésében és karbantartásában. Akár hiszi, akár nem, semmi sem fogja felülmúlni a tiszta, használatra kész bash kód írásának megelégedését, amely minden alkalommal működik.

A cikkben a szerző megosztja, hogy mit tanult az elmúlt években, valamint néhány gyakori hibát, amelyek megfogták. Ez azért fontos, mert karrierje bizonyos pontján minden szoftverfejlesztő szkriptekkel dolgozik a rutin munkafeladatok automatizálása érdekében.

Csapdakezelők

A legtöbb bash szkript, amellyel találkoztam, soha nem használ hatékony tisztítási mechanizmust, ha valami váratlan történik a szkript végrehajtása során.

Meglepetések adódhatnak kívülről, például a magból érkező jel vétele. Az ilyen esetek kezelése rendkívül fontos annak biztosítása érdekében, hogy a szkriptek elég megbízhatóak legyenek az éles rendszereken való futtatáshoz. Gyakran használok kilépéskezelőket az ehhez hasonló forgatókönyvekre való reagáláshoz:

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 egy shell beépített parancs, amely segít regisztrálni egy tisztítási függvényt, amely bármilyen jel esetén meghívódik. Különös óvatossággal kell azonban eljárni az olyan kezelőkkel, mint pl SIGINT, ami miatt a szkript megszakad.

Ráadásul a legtöbb esetben csak fogni szabad EXIT, de az ötlet az, hogy valóban testreszabhatja a szkript viselkedését minden egyes jelhez.

Beépített beállított funkciók - gyors leállítás hiba esetén

Nagyon fontos, hogy azonnal reagáljunk a hibákra, és gyorsan leállítsuk a végrehajtást. Semmi sem lehet rosszabb, mint egy ehhez hasonló parancs futtatása:

rm -rf ${directory_name}/*

Felhívjuk figyelmét, hogy a változó directory_name nem meghatározott.

Az ilyen forgatókönyvek kezeléséhez fontos a beépített függvények használata set, Mint set -o errexit, set -o pipefail vagy set -o nounset a forgatókönyv elején. Ezek a függvények biztosítják, hogy a szkript azonnal kilépjen, amint bármilyen nem nulla kilépési kóddal, definiálatlan változók használatával, csőben átadott érvénytelen parancsokkal és így tovább találkozik:

#!/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: beépített funkciók, mint pl set -o errexit, kilép a szkriptből, amint megjelenik egy „nyers” visszatérési kód (a nullától eltérő). Ezért jobb az egyéni hibakezelés bevezetése, 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

A szkriptek ilyen módon történő írása arra kényszeríti Önt, hogy óvatosabb legyen a szkriptben lévő összes parancs viselkedésével kapcsolatban, és előre jelezze a hiba lehetőségét, mielőtt az meglepné.

ShellCheck a fejlesztés közbeni hibák észleléséhez

Valami ilyesmit érdemes integrálni Shell Check a fejlesztési és tesztelési folyamatokba, hogy ellenőrizze a bash kódot a bevált gyakorlatokkal.

A helyi fejlesztői környezeteimben használom, hogy jelentéseket kapjak a szintaxisról, a szemantikáról és néhány olyan kódhibáról, amelyeket esetleg kihagytam a fejlesztés során. Ez egy statikus elemző eszköz a bash szkriptekhez, és nagyon ajánlom a használatát.

Saját kilépési kódok használatával

A POSIX visszatérési kódjai nem csak nullák vagy egyek, hanem nullák vagy nullától eltérő értékek. Ezekkel a szolgáltatásokkal egyéni hibakódokat küldhet vissza (201-254 között) különböző hibaesetekhez.

Ezt az információt ezután felhasználhatják más szkriptek, amelyek az Önét csomagolják, hogy pontosan megértsék, milyen típusú hiba történt, és ennek megfelelően reagáljon:

#!/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 megadott változónevekkel, hogy elkerülje a környezeti változók véletlen felülírását.

Naplózási funkciók

A szép és strukturált naplózás fontos a szkript eredményeinek könnyű megértéséhez. A többi magas szintű programozási nyelvhez hasonlóan a bash szkriptjeimben mindig natív naplózási függvényeket használok, mint pl. __msg_info, __msg_error és így tovább.

Ez segít szabványosított naplózási struktúrát biztosítani azáltal, hogy egyetlen helyen hajt végre változtatásokat:

#!/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 megpróbálok valamilyen mechanizmust beépíteni a szkriptjeimbe __init, ahol az ilyen naplózó változók és más rendszerváltozók inicializálva vagy alapértelmezett értékekre vannak állítva. Ezek a változók a parancssori opciókból is beállíthatók a szkripthívás során.

Például valami ilyesmit:

$ ./run-script.sh --debug

Egy ilyen szkript futtatásakor biztosítja, hogy a rendszerszintű beállítások alapértelmezett értékekre legyenek állítva, ha szükséges, vagy legalábbis inicializálva legyenek valami megfelelőre, ha szükséges.

Általában a felhasználói felület és a konfiguráció részletei közötti kompromisszumra alapozom, hogy mit kell inicializálni és mit nem.

Architektúra újrafelhasználáshoz és tiszta rendszerállapothoz

Moduláris/újrafelhasználható kód

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

Külön tárolót tartok, amivel inicializálhatok egy új projektet/bash szkriptet, amit fejleszteni akarok. Bármi, ami újra felhasználható, tárolható egy lerakatban, és lekérhető más projektek által, amelyek használni szeretnék ezt a funkciót. A projektek ilyen módon történő szervezése jelentősen csökkenti a többi szkript méretét, és azt is biztosítja, hogy a kódbázis kicsi és könnyen tesztelhető legyen.

A fenti példához hasonlóan minden naplózási funkció, mint pl __msg_info, __msg_error és mások, például a Slack-jelentések, külön találhatók common/* és dinamikusan csatlakozni más forgatókönyvekben, mint pl daily_database_operation.sh.

Hagyj magad mögött egy tiszta rendszert

Ha a szkript futása közben tölt be valamilyen erőforrást, akkor ajánlott minden ilyen adatot egy megosztott könyvtárban tárolni véletlenszerű névvel, pl. /tmp/AlRhYbD97/*. A könyvtárnév kiválasztásához véletlenszerű szöveggenerátorokat használhat:

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 fent tárgyalt horogkezelőkben lehet elvégezni. Ha az ideiglenes könyvtárakat nem kezelik, felhalmozódnak, és bizonyos szakaszokban váratlan problémákat okoznak a gazdagépen, például megtelt lemezt.

Zár fájlok használata

Gyakran meg kell győződnie arról, hogy egy adott időben csak egy szkriptpéldány fut egy gazdagépen. Ez megtehető zárfájlok segítségével.

Általában zárolási fájlokat készítek /tmp/project_name/*.lock és ellenőrizze a jelenlétüket a forgatókönyv elején. Ez segít a szkriptnek kecsesen befejezni, és elkerülni a rendszerállapotban a párhuzamosan futó másik szkript által okozott váratlan változtatásokat. Nincs szükség zárfájlokra, ha ugyanazt a szkriptet kell párhuzamosan végrehajtani egy adott gazdagépen.

Mérjen és javítson

Gyakran olyan szkriptekkel kell dolgoznunk, amelyek hosszú ideig futnak, például napi adatbázis-műveletekkel. Az ilyen műveletek általában több lépésből állnak: adatok betöltése, rendellenességek ellenőrzése, adatok importálása, állapotjelentések küldése stb.

Ilyen esetekben mindig igyekszem külön kis szkriptekre bontani a szkriptet, és ezek állapotát és végrehajtási idejét a következő használatával jelenteni:

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

Később láthatom a végrehajtási időt a következővel:

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

Ez segít azonosítani azokat a problémákat/lassú területeket a szkriptekben, amelyek optimalizálásra szorulnak.

Sok szerencsét!

Mit kell még olvasni:

  1. Go és GPU gyorsítótárak.
  2. Példa a Mail.ru Cloud Solutions S3 objektumtárolójában található webhookon alapuló eseményvezérelt alkalmazásra.
  3. Távirat csatornánk a digitális átalakulásról.

Forrás: will.com

Hozzászólás