Bash scripting: differenze tra le versioni

introduzione più estesa con esempi, prima riga e sintassi base dei comandi più importanti
(introduzione più estesa con esempi, prima riga e sintassi base dei comandi più importanti)
Riga 1: Riga 1:
{{Versioni_compatibili}}
{{Versioni_compatibili}}
=Introduzione=
=Introduzione=
Questa non è una guida completa, per la vastità dell'argomento trattato, e per il momento non è indicata nemmeno come prima guida a chi voglia approcciarsi a Bash.
Questa non è una guida completa, per la vastità dell'argomento trattato. In particolare non sono trattati alcuni comandi importanti:
 
* di condizione ed esecuzione condizionata avanzata (<code>[[</code>, <code>((</code>, <code>case</code>);
Infatti allo stato attuale è necessaria almeno la conoscenza dei comandi:
* di condizione (<code>test/[</code>);
* di esecuzione condizionata (<code>if</code>);
* di ciclo (<code>for</code>).
 
Mentre non sono trattati altri comandi importanti:
* di input (<code>read</code>);
* di condizione avanzata (<code>[[</code>, <code>((</code>);
* di esecuzione condizionata (<code>case</code>);
* di ciclo (<code>while</code>);
* per definire funzioni (<code>... ()</code> / <code>function ...</code>);
* per definire funzioni (<code>... ()</code> / <code>function ...</code>);
* per effettuare il parsing degli argomenti (<code>getopts</code>);
* per effettuare il parsing degli argomenti (<code>getopts</code>);
* di gestione e invio di segnali (<code>trap</code>, <code>kill</code>);
* di gestione dei file descriptor;
* modificatori di variabili (<code>readonly</code>, <code>local</code>, <code>declare</code>);
* modificatori di variabili (<code>readonly</code>, <code>local</code>, <code>declare</code>);
* ecc...
* ecc...


Al momento lo scopo della guida è invece, per chi abbia già appreso i concetti più basilari, di evidenziare i comportamenti più distintivi e facili da sbagliare di Bash, con enfasi particolare sulle espansioni di stringhe, estremamente diverse da altri linguaggi di programmazione. Così da passare poi a guide più avanzate per completare l'apprendimento dello scripting in Bash.
Lo scopo della guida è invece, partendo dai concetti più basilari, di evidenziare i comportamenti più distintivi e facili da sbagliare di Bash, con enfasi particolare sulle espansioni di stringhe, estremamente diverse da altri linguaggi di programmazione. Così da poter passare poi a guide più avanzate per completare l'apprendimento dello scripting in Bash.
 
Le spiegazioni fornite in questa sezione introduttiva su alcuni comandi fondamentali si limitano alla loro sintassi base, così che il loro impiego nelle sezioni successive possa essere facilmente compreso.


Per l'uso interattivo della shell si rimanda a [[Bash tips]]. Si noti che l'espansione della history, che qui non è trattata, è attiva soltanto in modalità interattiva.
Per l'uso interattivo della shell si rimanda a [[Bash tips]]. Si noti che l'espansione della history, che qui non è trattata, è attiva soltanto in modalità interattiva.
==Come creare uno script==
Uno script, per poter essere eseguito come un eseguibile qualsiasi, in ambiente UNIX e Unix-like deve:
* avere il bit di esecuzione attivo;
* iniziare con due caratteri: '''<code>#!</code>''' (''shebang'').
Questo specifica che il file è uno script, ossia non è compilato e direttamente eseguibile, e va eseguito indirettamente invocandone l'interprete con il percorso e le eventuali opzioni scritte sulla stessa riga dello shebang.
Per scrivere uno script in '''bash''' basta quindi creare un file con la prima riga che faccia riferimento al percorso della shell:
<pre>
#! /bin/bash
</pre>
È consigliato inoltre di determinare esplicitamente il valore di ritorno (''exit status'') di uno script, facendolo terminare con l'istruzione <code>exit</code> seguita dall'exit status, che è un valore intero compreso tipicamente tra 0 (l'unico valore per successo) e 255. In assenza di tale istruzione il valore di ritorno dello script sarà determinato dall'exit status dell'ultimo comando eseguito.
Inoltre è bene sapere che l'esecuzione di uno script in '''bash''', così come uno in '''sh''' (''POSIX''), di default non è interrotta in presenza di errori. È importante quindi controllare l'exit status dei comandi eseguiti, se possono fallire, con <code>if</code> oppure con gli operatori logici di concantenazione <code>&&</code> e <code>||</code>, che saranno introdotti.
===Commenti===
È considerato commento tutto ciò che segue il carattere <code>#</code> fino alla nuova riga, purché <code>#</code> non sia racchiuso tra virgolette, apici o preceduto da <code>\</code>, in tal caso mantiene il suo valore letterale.
I commenti sono ovviamente ignorati dall'interprete, ma rendono più leggibile il codice.
===Il primo script===
Esempio di classico programma che si limita a stampare "Hello world!" sullo schermo:
<pre>
#! /bin/bash
printf %s\\n "Hello world!"
exit 0
</pre>
===Rendere eseguibile uno script===
Una volta scritto lo script, non resta che attivarne il bit di esecuzione. Supponendo si chiami ''script.sh'', da un terminale dare:
<pre>
$ chmod +x script.sh
</pre>
E dalla directory in cui si trova è possibile eseguirlo dal terminale con:
<pre>
$ ./script.sh
</pre>
==Debug integrato==
'''Bash''', proprio come '''dash''' (la cui sintassi è limitata quasi soltanto alla shell POSIX), ha delle opzioni che ne consentono il debug.
Invocando uno script con <code>-n</code> è possibile effettuare un primitivo controllo di sintassi. Non vengono controllati comandi inesistenti e nemmeno le espansioni, ma può essere utile per verificare che i blocchi sono stati chiusi correttamente prima di eseguire lo script:
<pre>
$ bash -n script.sh
</pre>
Altre opzioni utili, che possono essere impiegate anche congiuntamente durante l'esecuzione:
* <code>-x</code> stampa ogni comando prima di eseguirlo;
* <code>-v</code> stampa l'intero blocco di codice che è stato letto (solo la prima volta);
* <code>-u</code> interrompe lo script se si accede a una variabile mai assegnata;
* <code>-e</code> interrompe lo script in caso di errore (se il comando non è controllato da un <code>if</code>, <code>while</code> o dalla concatenazione con <code>||</code>).
E possono essere usate anche dopo lo shebang nella prima riga dello script. Per esempio:
<pre>
#! /bin/bash -e
</pre>


==Comandi di output: echo e printf==
==Comandi di output: echo e printf==
Il comando <code>echo</code> è largamente diffuso in Bash per stampare delle stringhe su schermo, perché ha una sintassi più semplice di <code>printf</code> e non risente delle stesse limitazioni della shell POSIX, che interpreta ed espande i caratteri di escape (si legga la sezione dedicata) senza che ci sia un modo di stampare letteralmente una stringa.
Il comando <code>echo</code> è largamente diffuso in Bash per stampare delle stringhe su schermo, perché ha una sintassi più semplice di <code>printf</code> e non risente delle stesse limitazioni della shell sh (''POSIX''), che interpreta ed espande i caratteri di escape (si legga la sezione dedicata) senza che ci sia un modo di stampare letteralmente una stringa (non nota a priori).


Tuttavia negli script l'uso di <code>echo</code> non è sempre possibile, rendendo necessaria la conoscenza almeno basilare di <code>printf</code>. In particolare, se si vuole stampare il contenuto di $var, '''non''' è sempre corretto scrivere:
Tuttavia negli script l'uso di <code>echo</code> non è sempre possibile, rendendo necessaria la conoscenza almeno basilare di <code>printf</code>. In particolare, se si vuole stampare il contenuto di $var, '''non''' è sempre corretto scrivere:
Riga 85: Riga 137:
printf '\t%s\n' "$0  [ arg ]"
printf '\t%s\n' "$0  [ arg ]"
</pre>
</pre>
==Condizioni==
Le condizioni nella shell dipendono dal valore di uscita (exit status) di un comando. Si considera successo un exit status corrispondente a 0, ed è equivalente a una condizione vera/soddisfatta, mentre fallimento un exit status con valori diversi da zero, e sono equivalenti a una condizione falsa/non soddisfatta.
Il significato dell'exit status di un comando (successo/fallimento) può essere invertito facendolo precedere da un punto esclamativo (<code>!</code>).
Alcuni comandi hanno un exit status predeterminato:
* <code>:</code> o (equivalentemente) <code>true</code> hanno un exit status sempre di zero (successo/vero);
* <code>false</code> ha un exit status sempre diverso da zero (fallimento/falso).
Per esempio:
<pre>
:      # exit status 0
true    # equivalente
! :    # exit status diverso da 0
false  # exit status diverso da 0
! false # exit status 0
</pre>
===Espressioni booleane===
Le espressioni booleane più basilari, ereditate da ''POSIX'', si possono esprimere con i comandi <code>test</code> e <code>[</code>. L'unica differenza tra i due è che il secondo richiede <code>]</code> come ultimo argomento, ed è preferibile per questioni di leggibilità del codice. D'ora in poi infatti si considera soltanto <code>[ ... ]</code>, e in questa sezione vengono descritte solo le forme più basilari. Per tutte le opzioni supportate si rimanda all'aiuto integrato (<code>help test</code>).
<code>[...]</code> restituisce un exit status di 0 (successo/vero) se la condizione contenuta all'interno è vera, e 1 (fallimento/falso) altrimenti. È molto utile all'interno di istruzioni più complesse, come <code>if</code> per l'esecuzione condizionata e <code>while</code> per eseguire cicli.
Confronti unari con stringhe (tipicamente l'espansione di variabile o parametro):
* <code>[ -z "$var" ]</code>: vero se var contiene una stringa di lunghezza zero o non è definita;
* <code>[ -n "$var" ]</code>: vero se var contiene una stringa che non ha lunghezza zero.
Confronti binari tra stringhe (possono essere anche entrambe variabili):
* <code>[ "$var" = "stringa" ]</code>: vero se il contenuto di var è uguale alla stringa;
* <code>[ "$var" != "stringa" ]</code>: vero se è diverso.
Confronti binari tra stringhe contenenti interi (possono essere anche entrambe variabili):
* <code>[ "$var" -gt valore ]</code>: ('''g'''reater '''t'''han) vero se l'intero contenuto nella variabile è maggiore del valore dato;
* <code>[ "$var" -ge valore ]</code>: ('''g'''reater or '''e'''qual to) vero se l'intero contenuto nella variabile è maggiore o uguale al valore dato;
* <code>[ "$var" -lt valore ]</code>: ('''l'''ower '''t'''han) vero se l'intero contenuto nella variabile è inferiore del valore dato.
* <code>[ "$var" -le valore ]</code>: ('''l'''ower or '''e'''qual to) vero se l'intero contenuto nella variabile è inferiore o uguale al valore dato.
Se una delle due stringhe non è un intero, anche negativo, il confronto fallisce e può esserci la stampa di un messaggio d'errore sullo standard error. Per evitarlo va aggiunto <code>2> /dev/null</code> (il significato di tale redirezione sarà trattato in seguito). Per esempio:
<pre>
[ "$var" -gt 0 ] 2> /dev/null  # non stampa errori se $var non è un intero
</pre>
Confronti unari con stringhe contenenti percorsi di file (percorso di default: directory corrente, se mancante):
* <code>[ -e "$var" ]</code>: vero se il file (file regolare, directory, link simbolico, fifo, socket, ... ) esiste;
* <code>[ -f "$var" ]</code>: vero se il file esiste ed è un file regolare;
* <code>[ -d "$var" ]</code>: vero se il file esiste ed è una directory.
Le espressioni più complesse si possono comporre utilizzando gli operatori logici <code>&&</code> e <code>||</code> per aggregare più istruzioni <code>[...]</code>, e le parentesi <code>{ ... ; }</code> per determinarne la priorità, come si vedrà nella parte sui blocchi di istruzioni.
===Esecuzione condizionata===
Per eseguire un blocco di comandi soltanto se una condizione è soddisfatta si utilizza <code>if</code>, solitamente in combinazione con <code>[...]</code>.
La sua sintassi base (in congiunzione con <code>[...]</code>) è:
<pre>
if [ espressione-booleana ]; then
  ...
[ elif [ espressione-booleana ]; then
  ...              ] 
  ...
[ else ...
  ...              ]
fi
</pre>
Per esempio:
<pre>
if [ -z "$var" ]; then
  printf %s\\n "La variabile var è nulla!"
elif [ "$var" = "pluto" ]; then
  printf %s\\n "La variabile var contiene pluto"
else
  printf %s\\n "La variabile var non è nulla e non contiene pluto, ma: ${var}"
fi
</pre>
====Controllo degli errori====
Si ricordi che <code>if</code> accetta un comando qualsiasi come condizione, valutandone l'exit status ed eseguendo il ramo <code>then</code> se ha successo, e quello <code>elif</code>/<code>else</code> immediatamente successivo (se presente) altrimenti. Quindi è un ottimo strumento anche per controllare che un comando venga eseguito senza errori, permettendo anche la terminazione immediata dello script:
<pre>
if comando; then
  printf %s\\n "Comando riuscito!"
else
  printf %s\\n "ERRORE: comando fallito!"
  # esci con errore (exit status 1)
  exit 1
fi
</pre>
e se si è interessati al solo ramo ''else'', basta utilizzare <code>!</code> prima del comando:
<pre>
if ! comando; then
  exit 1
fi
</pre>
===Ciclo condizionato===
Per eseguire un blocco di comandi se un comando ha successo e ripeterlo finché il comando continua ad avere successo, si può usare l'istruzione <code>while</code>. Proprio come <code>if</code> è spesso usata in congiunzione con <code>[...]</code> per ripetere un blocco di codice tutte le volte che la condizione espressa è vera.
Sintassi base (in congiunzione con <code>[...]</code>):
<pre>
while [ espressione-booleana ]
do
  ...
done
</pre>
Per esempio:
<pre>
num=
# ripeti finché non è soddisfatta la condizione (si noti il ! davanti a [...])
while ! [ "$num" -gt 0 ] 2> /dev/null
do
  printf %s "Inserisci intero > 0 e premi INVIO: "
  # assegna l'input dell'utente a $num
  read num
done
printf %s\\n "Hai inserito: ${num}"
</pre>
Oppure si può utilizzare un comando qualsiasi, per esempio per eseguire un ciclo infinito:
<pre>
while :
do
  ...
done
</pre>
Un ciclo può essere sempre:
* interrotto da <code>break</code>;
* ripreso dalla prima istruzione del blocco, dopo aver rivalutato l'exit status del comando dopo <code>while</code>, con <code>continue</code>.


=Variabili (stringhe)=
=Variabili (stringhe)=
Riga 114: Riga 294:
var=~utente              # assegna la home di utente
var=~utente              # assegna la home di utente
</pre>
</pre>
===Assegnazione dallo standard input===
Con l'istruzione <code>read</code> è possibile assegnare a una o più variabili il contenuto di una riga dello standard input, che senza ridirezioni e pipe corrisponde a ciò che viene scritto da tastiera prima di un invio.
Sintassi (base): <code>read nomevariabile [ ... ]</code>
Il nome delle variabili non va preceduto da '''$''', proprio come nelle assegnazioni normali. Se sono presenti più nomi di variabile, la riga letta si divide in stringhe delimitate dai caratteri contenuti in <code>$IFS</code> (di default sono tre: spazio, tabulazione e invio), ma all'ultima variabile viene sempre assegnato tutto il contenuto rimanente fino a fine riga.
Esempio:
<pre>
printf %s "Scrivi qualcosa e premi invio: "
read testo
printf %s\\n "Hai scritto: ${testo}"
</pre>
===Assegnazione con ciclo===
Con l'istruzione <code>for</code> è possibile eseguire un blocco di istruzioni per ogni elemento di una lista di stringhe, assegnando un elemento per volta a una variabile.
Sintassi (base):
<pre>
for nomevariabile [ in ... ]
do
  ...
done
</pre>
Al solito il nome della variabile non va preceduto da '''$'''. Se la parola riservata <code>in</code> e la lista di stringhe sono omesse, allora è equivalente a: <code>in "$@"</code>
Due modi tipici per generare una lista di stringhe sono:
* con la variabile speciale <code>"$@"</code>, l'unica che se quotata si espande a una lista di stringhe;
* con l'espansione di percorso tramite <code>*</code>, che sarà trattata in seguito.
Per esempio:
<pre>
for file in /percorso/*.txt
do
  if [ -e "$file" ]; then
      # blocco eseguito su ciascun file .txt in /percorso/ tramite "$file"
      ...
  fi
done
</pre>
Il ciclo <code>for</code>, proprio come <code>while</code>, può essere:
* interrotto dall'istruzione <code>break</code>;
* continuato saltando la corrente iterazione del ciclo con <code>continue</code>, che passa al prossimo elemento della lista, se presente, altrimenti esce.


==Espansione di variabile==
==Espansione di variabile==
Riga 160: Riga 386:
''Esempio:''
''Esempio:''
<pre>
<pre>
for file in "$@"; do
for file in "$@"
do
     # fare quello che si vuole con "$file"
     # fare quello che si vuole con "$file"
     # ...
     ...
done
done
</pre>
</pre>
Riga 233: Riga 460:
Per esempio:
Per esempio:
<pre>
<pre>
printf %s\\n $'$PATH "" `ls ..`' # nessuna espansione
printf %s\\n $'stringa'        # stampa la stringa
printf %s\\n $'~ * .[a-z]*'      # nessuna espansione
printf %s $'stringa\n'          # equivalente: il carattere "a capo" ora è nella stringa
printf %s\\n $'{a,b,c} $((2*2))' # nessuna espansione
printf %s $'$PATH "" `ls ..`\n' # nessuna espansione
printf %s\\n $'escape: \\'      # per stampare un \ dev'essere preceduto da \
printf %s $'~ * .[a-z]*\n'      # nessuna espansione
printf %s\\n $'L\'albero di... ' # stampa "L'albero di..." (l'apice può essere stampato con escape)
printf %s $'{a,b,c} $((2*2))\n' # nessuna espansione
printf %s $'L\'albero di...\n'  # equivalente (il carattere "a capo" è nella stringa invece che nel formato di printf)
printf %s $'escape: \\\n'      # per stampare un \ dev'essere preceduto da \
printf %s $'L\'albero di...\n' # stampa "L'albero di..." (l'apice può essere stampato con escape)
</pre>
</pre>
È un metodo molto meno diffuso rispetto a racchiudere tra apici e virgolette, perché non derivata da '''sh''' (''POSIX'').


==Quotare (tra virgolette)==
==Quotare (tra virgolette)==
Riga 362: Riga 592:
oggi="$(date '+%F')" # equivalente a sopra (non servono nelle assegnazioni)
oggi="$(date '+%F')" # equivalente a sopra (non servono nelle assegnazioni)


# associo a testo il contenuto del file indicato da $file
# associo a testo la dimensione in bytes di $file
testo=$(cat -- $file)    # SBAGLIATO! (se la variabile $file contiene spazi o caratteri speciali)
testo=$(wc -c -- $file)    # SBAGLIATO! (se la variabile $file contiene spazi o caratteri speciali)
testo=$(cat -- "$file")  # le virgolette attorno alla variabile sono necessarie
testo=$(wc -c -- "$file")  # le virgolette attorno alla variabile sono necessarie
testo="$(cat -- "$file")" # equivalente a sopra
testo="$(wc -c -- "$file")" # equivalente a sopra
# testo contiene anche altre stringhe, ma sono interessato solo alla prima
bytes=${testo%% *}          # estraggo il primo argomento (espansione di parametro)
</pre>
</pre>


Riga 464: Riga 696:
{{Box | File | Su UNIX e Unix-like per file si può intendere sia un file regolare, ma anche una directory, un link simbolico, una pipe, un socket, un device, ecc...}}
{{Box | File | Su UNIX e Unix-like per file si può intendere sia un file regolare, ma anche una directory, un link simbolico, una pipe, un socket, un device, ecc...}}


Le espansioni di percorso sono possibili solo se i caratteri speciali che la consentono non sono racchiusi tra virgolette, apici o preceduti da <code>/</code>. È sempre consigliabile racchiudere tutto il resto tra virgolette, per non permettere espansioni accidentali.
Le espansioni di percorso sono possibili solo se i caratteri speciali che la consentono (<code>* ? [ ]</code>) non sono racchiusi tra virgolette, apici o preceduti da <code>/</code>. È sempre consigliabile racchiudere tutto il resto tra virgolette, per non permettere espansioni accidentali.


L'espansione non è possibile, direttamente, in un'assegnazione. Avendo la priorità più bassa, contrariamente all'espansione di tilda può avvenire anche in seguito all'espansione di una variabile (e con ogni altra espansione), se non è quotata:
L'espansione non è possibile, direttamente, in un'assegnazione. Avendo la priorità più bassa, contrariamente all'espansione di tilda può avvenire anche in seguito all'espansione di una variabile (e con ogni altra espansione), se non è quotata:
Riga 663: Riga 895:


===Catturare l'exit status===
===Catturare l'exit status===
Per catturare lo stato d'uscita di un comando appena eseguito è sufficiente espandere la variabile speciale <code>$?</code>, come già visto. Tuttavia in caso di fallimento del comando, il controllo effettuato via <code>$?</code> avverrebbe soltanto '''dopo''' un blocco con errore (si veda la parte sul debug).
Per catturare lo stato d'uscita di un comando appena eseguito è sufficiente espandere la variabile speciale <code>$?</code>, come già visto. Tuttavia in caso di fallimento del comando, il controllo effettuato via <code>$?</code> avverrebbe soltanto '''dopo''' un blocco con errore (si veda la parte introduttiva sul debug).


Per evitare che un blocco abbia un exit status diverso da zero, si possono usare le concatenazioni <code>&&</code> e <code>||</code> (oppure un <code>if</code>):
Per evitare che un blocco abbia un exit status diverso da zero, si possono usare le concatenazioni <code>&&</code> e <code>||</code> (oppure un <code>if</code>):
Riga 671: Riga 903:
status=$?  # se sbagliato
status=$?  # se sbagliato
</pre>
</pre>
= Debug integrato =
'''Bash''', proprio come '''dash''', ha delle opzioni che ne consentono il debug.
Invocando uno script con <code>-n</code> è possibile effettuare un primitivo controllo di sintassi. Non vengono controllati comandi inesistenti e nemmeno le espansioni, ma può essere utile per verificare che i blocchi sono stati chiusi correttamente prima di eseguire lo script:
$ bash -n script.sh
Altre opzioni utili, che possono essere impiegate anche congiuntamente durante l'esecuzione:
* <code>-x</code> stampa ogni comando prima di eseguirlo;
* <code>-v</code> stampa l'intero blocco di codice che è stato letto (solo la prima volta);
* <code>-u</code> interrompe lo script se si accede a una variabile mai assegnata;
* <code>-e</code> interrompe lo script in caso di errore (se il comando non è controllato da un <code>if</code>, <code>while</code> o dalla concatenazione con <code>||</code>).


=Link=
=Link=
Riga 693: Riga 913:
|Verificata_da=
|Verificata_da=
:[[Utente:S3v|S3v]] (in Bash tips)
:[[Utente:S3v|S3v]] (in Bash tips)
:[[Utente:HAL 9000|HAL 9000]] 12:14, 15 lug 2014 (CEST)
:[[Utente:HAL 9000|HAL 9000]] 13:24, 16 lug 2014 (CEST)
|Estesa_da=
|Estesa_da=
:[[Utente:S3v|S3v]] (in Bash tips)
:[[Utente:S3v|S3v]] (in Bash tips)
3 581

contributi