Bash scripting - variabili - stringhe: differenze tra le versioni
S3v (discussione | contributi) (- template "Versioni compatibili") |
m (→Assegnazione dallo standard input: chiarimento) |
||
(11 versioni intermedie di 2 utenti non mostrate) | |||
Riga 1: | Riga 1: | ||
{{ | {{Bash scripting}} | ||
__TOC__ | |||
In '''bash''' ogni variabile di default è trattata come una stringa e, benché siano supportati anche interi e array (indicizzati o associativi), questa guida si limita al solo tipo base. | In '''bash''' ogni variabile di default è trattata come una stringa e, benché siano supportati anche interi e array (indicizzati o associativi), questa guida si limita al solo tipo base. | ||
==Nomi di variabili== | == Nomi di variabili == | ||
Un nome di variabile ammette soltanto caratteri alfabetici (maiuscoli e minuscoli), l'underscore ('_') e numeri (non in prima posizione). E il suo contenuto si accede con <code>${nome}</code> oppure con la forma abbreviata <code>$nome</code>. | Un nome di variabile ammette soltanto caratteri alfabetici (maiuscoli e minuscoli), l'underscore ('_') e numeri (non in prima posizione). E il suo contenuto si accede con <code>${nome}</code> oppure con la forma abbreviata <code>$nome</code>. | ||
La forma abbreviata assume che il nome della variabile sia composto da tutti i caratteri validi incontrati. Per esempio la concatenazione <code>"$nome$cognome"</code> è equivalente a <code>"${nome}${cognome}"</code>, ma <code>"$nome_$cognome"</code> non lo è a <code>"${nome}_${cognome}"</code> perché <code>nome_</code> (con underscore finale) sarebbe un nome valido. | La forma abbreviata assume che il nome della variabile sia composto da tutti i caratteri validi incontrati. Per esempio la concatenazione <code>"$nome$cognome"</code> è equivalente a <code>"${nome}${cognome}"</code>, ma <code>"$nome_$cognome"</code> non lo è a <code>"${nome}_${cognome}"</code> perché <code>nome_</code> (con underscore finale) sarebbe un nome valido. | ||
==Assegnazioni== | == Assegnazioni == | ||
Non si deve usare il <code>'''$'''</code> davanti alla variabile a cui assegnare. La forma consigliata, salvo necessità particolari, è quella tra virgolette per le stringhe e le concatenazioni di stringhe e variabili, e senza virgolette per una singola variabile: | Non si deve usare il <code>'''$'''</code> davanti alla variabile a cui assegnare. La forma consigliata, salvo necessità particolari, è quella tra virgolette per le stringhe e le concatenazioni di stringhe e variabili, e senza virgolette per una singola variabile: | ||
<pre> | <pre> | ||
Riga 29: | Riga 29: | ||
</pre> | </pre> | ||
===Modificatori=== | === Modificatori === | ||
Sono comandi interni che possono essere applicati soltanto a un nome di variabile (senza '''$''') o a un'assegnazione, e in quest'ultimo caso hanno effetto sulla variabile dopo l'avvenuta assegnazione. | Sono comandi interni che possono essere applicati soltanto a un nome di variabile (senza '''$''') o a un'assegnazione, e in quest'ultimo caso hanno effetto sulla variabile dopo l'avvenuta assegnazione. | ||
; export : specifica che la variabile farà parte delle variabili d'ambiente (''environment'') dei comandi esterni eseguiti dallo script: | ; export : specifica che la variabile farà parte delle variabili d'ambiente (''environment'') dei comandi esterni eseguiti dallo script: | ||
Riga 50: | Riga 50: | ||
VAR="valore" | VAR="valore" | ||
readonly VAR | readonly VAR | ||
</pre> | |||
È equivalente a: | |||
<pre> | |||
readonly VAR="valore" | readonly VAR="valore" | ||
</pre> | </pre> | ||
Per convenzione le costanti sono poste tutte all'inizio dello script, prima anche di eventuali definizioni delle funzioni. | Per convenzione le costanti sono poste tutte all'inizio dello script, prima anche di eventuali definizioni delle funzioni. | ||
===Assegnazione dallo standard input=== | === 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 redirezioni e pipe corrisponde a ciò che viene scritto da tastiera prima di un invio. | Con l'istruzione <code>read</code> è possibile assegnare a una o più variabili il contenuto di una riga dello standard input, che senza redirezioni e pipe corrisponde a ciò che viene scritto da tastiera prima di un invio. | ||
Sintassi (base): <code>read nomevariabile [ ... ]</code> | Sintassi (base):<br/> | ||
:<code>read [ -r ] 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. | 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. | ||
Il carattere <code>\</code> è speciale e può essere utilizzato per inserire in una variabile un carattere contenuto in <code>$IFS</code> e/o andare su più righe (gli "a capo" saranno rimossi però dalla variabile). Per inserirlo è una volta è quindi necessario digitare <code>\\</code>, in alternativa con l'opzione <code>-r</code> si può trattare normalmente. | |||
Esempio: | Esempio: | ||
<pre> | <pre> | ||
printf %s "Scrivi qualcosa e premi invio: " | printf %s "Scrivi qualcosa e premi invio: " | ||
read -r testo | |||
printf %s\\n "Hai scritto: ${testo}" | |||
</pre> | |||
Per permettere tramite <code>\</code> di scrivere più righe, basta rimuovere l'opzione <code>-r</code>: | |||
<pre> | |||
printf %s "Scrivi qualcosa (se vuoi andare a capo, premi prima "\"; mentre per scrivere "\" nel testo scrivi "\\") e premi invio: " | |||
read testo | read testo | ||
printf %s\\n "Hai scritto: ${testo}" | printf %s\\n "Hai scritto: ${testo}" # NOTA: tutto su una riga! | ||
</pre> | |||
Se si vogliono modificare i delimitatori, anziché modificare <code>$IFS</code>, che avrebbe effetti anche riguardo la separazione delle stringhe, è necessario assegnarlo sulla stessa riga dell'istruzione, in modo che sia poi ripristinato al suo valore precedente. Anche così facendo l'istruzione <code>read</code> si fermerà comunque al primo "a capo" trovato. | |||
Per esempio: | |||
<pre> | |||
printf %s\\n "Scrivi cognome, nome e data di nascita, separati da ',' e premi INVIO." | |||
printf %s "Cognome, Nome, GG/MM/AAAA: " | |||
# $IFS modificata solo per la durata dell'istruzione read | |||
IFS="," read -r cognome nome data # legge cognome, nome e data, rimuovendo i separatori | |||
# a $nome assegna anche nomi multipli, finché non trova ',' | |||
# $IFS ripristinata al suo valore normale | |||
</pre> | </pre> | ||
===Assegnazione con ciclo=== | === 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. Nella sintassi | 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. Nella sintassi [[POSIX]] è quindi equivalente al ''for each'' di alcuni linguaggi di programmazione. | ||
Sintassi (base): | Sintassi (base): | ||
Riga 104: | Riga 127: | ||
* continuato saltando la corrente iterazione del ciclo con <code>continue</code>, che passa al prossimo elemento della lista, se presente, altrimenti esce. | * 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== | Si ricordi che le istruzioni su più righe possono essere scritte sulla stessa, facendo terminare il comando con il carattere speciale <code>;</code> come fatto per esempio con <code>if ... ; then</code>. Per i cicli tuttavia scrivere <code>do</code> su una nuova riga ne migliora la leggibilità. | ||
== Espansione di variabile == | |||
I caratteri utilizzati per la divisione di una stringa in più stringhe sono quelli contenuti nella variabile <code>$IFS</code> (di default appunto: spazio, tabulazione e "a capo"), ed è consigliabile non modificarla, se non eventualmente prima di <code>read</code>, per non alterare il normale funzionamento di uno script. | |||
Con l'unica eccezione dell'assegnazione, quando si accede al contenuto di una variabile senza quotarla, questa può essere trasformata in più di una singola stringa ('''esplosione''') in base agli spazi (e tabulazioni e "a capo") contenuti, e perfino in "niente" se è vuota. "Niente" proprio come se non presente nel codice. | Con l'unica eccezione dell'assegnazione, quando si accede al contenuto di una variabile senza quotarla, questa può essere trasformata in più di una singola stringa ('''esplosione''') in base agli spazi (e tabulazioni e "a capo") contenuti, e perfino in "niente" se è vuota. "Niente" proprio come se non presente nel codice. | ||
Riga 136: | Riga 163: | ||
Si noti che usando <code>"$ARGUMENTS"</code> (quotata) per una variabile contenente la stringa vuota, il comando leggerebbe lo stesso un argomento e potrebbe fallire. | Si noti che usando <code>"$ARGUMENTS"</code> (quotata) per una variabile contenente la stringa vuota, il comando leggerebbe lo stesso un argomento e potrebbe fallire. | ||
==Variabili speciali== | == Variabili speciali == | ||
; <code>$?</code> : contiene il valore di uscita dell'ultimo comando o funzione (0 solo in caso di successo); | ; <code>$?</code> : contiene il valore di uscita dell'ultimo comando o funzione (0 solo in caso di successo); | ||
Riga 147: | Riga 174: | ||
; <code>$@</code> : contiene la lista di tutti i parametri passati allo script corrente o a una funzione. Ogni parametro viene opportunamente quotato, se questa variabile è quotata, e questo ne permette l'utilizzo nei '''cicli for''' per processare (ad esempio) una lista di nomi di file che possono contenere anche spazi. L'uso di questa variabile è quindi in genere preferito rispetto a <code>'''$*'''</code> che ha la stessa funzione ma, se quotata, non quota i vari parametri ma l'intera stringa; | ; <code>$@</code> : contiene la lista di tutti i parametri passati allo script corrente o a una funzione. Ogni parametro viene opportunamente quotato, se questa variabile è quotata, e questo ne permette l'utilizzo nei '''cicli for''' per processare (ad esempio) una lista di nomi di file che possono contenere anche spazi. L'uso di questa variabile è quindi in genere preferito rispetto a <code>'''$*'''</code> che ha la stessa funzione ma, se quotata, non quota i vari parametri ma l'intera stringa; | ||
Esempio: | |||
<pre> | <pre> | ||
# in "$@" si può anche omettere (lasciando solo: for file) perché implicito | |||
for file in "$@" | for file in "$@" | ||
do | do | ||
# fare quello che si vuole con "$file" | # fare quello che si vuole con "$file" | ||
... | ... | ||
done | |||
</pre> | |||
L'istruzione <code>shift</code> può essere usata per rimuovere i parametri più a sinistra (partendo da <code>$1</code>), spostando tutti gli altri (<code>$1</code><-<code>$2</code>, <code>$2</code><-<code>$3</code>, ...), in questo modo si può accedere, come una lista, solo ai successivi. | |||
Per esempio, codice che crea una lista di file in un percorso scelto: | |||
<pre> | |||
# Sintassi accettata: nomescript destinazione lista_file... | |||
# controllo numero argomenti | |||
if [ $# -lt 2 ]; then | |||
printf %s\\n "Sono richiesti almeno due argomenti!" >&2 | |||
exit 1 | |||
fi | |||
# assegno il primo argomento | |||
destinazine=$1 | |||
shift # ora $* e $@ contengono tutti gli altri parametri ($1<-$2, $2<-$3, ...) | |||
# ossia la lista di file | |||
# controllo l'esistenza della destinazione | |||
if ! [ -d "$destinazione" ]; then | |||
printf %s\\n "Directory di destinazione non trovata!" >&2 | |||
exit 2 | |||
fi | |||
# eseguo un ciclo su tutti i file della lista | |||
# si ricorda che è implicito: in "$@" | |||
for file | |||
do | |||
# crea il file | |||
if ! touch -- "${destinazione}/${file}"; then | |||
# se fallisce si interrompe e stampa un errore | |||
printf %s\\n "Impossibile creare il file \"${destinazione}/${file}\"!" >&2 | |||
exit 3 | |||
fi | |||
done | done | ||
</pre> | </pre> | ||
Riga 160: | Riga 220: | ||
; <code>$!</code> : PID dell'ultimo job in background. | ; <code>$!</code> : PID dell'ultimo job in background. | ||
Esempio: | |||
<pre> | <pre> | ||
comando & # lancio un comando in background | comando & # lancio un comando in background | ||
Riga 168: | Riga 228: | ||
status=$? # catturo il suo exit status | status=$? # catturo il suo exit status | ||
</pre> | </pre> | ||
[[Categoria:Bash]][[Categoria:Bash_Scripting]] | [[Categoria:Bash]][[Categoria:Bash_Scripting]] |
Versione attuale delle 12:05, 18 dic 2015
Bash scripting |
Sommario |
In bash ogni variabile di default è trattata come una stringa e, benché siano supportati anche interi e array (indicizzati o associativi), questa guida si limita al solo tipo base.
Nomi di variabili
Un nome di variabile ammette soltanto caratteri alfabetici (maiuscoli e minuscoli), l'underscore ('_') e numeri (non in prima posizione). E il suo contenuto si accede con ${nome}
oppure con la forma abbreviata $nome
.
La forma abbreviata assume che il nome della variabile sia composto da tutti i caratteri validi incontrati. Per esempio la concatenazione "$nome$cognome"
è equivalente a "${nome}${cognome}"
, ma "$nome_$cognome"
non lo è a "${nome}_${cognome}"
perché nome_
(con underscore finale) sarebbe un nome valido.
Assegnazioni
Non si deve usare il $
davanti alla variabile a cui assegnare. La forma consigliata, salvo necessità particolari, è quella tra virgolette per le stringhe e le concatenazioni di stringhe e variabili, e senza virgolette per una singola variabile:
var="stringa" var=$var2 # senza virgolette var=${var2} # equivalente a sopra var="$var2" # come sopra var="${var1} testo ${var2}" # con virgolette
Altre forme sono possibili, e il loro significato è trattato in seguito:
var=stringa # assegno una stringa (senza spazi e caratteri speciali) var=$'stringa con escape' # come sopra, ma con caratteri di escape e senza espansioni var='stringa senza apici' # niente espansioni, né caratteri di escape # espansioni... var=$(comando) # assegna l'output del comando var=$(($n * 10)) # assegna il risultato dell'operazione var=~utente # assegna la home di utente
Modificatori
Sono comandi interni che possono essere applicati soltanto a un nome di variabile (senza $) o a un'assegnazione, e in quest'ultimo caso hanno effetto sulla variabile dopo l'avvenuta assegnazione.
- export
- specifica che la variabile farà parte delle variabili d'ambiente (environment) dei comandi esterni eseguiti dallo script:
var="stringa" export var # equivalente a: export var="stringa" # comando esterno comando # può accedere al contenuto di var
È possibile definire una o più variabili nell'ambiente di un comando, senza farle ereditare a quelli successivi, semplicemente scrivendo le assegnazioni prima del nome del comando. Per esempio:
var="stringa" comando # può accedere al contenuto di var printf %s\\n "$var" # $var è vuota!
- readonly
- specifica che la variabile (per convenzione scritta con caratteri maiuscoli) è di sola lettura e dev'essere trattata da quel punto in poi come una costante:
VAR="valore" readonly VAR
È equivalente a:
readonly VAR="valore"
Per convenzione le costanti sono poste tutte all'inizio dello script, prima anche di eventuali definizioni delle funzioni.
Assegnazione dallo standard input
Con l'istruzione read
è possibile assegnare a una o più variabili il contenuto di una riga dello standard input, che senza redirezioni e pipe corrisponde a ciò che viene scritto da tastiera prima di un invio.
Sintassi (base):
read [ -r ] nomevariabile [ ... ]
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 $IFS
(di default sono tre: spazio, tabulazione e invio), ma all'ultima variabile viene sempre assegnato tutto il contenuto rimanente fino a fine riga.
Il carattere \
è speciale e può essere utilizzato per inserire in una variabile un carattere contenuto in $IFS
e/o andare su più righe (gli "a capo" saranno rimossi però dalla variabile). Per inserirlo è una volta è quindi necessario digitare \\
, in alternativa con l'opzione -r
si può trattare normalmente.
Esempio:
printf %s "Scrivi qualcosa e premi invio: " read -r testo printf %s\\n "Hai scritto: ${testo}"
Per permettere tramite \
di scrivere più righe, basta rimuovere l'opzione -r
:
printf %s "Scrivi qualcosa (se vuoi andare a capo, premi prima "\"; mentre per scrivere "\" nel testo scrivi "\\") e premi invio: " read testo printf %s\\n "Hai scritto: ${testo}" # NOTA: tutto su una riga!
Se si vogliono modificare i delimitatori, anziché modificare $IFS
, che avrebbe effetti anche riguardo la separazione delle stringhe, è necessario assegnarlo sulla stessa riga dell'istruzione, in modo che sia poi ripristinato al suo valore precedente. Anche così facendo l'istruzione read
si fermerà comunque al primo "a capo" trovato.
Per esempio:
printf %s\\n "Scrivi cognome, nome e data di nascita, separati da ',' e premi INVIO." printf %s "Cognome, Nome, GG/MM/AAAA: " # $IFS modificata solo per la durata dell'istruzione read IFS="," read -r cognome nome data # legge cognome, nome e data, rimuovendo i separatori # a $nome assegna anche nomi multipli, finché non trova ',' # $IFS ripristinata al suo valore normale
Assegnazione con ciclo
Con l'istruzione for
è possibile eseguire un blocco di istruzioni per ogni elemento di una lista di stringhe, assegnando un elemento per volta a una variabile. Nella sintassi POSIX è quindi equivalente al for each di alcuni linguaggi di programmazione.
Sintassi (base):
for nomevariabile [ in ... ] do ... done
Al solito il nome della variabile non va preceduto da $. Se la parola riservata in
e la lista di stringhe sono omesse, allora è equivalente a: in "$@"
Due modi tipici per generare una lista di stringhe, il cui significato sarà trattato successivamente, sono:
- con la variabile speciale
"$@"
, l'unica che se quotata si espande a una lista di stringhe (i parametri passati allo script o a una funzione); - con l'espansione di percorso, per generare una lista di file secondo un dato pattern;
Per esempio:
for file in /percorso/*.txt do if [ -e "$file" ]; then # blocco eseguito su ciascun file .txt in /percorso/ tramite "$file" ... fi done
Il ciclo for
può essere:
- interrotto dall'istruzione
break
; - continuato saltando la corrente iterazione del ciclo con
continue
, che passa al prossimo elemento della lista, se presente, altrimenti esce.
Si ricordi che le istruzioni su più righe possono essere scritte sulla stessa, facendo terminare il comando con il carattere speciale ;
come fatto per esempio con if ... ; then
. Per i cicli tuttavia scrivere do
su una nuova riga ne migliora la leggibilità.
Espansione di variabile
I caratteri utilizzati per la divisione di una stringa in più stringhe sono quelli contenuti nella variabile $IFS
(di default appunto: spazio, tabulazione e "a capo"), ed è consigliabile non modificarla, se non eventualmente prima di read
, per non alterare il normale funzionamento di uno script.
Con l'unica eccezione dell'assegnazione, quando si accede al contenuto di una variabile senza quotarla, questa può essere trasformata in più di una singola stringa (esplosione) in base agli spazi (e tabulazioni e "a capo") contenuti, e perfino in "niente" se è vuota. "Niente" proprio come se non presente nel codice.
Entrambi i comportamenti non sono intuitivi e costituiscono una comune sorgente di errori. Se si vuole sempre considerare il contenuto della variabile come una singola stringa, è necessario accederla quotata (tra virgolette), ossia con "$variabile"
oppure "${variabile}"
.
Si considerino per esempio i seguenti confronti (usati spesso con if
o while
):
[ $var = $var2 ] # SBAGLIATO! (se una delle due è vuota) [ "$var" = "$var2" ] # corretto [ -n $var ] # SBAGLIATO (se var è vuota) [ -n "$var" ] # corretto
Ciò è ancora più importante quando si passa la variabile a un comando che agisce su un file indicato dalla variabile, il cui contenuto in presenza di spazi (comuni per i nomi di file degli utenti) potrebbe venir trattato come una lista di file.
Esempio di codice che crea un backup di un file indicato da una variabile (tramite il comando esterno cp
):
cp -- "$file" "${file}.bak"
D'altra parte accedere una variabile senza quotarla permette di assegnare alla variabile tutte le opzioni da passare a un comando, se sono stringhe senza spazi e caratteri speciali che potrebbero essere espansi nuovamente (* ? [ ]
), per poi accederle in una volta sola:
ARGUMENTS="--arg1 --arg2 ..." ... comando $ARGUMENTS
Si noti che usando "$ARGUMENTS"
(quotata) per una variabile contenente la stringa vuota, il comando leggerebbe lo stesso un argomento e potrebbe fallire.
Variabili speciali
$?
- contiene il valore di uscita dell'ultimo comando o funzione (0 solo in caso di successo);
$0
- contiene il nome usato per lanciare lo script;
$#
- contiene il numero di argomenti passati allo script (o a una funzione, all'interno di una funzione);
$1, $2, ...
- contengono, se presenti, i parametri passati allo script (o a una funzione);
$@
- contiene la lista di tutti i parametri passati allo script corrente o a una funzione. Ogni parametro viene opportunamente quotato, se questa variabile è quotata, e questo ne permette l'utilizzo nei cicli for per processare (ad esempio) una lista di nomi di file che possono contenere anche spazi. L'uso di questa variabile è quindi in genere preferito rispetto a
$*
che ha la stessa funzione ma, se quotata, non quota i vari parametri ma l'intera stringa;
Esempio:
# in "$@" si può anche omettere (lasciando solo: for file) perché implicito for file in "$@" do # fare quello che si vuole con "$file" ... done
L'istruzione shift
può essere usata per rimuovere i parametri più a sinistra (partendo da $1
), spostando tutti gli altri ($1
<-$2
, $2
<-$3
, ...), in questo modo si può accedere, come una lista, solo ai successivi.
Per esempio, codice che crea una lista di file in un percorso scelto:
# Sintassi accettata: nomescript destinazione lista_file... # controllo numero argomenti if [ $# -lt 2 ]; then printf %s\\n "Sono richiesti almeno due argomenti!" >&2 exit 1 fi # assegno il primo argomento destinazine=$1 shift # ora $* e $@ contengono tutti gli altri parametri ($1<-$2, $2<-$3, ...) # ossia la lista di file # controllo l'esistenza della destinazione if ! [ -d "$destinazione" ]; then printf %s\\n "Directory di destinazione non trovata!" >&2 exit 2 fi # eseguo un ciclo su tutti i file della lista # si ricorda che è implicito: in "$@" for file do # crea il file if ! touch -- "${destinazione}/${file}"; then # se fallisce si interrompe e stampa un errore printf %s\\n "Impossibile creare il file \"${destinazione}/${file}\"!" >&2 exit 3 fi done
$$
- PID del processo corrente;
$!
- PID dell'ultimo job in background.
Esempio:
comando & # lancio un comando in background pid=$! # ottengo il PID del comando ... # eseguo altre operazioni wait $pid # attendo la terminazione del comando status=$? # catturo il suo exit status