Bash scripting - variabili - stringhe
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