Panoramica
In questo tutorial, esamineremo le varie tecniche per eseguire il debug degli script della shell Bash. La shell Bash non fornisce alcun debugger integrato. Tuttavia, ci sono alcuni comandi e costrutti che possono essere utilizzati per questo scopo.
In primo luogo, discuteremo gli usi del comando set per gli script di debug. Successivamente, controlleremo alcuni casi d’uso specifici di debug utilizzando i comandi set e trap. Infine, presenteremo alcuni metodi per eseguire il debug di script già in esecuzione.
Opzioni di debug Bash
Le opzioni di debug disponibili nella shell Bash possono essere attivate e disattivate in diversi modi. All’interno degli script, possiamo usare il comando set o aggiungere un’opzione alla riga shebang. Tuttavia, un altro approccio consiste nel specificare esplicitamente le opzioni di debug nella riga di comando durante l’esecuzione dello script. Tuffiamoci nella discussione.
2.1. Abilitazione della modalità verbose
Possiamo abilitare la modalità verbose usando l’interruttore-v, che ci consente di visualizzare ogni comando prima che venga eseguito.
Per dimostrarlo, creiamo uno script di esempio:
#! /bin/bashread -p "Enter the input: " valzero_val=0if then echo "Positive number entered."else echo "The input value is not positive."fi
Questo script controlla se il numero immesso come input è positivo o meno.
Quindi, eseguiamo il nostro script:
$ bash -v ./positive_check.sh#! /bin/bashread -p "Enter the input: " valEnter the input: -10zero_val=0if then echo "Positive number entered."else echo "The input value is not positive."fiThe input value is not positive.
Come possiamo notare, stampa ogni riga dello script sul terminale prima che venga elaborata.
Possiamo anche aggiungere l’opzione-v nella linea shebang:
#! /bin/bash -v
Questo ha lo stesso effetto di chiamare esplicitamente uno script usando bash-v. Un altro equivalente è abilitare la modalità all’interno di uno script usando il comando set:
#! /bin/bashset -v
In effetti, possiamo utilizzare uno dei modi discussi sopra per abilitare i vari switch che discuteremo d’ora in poi.
2.2. Controllo della sintassi Utilizzando la modalità noexec
Ci possono essere situazioni in cui potremmo voler convalidare lo script sintatticamente prima della sua esecuzione. Se è così, possiamo usare la modalità noexec usando l’opzione-n. Di conseguenza, Bash leggerà i comandi ma non li eseguirà.
Eseguiamo il nostro positive_check.sh script in modalità noexec:
$ bash -n ./positive_check.sh
Questo produce un output vuoto poiché non ci sono errori di sintassi. Ora, modificheremo un po ‘ il nostro script e rimuoveremo l’istruzione then:
#! /bin/bashread -p "Enter the input: " valzero_val=0if echo "Positive number entered."else echo "The input value is not positive."fi
Successivamente, lo convalideremo sintatticamente con l’opzione-n:
$ bash -n ./positive_check_noexec.sh./positive_check_noexec.sh: line 6: syntax error near unexpected token `else'./positive_check_noexec.sh: line 6: ` else'
Come previsto, ha generato un errore poiché abbiamo perso l’istruzione then nella condizione if.
2.3. Debug utilizzando la modalità xtrace
Nella sezione precedente, abbiamo testato lo script per errori di sintassi. Ma per identificare gli errori logici, potremmo voler tracciare lo stato delle variabili e dei comandi durante il processo di esecuzione. In questi casi, possiamo eseguire lo script in modalità xtrace (execution trace) utilizzando l’opzione-x.
Questa modalità stampa la traccia dei comandi per ogni riga dopo che sono stati espansi ma prima che vengano eseguiti.
Eseguiamo il nostro positive_check.sh script in modalità traccia esecuzione:
$ bash -x ./positive_check.sh+ read -p 'Enter the input: ' valEnter the input: 17+ zero_val=0+ ''+ echo 'Positive number entered.'Positive number entered.
Qui possiamo vedere la versione espansa delle variabili su stdout prima dell’esecuzione. È importante notare che le linee precedute dal segno + sono generate dalla modalità xtrace.
2.4. Identificazione delle variabili non impostate
Eseguiamo un esperimento per comprendere il comportamento predefinito delle variabili non impostate negli script Bash:
#! /bin/bashfive_val=5two_val=2total=$((five_val+tow_val))echo $total
Ora eseguiremo lo script di cui sopra:
$ ./add_values.sh5
Come possiamo notare, c’è un problema: lo script eseguito correttamente, ma l’output è logicamente errato.
Ora eseguiremo lo script con l’opzione-u:
$ bash -u ./add_values.sh./add_values.sh: line 4: tow_val: unbound variable
Certamente, c’è molta più chiarezza ora!
Lo script non è stato eseguito poiché la variabile tow_val non è definita. Avevamo erroneamente digitato two_val come tow_val durante il calcolo del totale.
L’opzione-u considera variabili e parametri non impostati come un errore durante l’esecuzione dell’espansione dei parametri. Di conseguenza, riceviamo una notifica di errore che una variabile non è legata al valore durante l’esecuzione dello script con l’opzione-u
Casi d’uso per eseguire il debug degli script della Shell
Finora, abbiamo visto le varie opzioni per il debug degli script. D’ora in poi, esamineremo alcuni casi d’uso e metodi per implementarli negli script di shell.
3.1. Combinazione di opzioni di debug
Per ottenere informazioni migliori, possiamo combinare ulteriormente le varie opzioni del comando set.
Eseguiamo il nostro add_values.sh script con entrambe le opzioni-v e-u abilitate:
$ bash -uv ./add_values.sh#! /bin/bashfive_val=5two_val=2total=$((five_val+tow_val))./add_values.sh: line 4: tow_val: unbound variable
Qui, abilitando la modalità dettagliata con l’opzione-u, potremmo facilmente identificare l’istruzione che innesca l’errore.
Allo stesso modo, possiamo combinare la modalità verbose e xtrace per ottenere informazioni di debug più precise.
Come discusso in precedenza, l’opzione-v mostra ogni riga prima di essere valutata e l’opzione-x mostra ogni riga dopo che sono state espanse. Quindi, possiamo combinare entrambe le opzioni-x e-v per vedere come appaiono le istruzioni prima e dopo le sostituzioni di variabili.
Ora, eseguiamo il nostro positive_check.sh script con modalità-x e-v abilitata:
$ bash -xv ./positive_check.sh#! /bin/bashread -p "Enter the input: " val+ read -p 'Enter the input: ' valEnter the input: 5zero_val=0+ zero_val=0if then echo "Positive number entered."else echo "The input value is not positive."fi+ ''+ echo 'Positive number entered.'Positive number entered.
Possiamo osservare che le istruzioni vengono stampate su stdout prima e dopo l’espansione della variabile.
3.2. Debug di parti specifiche dello script
Il debug con gli script di shell delle opzioni-x o-v genera un output per ogni istruzione su stdout. Tuttavia, ci possono essere situazioni in cui potremmo voler ridurre le informazioni di debug solo a parti specifiche dello script. Possiamo ottenere ciò abilitando la modalità di debug prima dell’avvio del blocco di codice e successivamente ripristinarlo utilizzando il comando set.
Controlliamo con un esempio:
#! /bin/bashread -p "Enter the input: " valzero_val=0set -xif then echo "Positive number entered."else echo "The input value is not positive."fiset +xecho "Script Ended"
Qui, potremmo eseguire il debug solo della condizione if utilizzando l’istruzione set prima dell’avvio della condizione. Successivamente, potremmo ripristinare la modalità xtrace dopo la fine del blocco if usando il comando set +x.
Convalidiamolo con l’output:
$ ./positive_debug.shEnter the input: 7+ ''+ echo 'Positive number entered.'Positive number entered.+ set +xScript Ended
Certamente, l’uscita sembra meno ingombra.
3.3. Reindirizzare solo l’output di debug a un file
Nella sezione precedente, abbiamo esaminato come possiamo limitare il debug solo a determinate parti dello script. Di conseguenza, potremmo limitare la quantità di output su stdout.
Inoltre, possiamo reindirizzare le informazioni di debug su un altro file e lasciare che l’output dello script venga stampato su stdout.
Creiamo un altro script per controllarlo:
#! /bin/bashexec 5> debug.log PS4='$LINENO: ' BASH_XTRACEFD="5" read -p "Enter the input: " valzero_val=0if then echo "Positive number entered."else echo "The input value is not positive."fi
Per prima cosa, abbiamo aperto il debug.log file on File Descriptor (FD) 5 per la scrittura utilizzando il comando exec.
Poi abbiamo cambiato la variabile shell speciale PS4. La variabile PS4 definisce il prompt che viene visualizzato quando eseguiamo uno script di shell in modalità xtrace. Il valore predefinito di PS4 è+. Abbiamo cambiato il valore della variabile PS4 per visualizzare i numeri di riga nel prompt di debug. Per raggiungere questo obiettivo, abbiamo usato un’altra variabile shell speciale LINENO.
Successivamente, abbiamo assegnato FD 5 alla variabile Bash BASH_XTRACEFD. In effetti, Bash ora scriverà l’output xtrace su FD5, ovvero debug.log. Eseguiamo lo script:
$ bash -x ./debug_logging.sh+ exec+ PS4='$LINENO: '4: BASH_XTRACEFD=5Enter the input: 2Positive number entered.
Come previsto, l’output di debug non è scritto sul terminale. Sebbene siano state stampate le prime righe, fino a quando FD 5 è assegnato all’output di debug.
Inoltre, lo script crea anche un debug del file di output.log, che contiene le informazioni di debug:
$ cat debug.log5: read -p 'Enter the input: ' val6: zero_val=07: ''9: echo 'Positive number entered.'
Script di debug Usando trap
Possiamo utilizzare la funzione trap di DEBUG di Bash per eseguire ripetutamente un comando. Il comando specificato negli argomenti del comando trap viene eseguito prima di ogni istruzione successiva nello script.
Illustriamo questo con un esempio:
#! /bin/bashtrap 'echo "Line- ${LINENO}: five_val=${five_val}, two_val=${two_val}, total=${total}" ' DEBUGfive_val=5two_val=2total=$((five_val+two_val))echo "Total is: $total"total=0 && echo "Resetting Total"
In questo esempio, abbiamo specificato il comando echo per stampare i valori delle variabili five_val, two_val e total. Successivamente, abbiamo passato questa istruzione echo al comando trap con il segnale di DEBUG. In effetti, prima dell’esecuzione di ogni comando nello script, i valori delle variabili vengono stampati.
Controlliamo l’output generato:
$ ./trap_debug.shLine- 3: five_val=, two_val=, total=Line- 4: five_val=5, two_val=, total=Line- 5: five_val=5, two_val=2, total=Line- 6: five_val=5, two_val=2, total=7Total is: 7Line- 7: five_val=5, two_val=2, total=7Line- 7: five_val=5, two_val=2, total=0Resetting Total
Debug degli script già in esecuzione
Finora, abbiamo presentato i metodi per eseguire il debug degli script della shell durante l’esecuzione. Ora, vedremo i modi per eseguire il debug di uno script già in esecuzione.
Si consideri un esempio di script in esecuzione che esegue sleep in un ciclo while infinito:
#! /bin/bashwhile :do sleep 10 & echo "Sleeping for 4 seconds.." sleep 4done
Con l’aiuto del comando pstree, possiamo controllare i processi figlio biforcati dal nostro script sleep.sh:
$ pstree -pinit(1)─┬─init(148)───bash(149)───sleep.sh(372)─┬─sleep(422) │ ├─sleep(424) │ └─sleep(425) ├─init(213)───bash(214)───pstree(426) └─{init}(7)
Abbiamo utilizzato un’opzione aggiuntiva-p per stampare gli ID dei processi insieme ai nomi dei processi. Quindi, siamo in grado di renderci conto che lo script è in attesa del completamento dei processi figlio (sleep).
A volte potremmo voler dare un’occhiata più da vicino alle operazioni eseguite dai nostri processi. In questi casi, possiamo usare il comando strace per tracciare le chiamate di sistema Linux in corso:
$ sudo strace -c -fp 372strace: Process 372 attachedstrace: Process 789 attachedstrace: Process 790 attached^Cstrace: Process 372 detachedstrace: Process 789 detachedstrace: Process 790 detached% time seconds usecs/call calls errors syscall------ ----------- ----------- --------- --------- ----------------100.00 0.015625 5208 3 wait4 0.00 0.000000 0 6 read 0.00 0.000000 0 1 write 0.00 0.000000 0 39 close 0.00 0.000000 0 36 fstat 0.00 0.000000 0 38 mmap 0.00 0.000000 0 8 mprotect 0.00 0.000000 0 2 munmap 0.00 0.000000 0 6 brk 0.00 0.000000 0 16 rt_sigaction 0.00 0.000000 0 20 rt_sigprocmask 0.00 0.000000 0 1 rt_sigreturn 0.00 0.000000 0 6 6 access 0.00 0.000000 0 1 dup2 0.00 0.000000 0 2 getpid 0.00 0.000000 0 2 clone 0.00 0.000000 0 2 execve 0.00 0.000000 0 2 arch_prctl 0.00 0.000000 0 37 openat------ ----------- ----------- --------- --------- ----------------100.00 0.015625 228 6 total
Qui abbiamo usato l’opzione-p per allegare al processo id (372) cioè il nostro script in esecuzione. Inoltre, abbiamo anche usato l’opzione-f per collegare a tutti i suoi processi figlio. Si noti che, il comando strace genera output per ogni chiamata di sistema. Quindi, abbiamo usato l’opzione-c per stampare un riepilogo delle chiamate di sistema al termine di strace.
Conclusione
In questo tutorial, abbiamo studiato più tecniche per eseguire il debug di uno script di shell.
All’inizio, abbiamo discusso le varie opzioni del comando set e il loro utilizzo per gli script di debug. Successivamente, abbiamo implementato diversi casi di studio per studiare una combinazione di opzioni di debug. Oltre a questo, abbiamo anche esplorato modi per limitare l’output di debug e reindirizzarlo a un altro file.
Successivamente, abbiamo presentato un caso d’uso del comando trap e del segnale di DEBUG per gli scenari di debug. Infine, abbiamo offerto alcuni approcci per eseguire il debug di script già in esecuzione.