Bash-skripti parim praktika: lĂŒhike juhend usaldusvÀÀrsete ja efektiivsete bash-skriptide jaoks

Bash-skripti parim praktika: lĂŒhike juhend usaldusvÀÀrsete ja efektiivsete bash-skriptide jaoks
Shelli taustafoto autor manapi

Bash-skriptide tĂ”rkeotsing on nagu nĂ”ela otsimine heinas, eriti kui olemasolevas koodibaasis ilmuvad uued lisandused, ilma et struktuuri, logimise ja usaldusvÀÀrsuse kĂŒsimusi Ă”igel ajal arutletaks. Sellistes olukordades vĂ”ib sattuda probleemidesse nii oma vigade tĂ”ttu kui ka keeruliste skriptikogumite haldamisel.

Meeskond Mail.ru Cloud Solutions tÔlkis artikli soovitustega, mis vÔimaldavad teil oma skripte paremini kirjutada, tÔrkeotsida ja hallata. Usuge vÔi mitte, aga miski ei saa vÔrreldagi rahuloluga, mida toob puhta, kasutamiseks valmis bash-koodi kirjutamine, mis töötab iga kord.

Artiklis jagab autor oma viimaste aastate jooksul omandatud teadmisi ja mĂ”ningaid levinud vigu, mis on teda ootamatult kĂ€tte saanud. See on oluline, sest iga tarkvaraarendaja töötab oma karjÀÀri teatud etapis skriptidega rutiinsete tĂ¶Ă¶ĂŒlesannete automatiseerimiseks.

PĂŒĂŒgihaldurid

Enamikest bash-skripti, millega olen kokku puutunud, ei ole kunagi kasutatud tÔhusat puhastusmehhanismi, kui skripti kÀivitamise ajal tekib midagi ootamatut.

Ootamatuseid vĂ”ib tekkida vĂ€ljastpoolt, nĂ€iteks tuumalt signaali saamise tĂ”ttu. Selliste olukordade kĂ€sitlemine on ÀÀrmiselt oluline, et skriptid oleksid piisavalt usaldusvÀÀrsed tootmisse sĂŒsteemides. Kasutan sageli vĂ€ljumisprotseduure, et reageerida sellistes olukordades:

function handle_exit() {
  	// Lisa siia puhastuskood
  	// nÀiteks rm -f "/tmp/${lock_file}.lock"
  	// vÀlju sobiva olekukoodiga
}
  
// trap  
trap handle_exit 0 SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM

trap — on sisseehitatud kĂ€sk shellis, mis aitab sul registreerida puhastusfunktsiooni, mis kutsutakse esile mistahes signaalide korral. Kuid tuleb olla ettevaatlik selliste kĂ€itlejate puhul nagu SIGINT, mis pĂ”hjustab skripti katkestamist.

Lisaks tuleks enamikul juhtudel pĂŒĂŒda ainult EXIT, kuid idee on see, et sa saad tĂ”eliselt kohandada skripti kĂ€itumist iga konkreetse signaali jaoks.

Sisseehitatud funktsioonid set — kiire lĂ”petamine vea korral

On oluline reageerida vigadele kohe, kui need ilmnevad, ja kiiresti lÔpetada töö. Pole midagi hullemat kui jÀtkata sellise kÀsu tÀitmist:

rm -rf ${directory_name}/*

Pange tÀhele, et muutuja directory_name ei ole mÀÀratletud.

Selliste stsenaariumide kÀitlemiseks on oluline kasutada sisseehitatud funktsioone set, nagu set -o errexit, set -o pipefail vÔi set -o nounset skripti alguses. Need funktsioonid tagavad, et teie skript lÔpetab tÀitmise kohe, kui ta kohtab mis tahes mitte-null lÔpetamiskoodi, mÀÀramata muutujaid, valeid kÀske, mis on edastatud toru kaudu jne.

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

MĂ€rkus: sisseehitatud funktsioonid, nagu set -o errexit, lĂ”petavad skripti tĂ€itmise, kui ilmneb „töötlemata” tagastuskoode (vĂ€lja arvatud null). SeetĂ”ttu on parem luua kohandatud veahaldus, nĂ€iteks:

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

Selline skriptide kirjutamine kutsub teid ĂŒles paremini tĂ€helepanu pöörama igasuguste kĂ€skude kĂ€itumisele skriptis ja arvestama vĂ”imalike vigade tekkimisega enne, kui need teid ĂŒllatavad.

ShellCheck vigade tuvastamiseks arendamise kÀigus

Tasub integreerida midagi sellist nagu ShellCheck teie arendus- ja testimisprotsessidesse, et kontrollida oma bash-koodi parimate praktikate jÀrgimist.

Ma kasutan seda oma kohalikes arenduskeskkondades, et saada aruandeid sĂŒntaksist, semantikast ja mĂ”ningatest vigadest koodis, mille ma arendamise kĂ€igus olen vĂ”inud tĂ€helepanuta jĂ€tta. See on statilise analĂŒĂŒsi tööriist teie bash-skriptide jaoks, mida ma soovitan tungivalt kasutada.

Oma exit-koodide kasutamine

POSIX-i tagastuskoodid ei ole lihtsalt null vĂ”i ĂŒks, vaid null vĂ”i mitte-null vÀÀrtus. Kasutage neid vĂ”imalusi kasutajate vigade koode (vahemikus 201-254) erinevate viga olukordade jaoks.

Seda teavet saab seejĂ€rel kasutada teiste skriptide poolt, mis ĂŒmbritsevad teie skripti, et tĂ€pselt mĂ”ista, millist tĂŒĂŒpi viga on juhtunud, ja reageerida vastavalt:

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

MĂ€rkus: palun olge eriti ettevaatlikud muutuja nimedega, mida te mÀÀratlete, et vĂ€ltida keskkonnaparameetrite juhuslikku ĂŒlekirjutamist.

Logifunktsioonid

Ilus ja struktureeritud logimine on oluline, et mÔista teie skripti tÀitmise tulemusi. Nagu teiste kÔrgema taseme programmeerimiskeelte puhul, kasutan ma alati oma bash-skripte kirjutades isiklikke logimisfunktsioone, nÀiteks __msg_info, __msg_error ja nii edasi.

See aitab tagada standardiseeritud logistruktuuri, muutes vÀÀrtusi ainult ĂŒhes kohas:

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

PĂŒĂŒan tavaliselt oma skripte kirjutades kasutada mingisugust mehhanismi __init, kus sellised loggeri muutujad ja muud sĂŒsteemimuutujad initsialiseeritakse vĂ”i seatakse vaikimisi vÀÀrtustesse. Need muutujad vĂ”ivad samuti saada vÀÀrtusi kĂ€surea parameetritest skripti kĂ€ivitamise ajal.

NĂ€iteks midagi sellist:

$ ./run-script.sh --debug

Kui sellist skripti kĂ€itatakse, on tagatud, et sĂŒsteemi ĂŒldised seaded on seatud vaikimisi vÀÀrtustesse, kui need on kohustuslikud, vĂ”i vĂ€hemalt initsialiseeritud millegagi sobivaga, kui see on vajalik.

Olen tavaliselt valikute tegemisel, mida initsialiseerida ja mida mitte, kompromissi kasutajaliidese ja konfiguratsiooni detailide vahel, millesse kasutaja peab sĂŒvenema.

Taaskasutatava ja puhta sĂŒsteemi arhitektuur

Modulaarne / korduvkasutatav kood

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

Hoian eraldi repositooriumi, mida saab kasutada uue projekti / bash skripti initsialiseerimiseks, mida soovin arendada. KÔik, mida saab taaskasutada, saab salvestada repositooriumisse ja kasutada teistes projektides, mis soovivad selliseid funktsioone kasutada. Selline projektide korraldamine vÀhendab oluliselt teiste skriptide suurust ning tagab, et koodibaas on vÀike ja lihtsalt testitav.

Nagu ĂŒlaltoodud nĂ€ites, sisaldavad kĂ”ik logimisfunktsioonid, nagu __msg_info, __msg_error ja muud, nĂ€iteks Slacki raportid, on eraldi common/* ja laaditakse dĂŒnaamiliselt teistes stsenaariumites, nagu daily_database_operation.sh.

JĂ€tke pĂ€rast end puhta sĂŒsteemiga

Kui laadite skripti tĂ€itmise ajal mĂ”ned ressursid ĂŒles, on soovitatav hoida kĂ”iki selliseid andmeid ĂŒhises kataloogis, millel on juhuslik nimi, nĂ€iteks /tmp/AlRhYbD97/*. Saate kasutada juhusliku teksti genereerijaid katalooge nime valimiseks:

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

Töö lĂ”petamisel vĂ”ib selliste kataloogide puhastamine olla tagatud ĂŒlaltoodud lĂ”ksude kĂ€itlejates. Kui ajutiste kataloogide hooldamine unustatakse, vĂ”ivad need koguneda ja teatud hetkel pĂ”hjustada ootamatuid probleeme hostis, nĂ€iteks tĂ€is ketas.

Lukufailide kasutamine

Tihti on vajalik tagada, et hostis töötaks korraga ainult ĂŒks skripti eksemplar. Seda saab teha lukufailide abil.

Ma tavaliselt loon lukufailid /tmp/project_name/*.lock ja kontrollin nende olemasolu skripti alguses. See aitab skripti Ă”igesti lĂ”petada ja vĂ€ltida sĂŒsteemi oleku ootamatuid muutusi teise paralleelselt töötava skripti poolt. Lukufailid pole vajalikud, kui peate kindlustama, et sama skript kĂ€ivituks paralleelselt antud hostis.

MÔÔtke ja parandage

Me peame sageli töötama stsenaariumitega, mis kestavad pikema aja, nÀiteks igapÀevased andmebaasi toimingud. Need toimingud hÔlmavad tavaliselt jÀrgmisi samme: andmete laadimine, anomaaliate kontrollimine, andmete importimine, olekuteate saatmine ja nii edasi.

Sellistel juhtudel pĂŒĂŒan alati stsenaariumi jagada vĂ€ikesteks skriptideks ja edastada nende olekut ning tĂ€itmise aega jĂ€rgmiste abil:

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

Hiljem saan ma vaatada tÀitmise aega jÀrgmise abil:

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

See aitab mul vÀlja selgitada probleemsed/aeglased kohad skriptides, mis vajavad optimeerimist.

Edu!

Mida veel lugeda:

  1. Go ja GPU vahemÀlu.
  2. NĂ€ide sĂŒndmustest juhitud rakendusest, mis pĂ”hineb veebikonksudel objekti S3 ladustamises Mail.ru Pilveteenustes.
  3. Meie telegrammi kanal digitaalse transformatsiooni kohta.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster