3 581
contributi
(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 | 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>); | |||
* di condizione avanzata (<code>[[</code>, <code>((</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... | ||
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 "$@" | 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 | printf %s $'stringa\n' # equivalente: il carattere "a capo" ora è nella stringa | ||
printf %s | printf %s $'$PATH "" `ls ..`\n' # nessuna espansione | ||
printf %s | printf %s $'~ * .[a-z]*\n' # nessuna espansione | ||
printf %s | printf %s $'{a,b,c} $((2*2))\n' # nessuna espansione | ||
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 | # associo a testo la dimensione in bytes di $file | ||
testo=$( | testo=$(wc -c -- $file) # SBAGLIATO! (se la variabile $file contiene spazi o caratteri speciali) | ||
testo=$( | testo=$(wc -c -- "$file") # le virgolette attorno alla variabile sono necessarie | ||
testo="$( | 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> | ||
=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]] | :[[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) |
contributi