
Bug e refusi negli script di Linux Bash possono fare cose terribili quando lo script viene eseguito. Ecco alcuni modi per controllare la sintassi dei tuoi script prima ancora di eseguirli.
Quei fastidiosi insetti
Scrivere codice è difficile. O per essere più precisi, scrivere codice non banale privo di bug è difficile. E più righe di codice ci sono in un programma o script, più è probabile che ci siano dei bug.
La lingua in cui si programma ha un’influenza diretta su questo. La programmazione in assembly è molto più difficile della programmazione in C e la programmazione in C è più impegnativa della programmazione in Python. Più basso è il linguaggio in cui stai programmando, più lavoro devi fare da solo. Python potrebbe godere di routine di garbage collection integrate, ma C e assembly certamente no.
La scrittura di script di shell Linux pone le proprie sfide. Con un linguaggio compilato come C, un programma chiamato compilatore legge il tuo codice sorgente, le istruzioni leggibili dall’uomo che digiti in un file di testo, e lo trasforma in un file eseguibile binario. Il file binario contiene le istruzioni del codice macchina che il computer può comprendere e utilizzare.
Il compilatore genererà un file binario solo se il codice sorgente che sta leggendo e analizzando obbedisce alla sintassi e ad altre regole del linguaggio. Se si scrive a parola riservata—una delle parole di comando della lingua—o un nome di variabile errato, il compilatore genererà un errore.
Ad esempio, alcune lingue insistono nel dichiarare una variabile prima di usarla, altre non sono così esigenti. Se la lingua in cui stai lavorando richiede di dichiarare variabili ma ti dimentichi di farlo, il compilatore genererà un messaggio di errore diverso. Per quanto fastidiosi siano questi errori di compilazione, catturano molti problemi e ti costringono ad affrontarli. Ma anche quando hai un programma che non ha bug sintattici non significa che non ci siano bug. Lontano da esso.
Bug che sono dovuti a difetti logici di solito sono molto più difficili da individuare. Se dici al tuo programma di aggiungere due più tre ma volevi davvero che aggiungesse due più due, non otterrai la risposta che ti aspettavi. Ma il programma sta facendo ciò per cui è stato scritto. Non c’è niente di sbagliato nella composizione o nella sintassi del programma. Il problema sei tu. Hai scritto un programma ben formato che non fa quello che volevi.
Il test è difficile
Testare a fondo un programma, anche semplice, richiede molto tempo. Eseguirlo un paio di volte non è sufficiente; hai davvero bisogno di testare tutti i percorsi di esecuzione nel tuo codice, in modo che tutte le parti del codice siano verificate. Se il programma richiede input, è necessario fornire un intervallo di valori di input sufficiente per testare tutte le condizioni, incluso input inaccettabile.
Per le lingue di livello superiore, i test unitari e i test automatizzati aiutano a rendere i test approfonditi un esercizio gestibile. Quindi la domanda è: ci sono strumenti che possiamo usare per aiutarci a scrivere script di shell Bash privi di bug?
La risposta è sì, inclusa la shell Bash stessa.
Utilizzo di Bash per controllare la sintassi dello script
Il Bash -n
(noexec) l’opzione dice a Bash di leggere uno script e controllarlo per errori sintattici, senza eseguire lo script. A seconda di ciò che lo script è destinato a fare, questo può essere molto più sicuro che eseguirlo e cercare problemi.
Ecco lo script che andremo a controllare. Non è complicato, è principalmente un insieme di if
dichiarazioni. Richiede e accetta un numero che rappresenta un mese. La sceneggiatura decide a quale stagione appartiene il mese. Ovviamente, questo non funzionerà se l’utente non fornisce alcun input o se fornisce input non validi come una lettera anziché una cifra.
#! /bin/bash read -p "Enter a month (1 to 12): " month # did they enter anything? if [ -z "$month" ] then echo "You must enter a number representing a month." exit 1 fi # is it a valid month? if (( "$month" < 1 || "$month" > 12)); then echo "The month must be a number between 1 and 12." exit 0 fi # is it a Spring month? if (( "$month" >= 3 && "$month" < 6)); then echo "That's a Spring month." exit 0 fi # is it a Summer month? if (( "$month" >= 6 && "$month" < 9)); then echo "That's a Summer month." exit 0 fi # is it an Autumn month? if (( "$month" >= 9 && "$month" < 12)); then echo "That's an Autumn month." exit 0 fi # it must be a Winter month echo "That's a Winter month." exit 0
Questa sezione controlla se l’utente ha inserito qualcosa. Verifica se il $month
la variabile non è impostata.
if [ -z "$month" ] then echo "You must enter a number representing a month." exit 1 fi
Questa sezione controlla se hanno inserito un numero compreso tra 1 e 12. Intrappola anche l’input non valido che non è una cifra, perché lettere e segni di punteggiatura non si traducono in valori numerici.
# is it a valid month? if (( "$month" < 1 || "$month" > 12)); then echo "The month must be a number between 1 and 12." exit 0 fi
Tutte le altre clausole If controllano se il valore in $month
variabile è tra due valori. Se lo è, il mese appartiene a quella stagione. Ad esempio, se il mese inserito dall’utente è 6, 7 o 8, è un mese estivo.
# is it a Summer month? if (( "$month" >= 6 && "$month" < 9)); then echo "That's a Summer month." exit 0 fi
Se vuoi elaborare i nostri esempi, copia e incolla il testo dello script in un editor e salvalo come “seasons.sh”. Quindi rendi eseguibile lo script usando il file chmod
comando:
chmod +x seasons.sh
Possiamo testare lo script da
- Non fornendo alcun input.
- Fornire un input non numerico.
- Fornire un valore numerico al di fuori dell’intervallo da 1 a 12.
- Fornire valori numerici compresi tra 1 e 12.
In tutti i casi, avviamo lo script con lo stesso comando. L’unica differenza è l’input fornito dall’utente quando promosso dallo script.
./seasons.sh
Sembra funzionare come previsto. Facciamo controllare a Bash la sintassi del nostro script. Lo facciamo invocando il -n
(noexec) e passando il nome del nostro script.
bash -n ./seasons.sh
Questo è un caso di “nessuna notizia è una buona notizia”. Tornarci silenziosamente al prompt dei comandi è il modo in cui Bash dice che tutto sembra a posto. Sabotiamo il nostro script e introduciamo un errore.
Rimuoveremo il then
dal primo if
clausola.
# is it a valid month? if (( "$month" < 1 || "$month" > 12)); # "then" has been removed echo "The month must be a number between 1 and 12." exit 0 fi
Ora eseguiamo lo script, prima senza e poi con l’input dell’utente.
./seasons.sh
La prima volta che lo script viene eseguito, l’utente non inserisce un valore e quindi lo script viene terminato. La sezione che abbiamo sabotato non viene mai raggiunta. Lo script termina senza un messaggio di errore da Bash.
La seconda volta che lo script viene eseguito, l’utente fornisce un valore di input e la prima clausola if viene eseguita per verificare l’integrità dell’input dell’utente. Ciò attiva il messaggio di errore da Bash.
Nota che Bash controlla la sintassi di quella clausola, e di ogni altra riga di codice, perché non gli interessa logica della sceneggiatura. All’utente non viene richiesto di inserire un numero quando Bash controlla lo script, perché lo script non è in esecuzione.
I diversi possibili percorsi di esecuzione dello script non influenzano il modo in cui Bash controlla la sintassi. Bash si fa strada in modo semplice e metodico dall’inizio alla fine dello script, controllando la sintassi per ogni riga.
L’utilità ShellCheck
Un linter, che prende il nome da uno strumento di controllo del codice sorgente C dei tempi d’oro di Unix, è uno strumento di analisi del codice utilizzato per rilevare errori di programmazione, errori stilistici e uso sospetto o discutibile del linguaggio. I linter sono disponibili per molti linguaggi di programmazione e sono rinomati per essere pedanti. Non tutto ciò che trova un linter è un bug di per séma tutto ciò che portano alla tua attenzione probabilmente merita attenzione.
ShellCheck è uno strumento di analisi del codice per gli script di shell. Si comporta come un linter per Bash.
Mettiamo il nostro disperso then
parola riservata nel nostro script e prova qualcos’altro. Rimuoveremo la parentesi di apertura “[”findall’inizio[”fromtheveryfirstif
clausola.
# did they enter anything? if -z "$month" ] # opening bracket "[" removed then echo "You must enter a number representing a month." exit 1 fi
se usiamo Bash per controllare lo script non ci sono problemi.
bash -n seasons.sh
./seasons.sh
Ma quando ci proviamo correre lo script vediamo un messaggio di errore. E, nonostante il messaggio di errore, lo script continua a essere eseguito. Questo è il motivo per cui alcuni bug sono così pericolosi. Se le azioni intraprese più avanti nello script si basano sull’input valido dell’utente, il comportamento dello script sarà imprevedibile. Potrebbe potenzialmente mettere a rischio i dati.
Il motivo del Bash -n
(noexec) l’opzione non trova l’errore nello script è la parentesi di apertura “[”èunprogrammaesternochiamato[”isanexternalprogramcalled[
. Non fa parte di Bash. È un modo abbreviato di usare il test
comando.
Bash non controlla l’uso di programmi esterni durante la convalida di uno script.
Installazione di ShellCheck
ShellCheck richiede l’installazione. Per installarlo su Ubuntu, digita:
sudo apt install shellcheck
Per installare ShellCheck su Fedora, usa questo comando. Nota che il nome del pacchetto è in maiuscolo misto, ma quando esegui il comando nella finestra del terminale è tutto in minuscolo.
sudo dnf install ShellCheck
Su Manjaro e distribuzioni simili basate su Arch, utilizziamo pacman
:
sudo pacman -S shellcheck
Utilizzando ShellCheck
Proviamo a eseguire ShellCheck sul nostro script.
shellcheck seasons.sh
ShellCheck rileva il problema, ce lo segnala e fornisce una serie di collegamenti per ulteriori informazioni. Se fai clic con il pulsante destro del mouse su un collegamento e scegli “Apri collegamento” dal menu contestuale visualizzato, il collegamento si aprirà nel tuo browser.
ShellCheck trova anche un altro problema, che non è così grave. È riportato in testo verde. Ciò indica che si tratta di un avviso, non di un vero e proprio errore.
Correggiamo il nostro errore e sostituiamo il “[”mancanteUnastrategiadicorrezionedeibugconsistenelcorreggereprimaiproblemiconlaprioritàpiùaltaepoiridurreiproblemiconlaprioritàpiùbassacomegliavvisi[”Onebug-fixstrategyistocorrectthehighestpriorityissuesfirstandworkdowntothelowerpriorityissueslikewarningslater
Abbiamo sostituito il “[“mancanteedeseguitoancoraunavoltaShellCheck[”andranShellCheckoncemore
shellcheck seasons.sh
L’unico output di ShellCheck si riferisce al nostro avviso precedente, quindi va bene. Non abbiamo problemi ad alta priorità da risolvere.
L’avviso ci dice che usando il read
comando senza il -r
L’opzione (leggi così com’è) farà sì che le barre inverse nell’input vengano trattate come caratteri di escape. Questo è un buon esempio del tipo di output pedante che può generare un linter. Nel nostro caso l’utente non dovrebbe comunque inserire una barra rovesciata: abbiamo bisogno che inserisca un numero.
Avvisi come questo richiedono un giudizio da parte del programmatore. Fare lo sforzo di aggiustarlo o lasciarlo così com’è? È una semplice correzione di due secondi. E fermerà l’avviso che ingombra l’output di ShellCheck, quindi potremmo anche seguire il suo consiglio. Aggiungeremo una “r” per opzionare i flag su read
comando e salvare lo script.
read -pr "Enter a month (1 to 12): " month
L’esecuzione di ShellCheck ancora una volta ci dà un buono stato di salute.
ShellCheck è tuo amico
ShellCheck è in grado di rilevare, segnalare e fornire consigli su un’intera gamma di problemi. Dai un’occhiata alla loro galleria di codice errato, che mostra quanti tipi di problemi può rilevare.
È gratuito, veloce e rimuove molto il dolore dalla scrittura di script di shell. Cosa non va?