Bentornato al nostro consueto appuntamento settimanale sul bash scripting. Quello che oggi ti propongo è una prima infarinatura sugli scripts.
Come già ci siamo detti l’obiettivo di questa serie di guide è imparare a scrivere delle procedure (scripts) in maniera chiara e funzionale; e per farlo dobbiamo iniziare a capire come questi devono essere strutturati.
Introduzione
Qualsiasi sviluppatore, sia che esso programmi in php che in ruby che in qualsiasi altro linguaggio, ti dirà che ci sono sostanzialmente due modi di scrivere il software che si possono descrivere con
- Scrittura fatta a cazzo di cane
- Scrittura fatta bene
Entrambi i metodi funzionano, dando per scontato che i software sono scritti senza errori, vale a dire che due software uguali ma scritti con i due metodi sopra illustrati funzioneranno entrambi, ma – c’è un ma – solo il software scritto col secondo metodo potrà essere definito un buon software, perchè?
Perchè chi usa il primo metodo tende a scrivere il software nella maniera più veloce, senza inserire i cosiddetti “commenti” all’interno del codice – che sono importantissimi – inserendo le funzioni base e senza pensare se una determinata azione può essere eseguita in altro modo, magari con meno linee di codice.
Il secondo metodo invece è migliore perchè, anche se necessità di più tempo per scrivere lo stesso software (ma non sempre eh), fa scrivere del codice più funzionale, vale a dire che se ad esempio il codice è ben commentato e ben indentato1 sarà più facile rileggerlo magari dopo mesi che è stato scritto, soprattutto se a rileggerlo è una persona diversa da chi l’ha scritto inizialmente; inoltre, se il codice è anche ben ottimizzato ne risulterà un software più snello che richiede meno tempo e meno risorse per essere eseguito. Ne consegue che più lo script è complesso e più è importante, nonchè necessario, un processo di progettazione precedente alla stesura del codice; la progettazione è lo step che in molti casi determina la qualità del software finale, pensa che a volte il processo di progettazione può richiedere parecchio tempo, molto più di quello necessario per la stesura del codice finale.
Ecco perchè è cosi importante saper ottimizare il codice che si scrive.
La struttura degli scripts bash
Dunque uno script bash possiamo dividerlo in tre macro-aree che chiameremo ispirandoci alla terminologia che si usa per l’html delle pagine web: header, corpo e – se necessario – il footer. Se non hai mai scritto in bash sappi che tra poco inizierai a leggere dei temini dei quali, quasi sicuramente, non ne saprai il significato. Però non devi spaventarti perchè ovviamente il tutto ti sarà chiarito mano mano che leggerai l’articolo, e qualora cosi non fosse sei invitato a chiedere tutto quello che vuoi nei commenti.
Header
Nello scripting unix l’header, o apertura, degli scripts include come primissima cosa l’interprete che il sistema deve usare per leggere ed eseguire il codice che seguirà. A seguire troveremo – opzionalmente – le istruzioni che servono ad includere eventuali file di configurazione, librerie, altri script, ecc… e poi tutte le variabili che sono necessarie per l’esecuzione dello script.
L’interprete deve essere inserito come prima riga dello script e nel caso di bash si usa:
Se hai visto qualche volta del codice bash noterai una cosa, questa riga inizia con un cancelletto che viene usato per i commenti, però questo codice viene eseguito perchè altrimenti come farebbe la shell a capire quale interprete usare? In realtà una differenza c’è, perchè i commenti iniziano con un asterisco e basta, noi invece stiamo usando la combinazione di asterisco+punto esclamativo che in bash si chiama shebang.
Come fa la shell a capire che quella è una shebang (chiamata anche hashbang) e non un commento? Perchè per essere una hashbang valida, questa combinazione deve apparire per prima, vale a dire che i primi due caratteri del paragrafo che inizia, devono essere appunto “#!” se invece viene piazzato all’interno di un paragrafo in qualsiasi altra posizione allora la shell sa che quello è un commento.
#!/bin/bash <- questa è una hashbang. echo "#!/bin/bash" <- questo è un messaggio da stampare a schermo. # Using #!/bin/bash as interpreter <- questo invece è un commento.
NB. Se dopo aver scritto uno script e lo lanciamo con “./script.sh” otteniamo un “command not found” significa che molto probabilmente non è stato inserito l’interprete in testa al file.
Dopo l’interprete a volte potremmo trovare una sezione dedicata al cosiddetto sourcing delle dipendenze. Per esempio noi potremmo scrivere uno script che per funzionare ha bisogno di un file di configurazione che a sua volta può contenere delle variabili, altri comandi da eseguire, ecc…
Il sourcing può anche essere fatto al di fuori degli script con l’apposito comando ‘source’, per esempio, potremmo voler definire un alias di comandi all’interno del nostro file .bashrc2 per poter aggiornare tutti i pacchetti installati nella nostra linux box.
Normalmente lanceremmo “sudo pacman -Sy” se stessimo usando Archlinux oppure “sudo apt-get safe-upgrade -y” per debian/ubuntu, ma per fare ancora prima possiamo inserire dentro il nostro .bashrc l’alias
oppure
pkgup='apt-get safe-upgrade -y'
di modo da dover solo scrivere “sudo pkgup” per ottenere la stessa azione. Bene, una volta fatta questa modifica per poter usare quell’alias dovremmo lanciare una nuova shell o riloggarci al sistema nel caso stessimo usando un server che non ha quindi un desktop manager come gnome o kde; oppure possiamo renderla definitiva da subitousando il comando source.
Nel caso degli script non dobbiamo usare il comando source, basta solo inserire il path al file che ci interessa preceduto da un punto ed uno spazio.
# Includo il file di configurazione . /path/to/configuration/file
NB. Un file che viene incluso in testa allo script non per forza necessita di essere reso eseguibile (chmod +x).
La prossima cosa che incontriamo nell’header degli scripts sono le definizioni delle variabili, queste sono molto importanti in quanto ci consentono di poter definire a monte pezzi di codice che poi verranno riutilizzati dentro il corpo dello script, per farlo si richiamano appunto queste variabili anteponendo il simbolo del dollaro al nome della variabile, la variabile VAR quindi verrà richiamata con $VAR.
E’ molto importante fare l’uso delle variabili perchè cosi evitiamo di dover ripetere gli stessi pezzi di codice più volte all’interno dello script (ottimizzazione, ricordi?), è facile intuire quindi che le variabili vengono concepite per lo più durante la fase di progettazione.
Per capire come si usano le variabili andiamo a vedere un pezzo di script che creai un paio di anni fa, quando dovetti mettere in piedi una procedura di backup dei server basata su rsync.
#!/bin/bash #Definizione delle variabili PATH_SCRIPT="/usr/local/scripts" RSYNC_CMD=`which rsync` INCLUDE_FILE="$PATH_SCRIPT/path.conf" SERVER_LIST="$PATH_SCRIPT/servers.inc" PATH_BCK="/mnt/backups"
Possiamo notare da subito due cose, la prima è che inizio ad usare sin da subito le variabili che definisco, $PATH_SCRIPT infatti l’ho usata anche per definire il path dell’include file3 ed il path della lista dei server da backuppare; la seconda cosa riguarda il comando rsync:
Avrai notato che tutte le variabili sono definite dentro i doppi apici tranne RSYNC_CMD che è definita dentro degli apici inversi, questo perchè apici, doppi apici, ed apici inversi in bash hanno determinate funzioni.
Con i doppi apici diciamo alla bash che quello che delimitano deve essere stampato valorizzando eventuali variabili contenute all’interno, analogamente con il singolo apice diciamo a bash che quello che è contenuto all’interno deve essere stampato cosi com’è senza tenere conto di variabili, per esempio ponendo di avere definito una variabile VAR=”3″:
echo "il valore di VAR è $VAR"
ci darà in output “il valore di VAR è 3″ mentre
echo 'il valore di var è $VAR'
ci darà in output “il valore di VAR è $VAR”.
Mentre il comportamento dell’apice inverso (che in linux otteniamo con la combinazione altgr+’ mentre in windows tenendo premuto ALT e digitando sul tastierino numerico 096) è differente, dichiarando una variabile con questo carattere diciamo a bash che il valore della stessa deve essere uguale all’output del comando inserito tra apici inversi.
Nel caso dello script di cui sopra quindi RSYNC_CMD avrà come valore l’output del comando “which rsync” che nella maggior parte dei casi sarà uguale a “/bin/rsync”.
Ma perchè ho definito il comando di rsync con una variabile al posto di scrivere “/bin/rsync” ogni volta che mi serviva? Per una questione di usabilità, quando si fa uno script infatti si deve pensare che questo può essere lanciato su qualsiasi architettura, e non tutte le distribuzioni hanno il comando rsync sotto /bin, potrebbe essere lanciato ad esempio su un server sul quale rsync è stato installato da sorgenti dentro il path /usr/local/bin, conviene sempre quindi definire i comandi usati nello script con delle variabili passandogli `which nomecomando` come valore, eccezion fatta per i comandi standard ovviamente, come ad esempio “ls” che troveremo sempre dentro “/bin/ls”.
- L’indentazione è l’inserimento di una serie di spazi vuoti tramite il tasto TAB ad inizio di ogni paragrafo, serve per aiutare la lettura
- .bashrc è il file che viene letto ad ogni nostro login, od ogni qualvolta lanciamo una shell, esso contiene tutte le nostre preferenze per l’uso della shell, come variabili d’ambiente, alias di comandi, personalizzazioni del prompt della shell e tanto altro.
- rsync consente di passare un file come parametro all’interno del quale ci sono dei percorsi specifici da includere o da escludere dai backup
da http://www.ilportalinux.it/linux-bash-scripting-for-dummies-struttura-scripts/