GNU/Linux >> Linux Esercitazione >  >> Linux

Scopri la gestione degli errori di Bash con l'esempio

In questo articolo, presento alcuni trucchi per gestire le condizioni di errore:alcuni rigorosamente non rientrano nella categoria della gestione degli errori (un modo reattivo per gestire gli imprevisti), ma anche alcune tecniche per evitare errori prima che si verifichino.

Caso di studio:semplice script che scarica un report hardware da più host e lo inserisce in un database.

Supponi di avere un cron lavoro su ciascuno dei tuoi sistemi Linux e hai uno script per raccogliere le informazioni hardware da ciascuno:

#!/bin/bash# Script per raccogliere lo stato dell'output lshw dai server domestici# Dipendenze:# * LSHW:http://ezix.org/project/wiki/HardwareLiSter# * JQ:http://stedolan.github.io/jq/## Su ogni macchina puoi eseguire qualcosa del genere da cron (non conosco CRON, nessun problema:https://crontab-generator.org/)# 0 0 * * * /usr/sbin/lshw -json -quiet> /var/log/lshw-dump.json# Autore:Jose Vicente Nunez#declare -a server=(dmaf5)DATADIR="$HOME/Documents/lshw-dump"/usr /bin/mkdir -p -v "$DATADIR"per il server in ${server[*]}; do    echo "Visitare:$server"    /usr/bin/scp -o logLevel=Errore ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &donewaitfor lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json'); do    /usr/bin/jq '.["product","vendor", "configuration"]' $lshwdone 

Se tutto va bene, raccogli i tuoi file in parallelo perché non hai più di dieci sistemi. Puoi permetterti di inviare ssh a tutti contemporaneamente e quindi mostrare i dettagli hardware di ciascuno.

  Visita:dmaf5lshw dump.json 100% 54kb 136.9MB/s 00:00 "dmaf5 (stringa predefinita)" "Besstar Tech Limited" {"Boot":"Normale", "Chassis":"Desktop" ",  "family":"Stringa predefinita",  "sku":"Stringa predefinita",  "uuid":"00020003-0004-0005-0006-000700080009"} 

Ecco alcune possibilità del motivo per cui le cose sono andate storte:

  • Il tuo rapporto non è stato eseguito perché il server era inattivo
  • Non è stato possibile creare la directory in cui devono essere salvati i file
  • Mancano gli strumenti necessari per eseguire lo script
  • Non puoi raccogliere il rapporto perché il tuo computer remoto si è bloccato
  • Uno o più rapporti sono danneggiati

La versione corrente dello script ha un problema:verrà eseguita dall'inizio alla fine, errori o meno:

; :Nessun file o directoryscp di questo tipo:/var/log/lshw-dump.json:Nessun errore di analisi di file o directory di questo tipo:separatore previsto tra i valori alla riga 3, colonna 9

Successivamente, dimostro alcune cose per rendere il tuo script più robusto e in alcuni momenti ripristinarlo da un errore.

L'opzione nucleare:fallire duramente, fallire velocemente

Il modo corretto per gestire gli errori è controllare se il programma è terminato correttamente o meno, utilizzando i codici di ritorno. Sembra ovvio ma restituisce codici, un numero intero memorizzato in bash $? o $! variabile, a volte hanno un significato più ampio. La pagina man di bash ti dice:

Ai fini della shell, un comando che esce con uno stato di uscita
zero è riuscito. Uno stato di uscita pari a zero indica successo.
Uno stato di uscita diverso da zero indica un errore. Quando un comando
termina su un segnale fatale N, bash utilizza il valore di 128+N come
stato di uscita.

Come al solito, dovresti sempre leggere la pagina man degli script che stai chiamando, per vedere quali sono le convenzioni per ciascuno di essi. Se hai programmato con un linguaggio come Java o Python, molto probabilmente conosci le loro eccezioni, i diversi significati e come non tutti vengono gestiti allo stesso modo.

Se aggiungi imposta -o errexit al tuo script, da quel momento in poi interromperà l'esecuzione se esiste un comando con un codice !=0 . Ma errexit non viene utilizzato quando si eseguono funzioni all'interno di un if condizione, quindi invece di ricordare quell'eccezione, mi occupo di gestione esplicita degli errori.

Dai un'occhiata alla versione due dello script. È leggermente meglio:

1 #!/bin/bash2 # Script per raccogliere lo stato dell'output lshw dai server domestici3 # Dipendenze:4 # * LSHW:http://ezix.org/project/wiki/HardwareLiSter5 # * JQ:http://stedolan.github.io/jq/6 #7 # Su ogni macchina puoi eseguire qualcosa del genere da cron (non conosco CRON, nessun problema:https://crontab-generator.org/ ) 8 # 0 0 * * * /usr/sbin/lshw -json -quiet> /var/log/lshw-dump.json9 Autore:Jose Vicente Nunez10 #11 set -o errtrace # Abilita la trap err, il codice verrà chiamato in caso di errore è stato rilevato12 trap "echo ERROR:si è verificato un errore in ${FUNCNAME-main context}, dettagli da seguire" ERR13 dichiara -a server=(14 macmini215 mac-pro-1-116 dmaf517 )18 19 DATADIR="$HOME/ Documents/lshw-dump"20 se [ ! -d "$DATADIR"]; quindi 21   /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL:Impossibile creare $DATADIR" &&exit 10022 fi 23 dichiarare -A server_pid24 per il server in ${servers[*]}; do25   echo "Visitare:$server"26   /usr/bin/scp -o logLevel=Errore ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json &27   server_pid [$server]=$! # Salva il PID di scp  di un determinato server per dopo28 done29 # Scorri tutti i server e:30 # Attendi il codice di ritorno di ciascuno31 # Controlla il codice di uscita da ogni scp32 per il server in ${!server_pid[*]}; do33   attendi ${server_pid[$server]}34   test $? -ne 0 &&echo "ERRORE:la copia dal server $ ha avuto problemi, non continuerà" &&exit 10035 done36 per lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump.json' ); do37   /usr/bin/jq '.["product","vendor", "configuration"]' $lshw38 fatto 

Ecco cosa è cambiato:

  • Righe 11 e 12, abilito la traccia degli errori e ho aggiunto una "trappola" per informare l'utente che si è verificato un errore e che sono presenti turbolenze. Potresti invece voler eliminare il tuo script qui, ti mostrerò perché potrebbe non essere il migliore.
  • Riga 20, se la directory non esiste, prova a crearla alla riga 21. Se la creazione della directory non riesce, esci con un errore.
  • Alla riga 27, dopo aver eseguito ogni lavoro in background, acquisisco il PID e lo associo alla macchina (relazione 1:1).
  • Sulle righe 33-35, aspetto il scp completare l'attività, ottenere il codice di ritorno e, se si tratta di un errore, interrompere.
  • Alla riga 37, controllo che il file possa essere analizzato, altrimenti esco con un errore.

Quindi, come appare ora la gestione degli errori?

  Visita:Macmini2Visiting:Mac-Pro-1-1-Visiting:dmaf5lshw-dump.json 100% 54kb 146.1MB/s 00:00 SCP:/var/log/lshw-dump.json:nessun file tale o directoryERROR:si è verificato un errore nel contesto principale, dettagli da seguireERRORE:la copia da mac-pro-1-1 ha avuto problemi, non continueràcp:/var/log/lshw-dump.json:nessun file o directory di questo tipo 

Come puoi vedere, questa versione è migliore nel rilevare gli errori ma è molto spietata. Inoltre, non rileva tutti gli errori, vero?

Quando rimani bloccato e vorresti avere un allarme

Il codice ha un aspetto migliore, tranne per il fatto che a volte il scp potrebbe rimanere bloccato su un server (durante il tentativo di copiare un file) perché il server è troppo occupato per rispondere o semplicemente in cattivo stato.

Un altro esempio consiste nel provare ad accedere a una directory tramite NFS dove $HOME è montato da un server NFS:

/usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt 

E scopri ore dopo che il punto di montaggio NFS non è aggiornato e il tuo script è bloccato.

Un timeout è la soluzione. E il timeout GNU viene in soccorso:

/usr/bin/timeout --kill-after 20.0s 10.0s /usr/bin/find $HOME -type f -name '*.csv' -print -fprint /tmp/report.txt 

Qui si tenta di uccidere regolarmente (segnale TERM) il processo dopo 10,0 secondi dall'avvio. Se è ancora in esecuzione dopo 20,0 secondi, invia un segnale KILL (kill -9 ). In caso di dubbio, controlla quali segnali sono supportati nel tuo sistema (kill -l , per esempio).

Se questo non è chiaro dalla mia finestra di dialogo, guarda lo script per maggiore chiarezza.

/usr/bin/time /usr/bin/timeout --kill-after=10.0s 20.0s /usr/bin/sleep 60sreal    0m20.003suser    0m0.000ssys    0m0.003s 

Torna allo script originale per aggiungere qualche altra opzione e hai la versione tre:

 1 #!/bin/bash 2 # Script per raccogliere lo stato dell'output lshw dai server domestici 3 # Dipendenze:4 # * Apri SSH:http://www.openssh.com/portable.html 5 # * LSHW:http://ezix.org/project/wiki/HardwareLiSter 6 # * JQ:http://stedolan.github.io/jq/ 7 # * timeout:https://www.gnu.org/software /coreutils/ 8 # 9 # Su ogni macchina puoi eseguire qualcosa del genere da cron (Non conosco CRON, nessun problema:https://crontab-generator.org/) 10 # 0 0 * * * /usr/sbin /lshw -json -quiet> /var/log/lshw-dump.json 11 # Autore:Jose Vicente Nunez 12 # 13 set -o errtrace # Abilita l'err trap, il codice verrà chiamato quando viene rilevato un errore 14 trap "echo ERRORE:si è verificato un errore in ${FUNCNAME-main context}, dettagli da seguire" ERR 15 16 dichiarare -a dependencies=(/usr/bin/timeout /usr/bin/ssh /usr/bin/jq) 17 per la dipendenza in ${dipendenze[@]}; fare 18     se [ ! -x $dipendenza]; then 19         echo "ERROR:Missing $ dependency" 20         exit 100 21     fi 22 done 23 24 require -a server=( 25 macmini2 26 mac-pro-1 27 dmaf5 28 ) 29 30 function remote_copy { 31     server locale=$1 32 echo "Visiting:$server" 33     /usr/bin/timeout --kill-after 25.0s 20.0s \ 34         /usr/bin/scp \ 35             -o BatchMode=yes \ 36             -o logLevel=Error \ 37       -      =5 \ 38             -o ConnectionAttempts=3 \ 39             ${server}:/var/log/lshw-dump.json ${DATADIR}/lshw-$server-dump.json 40     return $? 41 } 42 43 DATADIR="$HOME/Documents/lshw-dump" 44 se [ ! -d "$DATADIR"]; quindi 45     /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL:Impossibile creare $DATADIR" &&exit 100 46 fi 47 dichiara -A server_pid 48 per il server in ${servers[*]}; fai 49     copia_remota $server e 50     server_pid[$server]=$! # Salva il PID dello scp  di un determinato server per dopo 51 fatto 52 # Scorri tutti i server e:53 # Attendi il codice di ritorno di ciascuno 54 # Controlla il codice di uscita da ogni scp 55 per il server in ${!server_pid [*]}; do 56     attendi ${server_pid[$server]} 57     test $? -ne 0 &&echo "ERRORE:la copia dal server $ ha avuto problemi, non continuerà" &&exit 100 58 done 59 per lshw in $(/usr/bin/find $DATADIR -type f -name 'lshw-*-dump. json'); do 60     /usr/bin/jq '.["product","vendor", "configuration"]' $lshw 61 fatto 

Quali sono le modifiche?:

  • Tra le righe 16-22, controlla se sono presenti tutti gli strumenti di dipendenza richiesti. Se non può essere eseguito, allora "Houston abbiamo un problema".
  • Creato un copia_remota funzione, che utilizza un timeout per assicurarsi che il scp termina entro e non oltre 45.0s—linea 33.
  • Aggiunto un timeout di connessione di 5 secondi invece del TCP predefinito:riga 37.
  • Aggiunto un nuovo tentativo a scp sulla riga 38:3 tentativi che aspettano 1 secondo tra ciascuno.

Ci sono altri modi per riprovare quando si verifica un errore.

Aspettando la fine del mondo:come e quando riprovare

Hai notato che è stato aggiunto un nuovo tentativo a scp comando. Ma che riprova solo per connessioni non riuscite, cosa succede se il comando fallisce durante la copia?

A volte vuoi semplicemente fallire perché ci sono pochissime possibilità di riprenderti da un problema. Un sistema che richiede correzioni hardware, ad esempio, o puoi semplicemente tornare a una modalità degradata, il che significa che sei in grado di continuare il lavoro del tuo sistema senza i dati aggiornati. In questi casi, non ha senso aspettare per sempre, ma solo per un determinato periodo di tempo.

Ecco le modifiche al remote_copy , per mantenere questo breve (versione quattro):

#!/bin/bash# Codice omesso per chiarezza...declare REMOTE_FILE="/var/log/lshw-dump.json"declare MAX_RETRIES=3# Blah blah blah...function remote_copy {    local server=$1    tentativi locali=$2    locale ora=1    stato=0   mentre [ $now -le $ tentativi]; do        echo "INFO:Tentativo di copiare il file da:$server, tentativo=$now"        /usr/bin/timeout --kill-after 25.0s 20.0s \            /usr/bin/scp \                -o BatchMode=yes \                -o logLevel=Errore \                -o ConnectTimeout=5 \                -o ConnectionAttempts=3 \                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json        status=$? se [ $stato -ne 0 ]; then            sleep_time=$(((RANDOM % 60)+ 1))            echo "ATTENZIONE:Copia non riuscita per $server:$REMOTE_FILE. In attesa di '${sleep_time} secondi' prima di riprovare..."            /usr/bin/sleep ${sleep_time}s        else            break # Tutto bene, non ha senso aspettare...        fi        ((now=now+1))    done    return $status}DATADIR="$HOME/Documents/lshw-dump"if [ ! -d "$DATADIR"]; quindi    /usr/bin/mkdir -p -v "$DATADIR"|| "FATAL:Impossibile creare $DATADIR" &&exit 100fideclare -Un server_pidfor server in ${servers[*]}; fai    copia_remota $server $MAX_RETRIES &    server_pid[$server]=$! # Salva il PID di scp  di un determinato server per laterdone# Scorri tutti i server e:# Attendi il codice di ritorno di ciascuno# Controlla il codice di uscita da ciascun server scpfor in ${!server_pid[*]}; fare    attendere ${server_pid[$server]}    testare $? -ne 0 &&echo "ERRORE:la copia da $server ha avuto problemi, non continuerà" &&exit 100done# Blah blah blah, elabora i file che hai appena copiato... 

Come ti sembra adesso? In questa corsa, ho un sistema inattivo (mac-pro-1-1) e un sistema senza il file (macmini2). Puoi vedere che la copia dal server dmaf5 funziona subito, ma per gli altri due è necessario riprovare per un tempo casuale compreso tra 1 e 60 secondi prima di uscire:

INFO:tentativo di copiare il file da:macmini2, tentativo=1INFO:tentativo di copiare il file da:mac-pro-1-1, tentativo=1INFO:tentativo di copiare il file da:dmaf5, tentativo=1scp:/var/log/lshw-dump.json:nessun file o directory di questo tipo ERRORE:si è verificato un errore nel contesto principale, dettagli da seguire AVVERTENZA:copia non riuscita per macmini2:/var/log/lshw-dump.json. In attesa di "60 secondi" prima di riprovare... ssh:connessione all'host mac-pro-1-1 porta 22:nessun percorso verso l'hostERRORE:si è verificato un errore nel contesto principale, dettagli da seguire AVVISO:copia non riuscita per mac-pro -1-1:/var/log/lshw-dump.json. In attesa di '32 secondi' prima di riprovare...INFO:tentativo di copiare il file da:mac-pro-1-1, tentativo=2ssh:connessione all'host mac-pro-1-1 porta 22:nessun percorso verso l'hostERRORE:Si è verificato un errore nel contesto principale, dettagli da seguire AVVERTENZA:copia non riuscita per mac-pro-1-1:/var/log/lshw-dump.json. In attesa di "18 secondi" prima di riprovare... INFO:tentativo di copiare il file da:macmini2, tentativo=2scp:/var/log/lshw-dump.json:nessun file o directory di questo tipoERRORE:si è verificato un errore nel contesto principale , dettagli da seguire AVVERTENZA:copia non riuscita per macmini2:/var/log/lshw-dump.json. In attesa di '3 secondi' prima di riprovare...INFO:tentativo di copiare il file da:macmini2, tentativo=3scp:/var/log/lshw-dump.json:nessun file o directory di questo tipoERRORE:si è verificato un errore nel contesto principale , dettagli da seguire AVVERTENZA:copia non riuscita per macmini2:/var/log/lshw-dump.json. In attesa di '6 secondi' prima di riprovare...INFO:tentativo di copiare il file da:mac-pro-1-1, tentativo=3ssh:connessione all'host mac-pro-1-1 porta 22:nessun percorso verso l'hostERRORE:Si è verificato un errore nel contesto principale, dettagli da seguire AVVERTENZA:copia non riuscita per mac-pro-1-1:/var/log/lshw-dump.json. In attesa di '47 secondi' prima di riprovare...ERRORE:si è verificato un errore nel contesto principale, seguire i dettagliERRORE:la copia da mac-pro-1-1 ha avuto problemi, non continuerà 

Se fallisco, devo rifare tutto da capo? Utilizzando un checkpoint

Supponiamo che la copia remota sia l'operazione più costosa dell'intero script e che tu sia disposto o in grado di rieseguire questo script, magari utilizzando cron oppure facendolo manualmente due volte durante il giorno per assicurarti di raccogliere i file se uno o più sistemi sono inattivi.

Potresti, per la giornata, creare una piccola "cache di stato", in cui registri solo le operazioni di elaborazione riuscite per macchina. Se è presente un sistema, non preoccuparti di ricontrollare per quel giorno.

Alcuni programmi, come Ansible, fanno qualcosa di simile e ti consentono di riprovare un playbook su un numero limitato di macchine dopo un errore (--limit @/home/user/site.retry ).

Una nuova versione (versione cinque) dello script ha un codice per registrare lo stato della copia (righe 15-33):

15 dichiarare SCRIPT_NAME=$(/usr/bin/basename $BASH_SOURCE)|| uscita 10016 dichiara AAAAMMGG=$(/usr/bin/date +%Y%m%d)|| exit 10017 dichiarare CACHE_DIR="/tmp/$SCRIPT_NAME/$AAAAMMGG"18 # La logica per ripulire la directory della cache su base giornaliera non è mostrata qui19 se [ ! -d "$DIR_CACHE"]; poi20  /usr/bin/mkdir -p -v "$CACHE_DIR"|| exit 10021 fi22 trap "/bin/rm -rf $CACHE_DIR" INT KILL2324 funzione check_previous_run {25  local machine=$126  test -f $CACHE_DIR/$machine &&return 0|| return 127 }2829 funzione mark_previous_run {30    machine=$131    /usr/bin/touch $CACHE_DIR/$machine32    return $?33 } 

Hai notato la trappola sulla linea 22? Se lo script viene interrotto (ucciso), voglio assicurarmi che l'intera cache sia invalidata.

E poi, aggiungi questa nuova logica di supporto in remote_copy funzione (righe 52-81):

52 funzione copia_remota {53    server locale=$154    check_previous_run $server55    test $? -eq 0 &&echo "INFO:$1 è stato eseguito correttamente prima. Non si sta ripetendo" &&return 056    local retry=$257    local now=158    status=059    while [ $now -le $retries ]; do60        echo "INFO:Tentativo di copiare il file da:$server, tentativo=$now"61        /usr/bin/timeout --kill-after 25.0s 20.0s \62            /usr/bin/scp \63                -o BatchMode=yes \ 64 -o loglevel =ERROR \ 65 -O ConnectTimeout =5 \ 66 -o Connectionattempts =3 \ 67 $ {Server}:$ remote_file $ {datadir} /lshw-$server -dump.json68 STATUS =$? 69 se [ $stato -ne 0 ]; then70            sleep_time=$(((RANDOM % 60)+ 1))71            echo "WARNING:Copy failed for $server:$REMOTE_FILE. Waiting '${sleep_time} seconds' before use"72            /usr/bin /sleep ${sleep_time}s73        else74            break # Tutto bene, inutile aspettare...75        fi76        ((now=now+1))77    done78    test $status -eq 0 &&mark_previous_run $server79    test $? -ne 0 &&status=180    return $status81 } 

La prima volta che viene eseguito, viene stampato un nuovo nuovo messaggio per la directory della cache:

./collect_data_from_servers.v5.sh/usr/bin/mkdir:directory creata '/tmp/collect_data_from_servers.v5.sh'/usr/bin/mkdir:directory creata '/tmp/collect_data_from_servers.v5.sh /20210612'ERRORE:si è verificato un errore nel contesto principale, dettagli da seguireINFO:tentativo di copiare il file da:macmini2, tentativo=1ERRORE:si è verificato un errore nel contesto principale, dettagli da seguire 

Se lo esegui di nuovo, lo script riconosce che dma5f è a posto, non c'è bisogno di riprovare la copia:

./collect_data_from_servers.v5.shINFO:dmaf5 è stato eseguito correttamente prima. Non si ripeteERRORE:si è verificato un errore nel contesto principale, dettagli da seguireINFO:tentativo di copiare il file da:macmini2, tentativo=1ERRORE:si è verificato un errore nel contesto principale, dettagli da seguireINFO:tentativo di copiare il file da:mac-pro- 1-1, tentativo=1 

Immagina come questo acceleri quando hai più macchine che non dovrebbero essere rivisitate.

Lasciarsi alle spalle le briciole:cosa registrare, come registrare e output dettagliato

Se sei come me, mi piace un po' di contesto con cui correlare quando qualcosa va storto. L'eco le istruzioni sullo script sono belle, ma se potessi aggiungere un timestamp.

Se utilizzi logger , puoi salvare l'output su journalctl per una revisione successiva (anche aggregazione con altri strumenti disponibili). La parte migliore è che mostri la potenza di journalctl subito.

Quindi, invece di fare solo echo , puoi anche aggiungere una chiamata a logger in questo modo usando una nuova funzione bash chiamata 'message ':

SCRIPT_NAME=$(/usr/bin/nomebase $BASH_SOURCE)|| exit 100FULL_PATH=$(/usr/bin/realpath ${BASH_SOURCE[0]})|| exit 100set -o errtrace # Abilita l'err trap, il codice verrà chiamato quando viene rilevato un erroretrap "echo ERROR:Si è verificato un errore in ${FUNCNAME[0]-main context}, dettagli da seguire" ERRdeclare CACHE_DIR="/tmp /$SCRIPT_NAME/$AAAAMMGG"messaggio di funzione {    message="$1"    func_name="${2-unknown}"    priority=6    if [ -z "$2" ]; poi        echo "INFO:" $messaggio   altro        echo "ERRORE:" $messaggio        priority=0    fi    /usr/bin/logger --journald< 

Puoi vedere che puoi memorizzare campi separati come parte del messaggio, come la priorità, lo script che ha prodotto il messaggio, ecc.

Allora, come è utile? Bene, potresti ottenere i messaggi tra le 13:26 e le 13:27, solo errori (priority=0 ) e solo per il nostro script (collect_data_from_servers.v6.sh ) in questo modo, output in formato JSON:

journalctl --dal 13:26 --fino al 13:27 --output json-pretty PRIORITY=0 MESSAGE_ID=collect_data_from_servers.v6.sh 
  {"_boot_id":"dfcda9a1a1cd406ebd88a339bec96fb6", "_audit_loginuid":"1000", "syslog_idenzafier":"logger", "priorità":"0", "_transport":"journal", "_selux", "" :"unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023",        "__REALTIME_TIMESTAMP" :"1623518797641880",        "_AUDIT_SESSION" :"3",        "_GID" :"1000",  6.__   "servers". sh",        "MESSAGE" :"Copia non riuscita per macmini2:/var/log/lshw-dump.json. In attesa di '45 secondi' prima di riprovare...",        "_CAP_EFFECTIVE" :"0",        "CODE_FUNC" :"remote_copy", "_machine_id":"60d7a3f69b674aaebb600c0e82e01d05", "_comm":"logger", "code_file":/home/josevnz/basherror/collect_datag_from_servers.v6.sh "," " :"25928272252",        "_HOSTNAME" :"dmaf5",        "_SOURCE_REALTIME_TIMESTAMP" :"1623518797641843",        "__CURSOR" :"s=97bb6295795a4560ad6f dedd8143df97;i=1f826;b=dfcda9a1a1cd406ebd88a339bec96fb6;m=60972097c;t=5c494ed383898;x=921c71966b8943e3",        "_UID" :"1000"} 

Poiché si tratta di dati strutturati, altri raccoglitori di log possono esaminare tutte le tue macchine, aggregare i log degli script e quindi non solo hai i dati ma anche le informazioni.

Puoi dare un'occhiata all'intera versione sei dello script.

Non essere così ansioso di sostituire i tuoi dati finché non li avrai controllati.


Se hai notato fin dall'inizio, ho copiato ripetutamente un file JSON danneggiato:

Errore di analisi:separatore previsto tra i valori alla riga 4, colonna 11ERROR durante l'analisi di '/home/josevnz/Documents/lshw-dump/lshw-dmaf5-dump.json' 

È facile da prevenire. Copia il file in una posizione temporanea e, se il file è danneggiato, non tentare di sostituire la versione precedente (e lascia quella difettosa per l'ispezione. righe 99-107 della versione 7 dello script):

funzione copia_remota {    server locale=$1    check_previous_run $server    test $? -eq 0 &&messaggio "$1 è stato eseguito correttamente prima. Non si sta ripetendo" &&return 0    local retry=$2    local now=1    status=0    while [ $now -le $retry]; do        messaggio "Tentativo di copiare il file da:$server, tentativo=$ora"        /usr/bin/timeout --kill-after 25.0s 20.0s \            /usr/bin/scp \                -o BatchMode=yes \                -o logLevel=Error \                -o ConnectTimeout=5 \                -o ConnectionAttempts=3 \                ${server}:$REMOTE_FILE ${DATADIR}/lshw-$server-dump.json.$$        status=$? se [ $stato -ne 0 ]; quindi            sleep_time=$(((RANDOM % 60)+ 1))            messaggio "Copia non riuscita per $server:$REMOTE_FILE. In attesa di '${sleep_time} secondi' prima di riprovare..." ${FUNCNAME[0]}            / usr/bin/sleep ${sleep_time}s        else            break # Tutto bene, non ha senso aspettare...        fi        ((now=now+1))    done    if [ $status -eq 0 ]; quindi        /usr/bin/jq '.' ${DATADIR}/lshw-$server-dump.json.$$> /dev/null 2>&1        status=$? se [ $stato -eq 0 ]; quindi            /usr/bin/mv -v -f ${DATADIR}/lshw-$server-dump.json.$$ ${DATADIR}/lshw-$server-dump.json &&mark_previous_run $server            test $? -ne 0 &&status=1        else            messaggio "${DATADIR}/lshw-$server-dump.json.$$ è danneggiato. In partenza per l'ispezione..." ${FUNCNAME[0]}        fi    fi    return $status} 

Scegli gli strumenti giusti per l'attività e prepara il codice dalla prima riga

Un aspetto molto importante della gestione degli errori è la codifica corretta. Se hai una cattiva logica nel tuo codice, nessuna quantità di gestione degli errori lo migliorerà. Per mantenere questo breve e relativo a bash, ti darò di seguito alcuni suggerimenti.

Dovresti SEMPRE controllare la sintassi degli errori prima di eseguire lo script:

bash -n $my_bash_script.sh 

Sul serio. Dovrebbe essere automatico come l'esecuzione di qualsiasi altro test.

Leggi la pagina man di bash e acquisisci familiarità con le opzioni da conoscere, come:

set -xvmy_complicated_instruction1my_complicated_instruction2my_complicated_instruction3set +xv 

Usa ShellCheck per controllare i tuoi script bash

È molto facile perdere problemi semplici quando i tuoi script iniziano a diventare grandi. ShellCheck è uno di quegli strumenti che ti evita di commettere errori.

shellcheck collect_data_from_servers.v7.shIn collect_data_from_servers.v7.sh riga 15:per la dipendenza in ${dipendenze[@]}; do                  ^----------------^ SC2068:Espansioni di array di virgolette doppie per evitare di dividere nuovamente gli elementi. In collect_data_from_servers.v7.sh riga 16:    if [ ! -x $dipendenza]; quindi              ^---------^ SC2086:virgolette doppie per prevenire il globbing e la divisione delle parole. Forse intendevi:     se [ ! -x "$dipendenza"]; allora... 

Se ti stai chiedendo, la versione finale dello script, dopo aver superato ShellCheck, è qui. Perfettamente pulito.

Hai notato qualcosa con i processi scp in background

Probabilmente hai notato che se uccidi lo script, lascia dietro di sé alcuni processi biforcati. Non va bene e questo è uno dei motivi per cui preferisco utilizzare strumenti come Ansible o Parallel per gestire questo tipo di attività su più host, lasciando che i framework facciano la pulizia adeguata per me. Ovviamente puoi aggiungere altro codice per gestire questa situazione.

Questo script bash potrebbe potenzialmente creare una bomba fork. Non ha alcun controllo su quanti processi generare contemporaneamente, il che è un grosso problema in un ambiente di produzione reale. Inoltre, esiste un limite al numero di sessioni ssh simultanee che puoi avere (per non parlare del consumo di larghezza di banda). Ancora una volta, ho scritto questo esempio fittizio in bash per mostrarti come puoi sempre migliorare un programma per gestire meglio gli errori.

Ricapitoliamo

[ Scarica ora:una guida per l'amministratore di sistema allo scripting Bash. ]

1.  Devi controllare il codice di ritorno dei tuoi comandi. Ciò potrebbe significare decidere di riprovare fino a quando una condizione transitoria non migliora o cortocircuitare l'intero script.
2. Parlando di condizioni transitorie, non è necessario ricominciare da zero. È possibile salvare lo stato delle attività riuscite e quindi riprovare da quel momento in poi.
3. Bash 'trap' è tuo amico. Usalo per la pulizia e la gestione degli errori.
4. Quando si scaricano dati da qualsiasi fonte, si supponga che siano danneggiati. Non sovrascrivere mai il tuo buon set di dati con dati nuovi finché non hai eseguito alcuni controlli di integrità.
5. Approfitta del journalctl e dei campi personalizzati. Puoi eseguire ricerche sofisticate alla ricerca di problemi e persino inviare tali dati agli aggregatori di log.
6. Puoi controllare lo stato delle attività in background (comprese le sottoshell). Ricorda solo di salvare il PID e di attendere.
7. E infine:usa un supporto per lanugine Bash come ShellCheck. Puoi installarlo sul tuo editor preferito (come VIM o PyCharm). Sarai sorpreso di quanti errori non vengono rilevati negli script Bash...

Se ti è piaciuto questo contenuto o desideri ampliarlo, contatta il team all'indirizzo [email protected].


Linux
  1. Gestione degli errori negli script Bash

  2. La variabile Curl Outfile non funziona nello script Bash?

  3. Errore script Bash:è prevista un'espressione intera?

  4. Bash:errore di sintassi vicino a token imprevisto `}'?

  5. Script Bash - Esempio Hello World

Bash Shebang

Risoluzione dei problemi di errore "Bash:comando non trovato" in Linux

Impara gli script Bash multi-threading con GNU Parallel

Esegui file di testo come comandi in Bash

Bash che ignora l'errore per un particolare comando

errore di sintassi vicino al token imprevisto ' - bash