GNU/Linux >> Linux Esercitazione >  >> Linux

Utilizzo di Bash per l'automazione

Data la recente serie di articoli che coprono gli aspetti fondamentali di Bash (elencati alla fine di questo articolo), è inevitabile che uno dei tuoi nuovi colleghi ne lanci uno nel cloud. Mentre queste cose vanno, i prossimi passi logici sono:

  1. La verifica di qualcosa di assolutamente critico dipende dal perfetto funzionamento dello script "cloud".
  2. Verifica che l'autore originale della sceneggiatura ha completamente dimenticato come funziona effettivamente.
  3. Verifica che l'amministratore più recente abbia il compito di modificarlo sostanzialmente senza alcuna convalida.

In questo articolo, assisto tutti gli amministratori e ti aiuto a evitare tutti gli errori di cui sopra. A sua volta, il risultato porta a una gestione più felice e, si spera, al nostro continuo impiego.

Come si scrive "Script Bash"

Per diventare illuminato (e per il tuo amore per $DEITY) controlla il tuo codice in uno strumento di gestione del codice sorgente (SCM). Anche mentre stai imparando, usa un repository locale come playground. Questa pratica non solo ti consente di tenere un diario dei tuoi sforzi nel tempo, ma ti consente anche di annullare facilmente gli errori. Ci sono molti articoli meravigliosi su come iniziare con git che consiglio vivamente.

Una breve nota sull'uso e l'apprendimento di Bash, perché questo linguaggio di scripting offre un insieme unico di -ismi e preferenze stilistiche dell'autore:non appena noti qualcosa che ti sembra nuovo (sintassi, stile o un costrutto linguistico), cercalo immediatamente . Dedica il tuo tempo alla comprensione del nuovo elemento dalla pagina man (prima scelta) o dalla Guida avanzata agli script di Bash (entrambi sono accessibili offline). In questo modo all'inizio ti rallenterai, ma nel tempo questa pratica ti aiuterà a sviluppare le tue conoscenze su dove trovare le risposte.

Scrittura di nuggets Bash riutilizzabili come librerie

Gli script nell'automazione sono scritti meglio abbracciando la filosofia Unix:molti piccoli strumenti che fanno solo una cosa. Ciò significa che farai molto meglio a scrivere piccoli script e librerie specializzate, rispetto a un gigantesco "lavello da cucina". Anche se, devo ammettere, ho scritto e mantenuto alcuni colossi (a volte servono a uno scopo).

Gli script di automazione spesso devono essere comprensibili e gestibili da più di un autore. Con molti piccoli script che volano in giro (e vengono monitorati nel controllo della versione) ti ritroverai rapidamente a dover condividere un riferimento a valori per nomi, versioni, percorsi o URL. Scrivere questi elementi comuni nelle librerie fornisce anche ulteriore spazio mentale ai manutentori per apprezzare la documentazione inline. Inoltre, così facendo, sfruttare gli unit test diventa quasi banale (ci occuperemo di questo argomento alla fine).

Pratichiamo una buona igiene del codice fin dall'inizio creando un repository locale di "riproduzione". All'interno del tuo nuovo repository, crea un luogo in cui contenere i nostri script e file di libreria. Mi piace attenermi agli standard FHS ben conosciuti per semplicità. Crea le directory ./bin/ e ./lib/ nella radice del repository. In un progetto di automazione più ampio, userei ancora questi nomi, ma potrebbero essere profondamente sepolti (ad esempio, sotto un scripts o tools sottodirectory).

Parlare di percorsi mi porta a un ottimo argomento per una prima libreria. Abbiamo bisogno di un modo per i nostri componenti futuri di fare riferimento a elementi strutturali e valori di alto livello. Usando il tuo editor preferito, crea il file ./lib/anchors.sh e aggiungi il contenuto di seguito:



# A Library of fundamental values
# Intended for use by other scripts, not to be executed directly.

# Set non-'false' by nearly every CI system in existence.
CI="${CI:-false}"  # true: _unlikely_ human-presence at the controls.
[[ $CI == "false" ]] || CI='true'  # Err on the side of automation

# Absolute realpath anchors for important directory tree roots.
LIB_PATH=$(realpath $(dirname "${BASH_SOURCE[0]}"))
REPO_PATH=$(realpath "$LIB_PATH/../")  # Specific to THIS repository
SCRIPT_PATH=$(realpath "$(dirname $0)")

Il file inizia con due righe vuote e il primo commento spiega il motivo. La libreria non essere impostato come eseguibile (nonostante il nome termini in .sh, indicando il suo tipo). Se la libreria è stata eseguita direttamente, è possibile che la shell dell'utente svanisca (o peggio). Disabilitazione dell'esecuzione diretta (chmod -x ./lib/anchors.sh ) è il primo livello di protezione dell'amministratore per principianti. Il commento all'inizio del file è il secondo livello.

Per convenzione, i commenti dovrebbero sempre descrivere il perché (non il cosa ) delle affermazioni che seguono. Un lettore può semplicemente leggere la dichiarazione per capire cosa fa, ma non può intuire in modo affidabile ciò che l'autore stava pensando in quel momento. Tuttavia, prima di approfondire, ho bisogno di dettagliare un problema che spesso coglie le persone alla sprovvista con Bash.

Le impostazioni predefinite di Bash forniscono una stringa vuota in riferimento a una variabile non definita. Il CI variabile (o qualcosa di analogo nella tua automazione) ha lo scopo di indicare la probabile assenza di un essere umano. Sfortunatamente, per i padroni dei robot, gli esseri umani dovranno probabilmente eseguire lo script manualmente almeno una volta. A questo punto, è probabile che dimentichino di impostare un valore per CI prima di premere Invio.

Quindi, dobbiamo impostare un valore predefinito per il valore e assicurarci che sia sempre vero o falso. Il codice della libreria di esempio sopra mostra sia come verificare se una stringa è vuota, sia come forzare la stringa a contenere uno di una coppia di valori. Il modo in cui leggo il primo gruppo di istruzioni in anchors.sh è:

Definisci 'CI ' andando avanti come risultato di:

  1. Esame del valore precedente di CI (potrebbe essere indefinito, quindi una stringa vuota).
  2. Il ':- ' parte significa:
    1. Se il valore era una stringa vuota, rappresenta la stringa 'false ' invece.
    2. Se il valore non era una stringa vuota, usa qualunque fosse (incluso 'DaRtH VaDeR ').

Prova la cosa all'interno di '[[ ' e ' ]] ':

  1. Se il nuovo valore di 'CI ' è uguale alla stringa letterale "false ", lancia il codice di uscita 0 (che significa successo o verità).
  2. Altrimenti, lancia il codice di uscita 1 (che significa fallimento o non verità)

Se il test è terminato con 0 , continua con la riga successiva, o (il ' || ' parte), presuppone che un amministratore principiante imposti CI=YES!PLEASE o un set di macchine perfette CI=true . Impostare immediatamente il valore di 'CI ' alla stringa letterale 'true '; perché le macchine perfette sono migliori, non sbagliare.

Per il resto di questa libreria, i valori del percorso di ancoraggio sono quasi sempre utili negli script eseguiti nell'automazione da un repository. Se utilizzato in un progetto più ampio, è necessario regolare la posizione relativa della directory della libreria rispetto alla radice del repository. Altrimenti lascerò la comprensione di queste affermazioni, come esercizio di ricerca per il lettore (fallo ora, ti aiuterà in seguito).

Utilizzo delle librerie Bash negli script Bash

Per caricare una libreria, usa il source comando integrato. Questo comando non è elegante. Assegnagli la posizione di un file, quindi legge ed esegue il contenuto proprio lì e poi, il che significa che il codice della libreria contesto di runtime sarà effettivamente lo script che source d esso.

Per evitare che troppo cervello goccioli dalle orecchie, ecco un semplice ./bin/example.sh script per illustrare:

#!/bin/bash

LIB_PATH="$PWD/$(dirname $0)/../lib/anchors.sh"
echo "Before loading: $LIB_PATH"
set -ax
cd /var/tmp
source $LIB_PATH
echo "After loading: $(export -p | grep ' LIB_PATH=')"

Potresti notare immediatamente che lo script ha alterato il contesto di runtime prima di caricare la libreria. Definisce anche LIB_PATH localmente e lo punta a un file (in modo confuso, invece di una directory) con un percorso relativo (a scopo illustrativo).

Vai avanti ed esegui questo script ed esamina l'output. Nota che tutte le operazioni nella libreria anchors.sh eseguito all'interno di /var/tmp/ directory ed ha esportato automaticamente le sue definizioni. La vecchia definizione per LIB_PATH è stato picchiato ed esportato da a nel set -ax . Questo fatto è visibile nell'output di declare -x proveniente dall'export comando. Si spera che l'output del debug (il x nel set -ax ) è comprensibile.

Quando si esegue il debug in questo modo, Bash stampa tutti i valori intermedi durante l'analisi di ogni riga. Ho incluso questo script qui per mostrare perché non vorresti mai set -ax o cambiare directory usando i comandi dal livello superiore di una libreria. Ricorda che le istruzioni della libreria vengono valutate al momento del caricamento nello script. Pertanto, la modifica dell'ambiente in una libreria provoca effetti collaterali di runtime in qualsiasi script utilizzato source per caricarlo. Effetti collaterali come questo sono garantiti per far impazzire almeno un amministratore di sistema. Non si sa mai, quell'amministratore potrei essere io, quindi non farlo.

Come esempio pratico, si consideri una libreria immaginaria che definisce una funzione utilizzando una variabile di ambiente nome utente/password per accedere a un servizio remoto. Se la libreria ha eseguito un set -ax di livello superiore prima di questa funzione, quindi ogni volta che viene caricata l'output di debug includerà la visualizzazione di queste variabili, splattering i tuoi segreti dappertutto affinché tutti possano vederli. Peggio ancora, sarà difficile (dal punto di vista di uno script chiamante), per un collega principiante disabilitare l'output senza urlare alla tastiera.

In conclusione, la chiave qui è rimanere consapevoli del fatto che le biblioteche "accadono" nel contesto del chiamante. Questo fattore è anche il motivo per cui l'esempio anchors.sh può utilizzare  $0   (il percorso dello script eseguibile e il nome del file), ma il percorso della libreria stessa è disponibile solo tramite il "magic" '${BASH_SOURCE[0]}' (elemento matrice). Questo fattore potrebbe creare confusione all'inizio, ma dovresti cercare di rimanere disciplinato. Evita comandi ampi e di vasta portata nelle librerie. Quando lo farai, tutti i nuovi assunti amministratori insisteranno per pagare le tue ciambelle.

Scrittura di unit test per le biblioteche

Scrivere unit test può sembrare un compito arduo finché non ti rendi conto che una copertura perfetta di solito è una perdita di tempo. Tuttavia, è una buona abitudine utilizzare e aggiornare sempre il codice di test quando si tocca il codice della libreria. L'obiettivo della scrittura del test è occuparsi dei casi d'uso più comuni e ovvi e poi andare avanti. Non prestare molta attenzione alle custodie angolari o agli usi meno frequenti. Suggerisco inoltre di concentrare inizialmente i tuoi sforzi di test della libreria a livello di test unitario anziché test di integrazione.

Diamo un'occhiata a un altro esempio:lo script eseguibile ./lib/test-anchors.sh :

#!/bin/bash

# Unit-tests for library script in the current directory
# Also verifies test script is derived from library filename

TEST_FILENAME=$(basename $0)  # prefix-replace needs this in a variable
SUBJ_FILENAME="${TEST_FILENAME#test-}"; unset TEST_FILENAME
TEST_DIR=$(dirname $0)/

ANY_FAILED=0

# Print text after executing command, set ANY_FAILED non-zero on failure
# usage: test_cmd "description" <command> [arg...]

test_cmd() {
   local text="${1:-no test text given}"
   shift
   if ! "$@"; then
      echo "fail - $text"; ANY_FAILED=1;
   else
      echo "pass - $text"
   fi
}

test_paths() {
   source $TEST_DIR/$SUBJ_FILENAME
   test_cmd "Library $SUBJ_FILENAME is not executable" \
      test ! -x "$SCRIPT_PATH/$SUBJ_FILENAME"
   test_cmd "Unit-test and library in same directory" \
      test "$LIB_PATH" == "$SCRIPT_PATH"
   for path_var in LIB_PATH REPO_PATH SCRIPT_PATH; do
      test_cmd "\$$path_var is defined and non-empty: ${!path_var}" \
         test -n "${!path_var}"
      test_cmd "\$$path_var referrs to existing directory" \
         test -d "${!path_var}"
   done
}

# CI must only/always be either 'true' or 'false'.
# Usage: test_ci <initial value> <expected value>

test_ci() {
   local prev_CI="$CI"  # original value restored at the end
   CI="$1"
   source $TEST_DIR/$SUBJ_FILENAME
   test_cmd "Library $SUBJ_FILENAME loaded from $TEST_DIR" \
      test "$?" -eq 0
   test_cmd "\$CI='$1' becomes 'true' or 'false'" \
      test "$CI" = "true" -o "$CI" = "false"
   test_cmd "\$CI value '$2' was expected" \
      test "$CI" = "$2"
   CI="$prev_CI"
}

test_paths
test_ci "" "false"
test_ci "$RANDOM" "true"
test_ci "FoObAr" "true"
test_ci "false" "false"
test_ci "true" "true"

# Always run all tests and report, exit non-zero if any failed

test_cmd "All tests passed" \
   test "$ANY_FAILED" -eq 0
[[ "$CI" == "false" ]] || exit $ANY_FAILED  # useful to automation
exit(0)

Il motivo per cui ho inserito questo script in ./lib (al contrario di ./bin ) è sia per comodità sia perché i test non devono mai basarsi sull'utilizzo del codice che stanno controllando. Poiché questo test deve verificare i percorsi, è più facile inserirlo nello stesso percorso della libreria. Altrimenti, questo approccio è una questione di preferenza personale. Sentiti libero di eseguire il test ora perché potrebbe aiutarti a comprendere il codice.

Conclusione

Questo articolo non rappresenta in alcun modo l'intero utilizzo di Bash nell'automazione. Tuttavia, ho cercato di instillare le conoscenze di base e i consigli che (se seguiti) ti semplificheranno senza dubbio la vita. Quindi, anche quando le cose diventano difficili, sarà utile una solida comprensione dell'importanza del contesto di runtime.

Infine, lo scripting in o per l'automazione può non perdonare gli errori. Avere anche test unitari di base in atto per le tue biblioteche rafforzerà la fiducia e aiuterà la prossima persona a venire (che potresti essere tu dopo cinque anni di dimenticanze). Puoi trovare tutto il codice di esempio utilizzato in questo articolo online qui.

Ti interessa rispolverare i tuoi fondamenti di Bash? Dai un'occhiata:

  • Come programmare con Bash:operatori logici ed espansioni della shell
  • 5 modi per migliorare i tuoi script Bash
  • Iniziare con gli script di shell
  • 10 comandi Linux di base che devi conoscere
  • Suggerimenti e trucchi per le variabili di ambiente Linux

[ Vuoi provare Red Hat Enterprise Linux? Scaricalo ora gratuitamente. ]


Linux
  1. Gestione degli errori negli script Bash

  2. Utilizzo dello strumento SS per la risoluzione dei problemi di rete

  3. Utilizzare l'estensione .sh o .bash per gli script Bash?

  4. Utilizzo del comando Linux Basename negli script Bash

  5. Script bash:utilizzo del comando script da uno script bash per la registrazione di una sessione

Suggerimenti per l'utilizzo di tmux

Suggerimenti per l'utilizzo dello schermo

Script di shell per principianti - Come scrivere script Bash in Linux

Usare "${a:-b}" per l'assegnazione di variabili negli script?

Utilizzo del comando Linux Dirname negli script Bash

Utilizzo del comando Bash printf per la stampa di output formattati