Plej bonaj Praktikoj pri Bash Scripting: Rapida Gvidilo al Fidindaj kaj Efikecaj Bash-Skriptoj

Plej bonaj Praktikoj pri Bash Scripting: Rapida Gvidilo al Fidindaj kaj Efikecaj Bash-Skriptoj
Ŝelo-tapeto de manapi

Sencimigi bash-skriptojn estas kiel serĉi kudrilon en fojnamaso, precipe kiam novaj aldonoj aperas en la ekzistanta kodbazo sen ĝustatempa konsidero de aferoj de strukturo, arbodehakado kaj fidindeco. Vi povas trovi vin en tiaj situacioj aŭ pro viaj propraj eraroj aŭ dum administrado de kompleksaj amasoj da skriptoj.

teamo Mail.ru Cloud Solutions tradukis artikolon kun rekomendoj, kiuj helpos vin skribi, sencimigi kaj pli bone konservi viajn skriptojn. Kredu aŭ ne, nenio superas la kontentigon verki puran, uzeblan bash-kodon, kiu funkcias ĉiufoje.

En la artikolo, la aŭtoro dividas tion, kion li lernis dum la lastaj jaroj, kaj ankaŭ kelkajn oftajn erarojn, kiuj kaptis lin senĝene. Ĉi tio estas grava ĉar ĉiu programisto, iam en sia kariero, laboras per skriptoj por aŭtomatigi rutinajn labortaskojn.

Kaptiloj

Plej multaj bash-skriptoj, kiujn mi renkontis, neniam uzas efikan purigan mekanismon kiam io neatendita okazas dum skripto-ekzekuto.

Surprizoj povas ekesti de ekstere, kiel ricevi signalon de la kerno. Pritrakti tiajn kazojn estas ege grava por certigi, ke la skriptoj estas sufiĉe fidindaj por funkcii per produktadsistemoj. Mi ofte uzas elirajn traktilojn por respondi al scenaroj kiel ĉi tio:

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 estas ŝelo enkonstruita komando kiu helpas vin registri purigan funkcion kiu estas vokita en kazo de iuj signaloj. Tamen, speciala zorgo devas esti prenita kun pritraktantoj kiel ekz SIGINT, kiu igas la skripton ĉesi.

Krome, en la plej multaj kazoj vi devus nur kapti EXIT, sed la ideo estas, ke vi efektive povas personecigi la konduton de la skripto por ĉiu individua signalo.

Enkonstruitaj aro-funkcioj - rapida ĉesigo pro eraro

Estas tre grave respondi al eraroj tuj kiam ili okazas kaj ĉesigi la ekzekuton rapide. Nenio povus esti pli malbona ol daŭrigi ruli komandon kiel ĉi tio:

rm -rf ${directory_name}/*

Bonvolu noti, ke la variablo directory_name ne determinita.

Gravas uzi enkonstruitajn funkciojn por trakti tiajn scenarojn set, kiel set -o errexit, set -o pipefailset -o nounset komence de la skripto. Ĉi tiuj funkcioj certigas, ke via skripto eliros tuj kiam ĝi renkontos iun ajn ne-nulan elirkodon, uzon de nedifinitaj variabloj, nevalidajn komandojn pasigitajn super tubo, ktp:

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

Notu: enkonstruitaj funkcioj kiel ekz set -o errexit, eliros la skripton tuj kiam estos "kruda" revenkodo (krom nulo). Tial estas pli bone enkonduki laŭmendan erartraktadon, ekzemple:

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

Verki skriptojn tiamaniere devigas vin esti pli singarda pri la konduto de ĉiuj komandoj en la skripto kaj antaŭvidi la eblecon de eraro antaŭ ol ĝi surprizas vin.

ShellCheck por detekti erarojn dum disvolviĝo

Indas integri ion similan ShellCheck en viajn disvolvajn kaj testajn duktojn por kontroli vian bash-kodon kontraŭ plej bonaj praktikoj.

Mi uzas ĝin en miaj lokaj evoluaj medioj por ricevi raportojn pri sintakso, semantiko kaj iuj eraroj en la kodo, kiujn mi eble maltrafis dum evoluado. Ĉi tio estas senmova analiza ilo por viaj bash-skriptoj kaj mi tre rekomendas uzi ĝin.

Uzante viajn proprajn elirajn kodojn

Revenkodoj en POSIX estas ne nur nul aŭ unu, sed nul aŭ ne-nula valoro. Uzu ĉi tiujn funkciojn por redoni kutimajn erarkodojn (inter 201-254) por diversaj erarkazoj.

Ĉi tiuj informoj tiam povas esti uzataj de aliaj skriptoj, kiuj envolvas vian por kompreni precize kian eraron okazis kaj reagi laŭe:

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

Notu: bonvolu aparte zorgi pri la variablonomoj, kiujn vi difinas, por eviti hazarde superregi mediovariablojn.

Funkcioj de registrado

Bela kaj strukturita enhavado estas grava por facile kompreni la rezultojn de via skripto. Kiel ĉe aliaj altnivelaj programlingvoj, mi ĉiam uzas denaskajn registradajn funkciojn en miaj bash-skriptoj, kiel ekzemple __msg_info, __msg_error kaj tiel plu.

Ĉi tio helpas provizi normigitan registradan strukturon farante ŝanĝojn en nur unu loko:

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

Mi kutime provas havi ian mekanismon en miaj skriptoj __init, kie tiaj registraj variabloj kaj aliaj sistemvariabloj estas pravaligitaj aŭ agordita al defaŭltaj valoroj. Ĉi tiuj variabloj ankaŭ povas esti agorditaj de komandliniaj opcioj dum skriba alvoko.

Ekzemple, io kiel:

$ ./run-script.sh --debug

Kiam tia skripto estas ekzekutita, ĝi certigas, ke tutsistemaj agordoj estas fiksitaj al defaŭltaj valoroj se ili estas postulataj, aŭ almenaŭ pravigitaj al io taŭga se necese.

Mi kutime bazas la elekton pri kio pravalorigi kaj kion ne fari sur kompromiso inter la uzantinterfaco kaj la detaloj de la agordoj, kiujn la uzanto povas/devu enprofundiĝi.

Arkitekturo por reuzo kaj pura sistema stato

Modula/reuzebla kodo

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

Mi konservas apartan deponejon, kiun mi povas uzi por pravalorigi novan projekton/bash-skripton, kiun mi volas evoluigi. Ĉio, kio povas esti reuzata, povas esti stokita en deponejo kaj prenita de aliaj projektoj, kiuj volas uzi tiun funkcion. Organizi projektojn tiel signife reduktas la grandecon de aliaj skriptoj kaj ankaŭ certigas ke la kodbazo estas malgranda kaj facile provi.

Kiel en la supra ekzemplo, ĉiuj registradaj funkcioj kiel ekz __msg_info, __msg_error kaj aliaj, kiel Slack-raportoj, estas enhavitaj aparte en common/* kaj dinamike konekti en aliaj scenaroj kiel daily_database_operation.sh.

Forlasu puran sistemon

Se vi ŝargas iujn ajn rimedojn dum la skripto funkcias, oni rekomendas konservi ĉiujn tiajn datumojn en komuna dosierujo kun hazarda nomo, ekz. /tmp/AlRhYbD97/*. Vi povas uzi hazardajn tekstajn generilojn por elekti la dosierujon:

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

Post fino de laboro, purigado de tiaj dosierujoj povas esti disponigita en la hoktraktiloj diskutitaj supre. Se provizoraj dosierujoj ne estas prizorgataj, ili amasiĝas kaj en iu stadio kaŭzas neatenditajn problemojn sur la gastiganto, kiel plena disko.

Uzante ŝlosildosierojn

Ofte vi devas certigi, ke nur unu okazo de skripto funkcias en gastiganto en iu ajn momento. Ĉi tio povas esti farita per ŝlosi dosieroj.

Mi kutime kreas ŝlosajn dosierojn en /tmp/project_name/*.lock kaj kontrolu ilian ĉeeston komence de la skripto. Ĉi tio helpas la skripton finiĝi gracie kaj eviti neatenditajn ŝanĝojn al la sistema stato per alia skripto funkcianta paralele. Ŝlosaj dosieroj ne estas bezonataj se vi bezonas la saman skripton por esti ekzekutita paralele sur difinita gastiganto.

Mezuri kaj plibonigi

Ni ofte bezonas labori kun skriptoj kiuj funkcias dum longaj tempodaŭroj, kiel ĉiutagaj datumbazaj operacioj. Tiaj operacioj tipe implikas sinsekvon de paŝoj: ŝargado de datumoj, kontrolado de anomalioj, importado de datumoj, sendado de statusraportoj, ktp.

En tiaj kazoj, mi ĉiam provas rompi la skripton en apartajn malgrandajn skriptojn kaj raporti ilian staton kaj ekzekuttempon uzante:

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

Poste mi povas vidi la ekzekuttempon kun:

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

Ĉi tio helpas min identigi problemojn/malrapidajn areojn en skriptoj, kiuj bezonas optimumigon.

Bonŝancon!

Kion alian legi:

  1. Iru kaj GPU-kaŝmemoroj.
  2. Ekzemplo de evento-movita aplikaĵo bazita sur rethokoj en la S3-objekta stokado de Mail.ru Cloud Solutions.
  3. Nia telegrama kanalo pri cifereca transformo.

fonto: www.habr.com

Aldoni komenton