Un consiglio che i programmatori bash alle prime armi si sentiranno ripetere continuamente:
Metti tra virgolette le tue variabili!
Perché?
Quando espandi una variabile (come $name), quasi sempre vuoi usare il valore della variabile come un'unica stringa (o un unico numero).
Non vuoi che il valore venga trattato inaspettatamente come due o più stringhe separate.
Il quoting è il meccanismo principale per assicurarsi che un pezzo di testo venga trattato come un'unica parola. Esistono due tipi principali di virgolette.
Le virgolette doppie permettono al loro interno l'espansione dei parametri, l'espansione aritmetica e la sostituzione dei comandi.
echo "This is bash version $BASH_VERSION"
Puoi includere una virgoletta doppia letterale all'interno di una stringa racchiusa tra virgolette doppie facendola precedere da una barra rovesciata.
echo "She said \"Let me teach you about bash.\""
Le virgolette singole non permettono alcuna espansione al loro interno.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
Non è possibile inserire una virgoletta singola all'interno di una stringa racchiusa tra virgolette singole.
Cosa c'è che non va esattamente in echo $name?
Quando la shell analizza una riga di codice, esegue diversi tipi di espansione, in ordine. Due delle ultime espansioni sono la suddivisione in parole e l'espansione dei nomi di file.
Qualsiasi risultato di un'espansione di parametri, di una sostituzione di comandi o di un'espansione aritmetica che non è avvenuto all'interno di virgolette doppie è soggetto alla «suddivisione in parole».
La shell prende il risultato espanso e lo divide in corrispondenza di qualsiasi sequenza di caratteri presente nel valore di $IFS.
Per impostazione predefinita, si tratta di spazio, tabulazione e nuova riga.
Possiamo vederlo in azione con un ciclo della shell:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
L'output è:
1 The quick brown fox jumps.
Ma senza virgolette:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
L'output è:
1 The
2 quick
3 brown
4 fox
5 jumps.
Vediamolo con un valore diverso di $IFS:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
L'output è:
first
second
third
Una tecnica per disattivare la suddivisione in parole consiste nell'impostare $IFS a una stringa vuota
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
L'output è:
1 The quick brown fox jumps.
Tuttavia, lasciare le variabili senza virgolette non è consigliato, a meno che non si disattivi anche l'espansione dei nomi di file. Vedremo come farlo più avanti.
I pattern «glob» si usano per esprimere un pattern conciso che individua un insieme di file.
Per esempio, puoi individuare tutti i file Markdown in una directory con *.md.
I pattern glob esistono fin dall'invenzione di Unix.
I caratteri jolly di glob sono:
* corrisponde a zero o più caratteri qualsiasi,
? corrisponde esattamente a un carattere qualsiasi,
[...] corrisponde esattamente a un carattere dell'insieme di caratteri elencati tra le parentesi quadre.
[abc] corrisponde esattamente a un carattere che sia a, b o c.
^)
[][] è un insieme di caratteri che corrisponde sia a una parentesi quadra chiusa sia a una aperta.[[\]]
[x-y] corrisponde esattamente a un carattere che sia nell'intervallo da x a y
^) oppure l'ultimo carattere prima della parentesi quadra chiusa.
[-xy] o [xy-] corrispondono ciascuno esattamente a un carattere che sia -, x o y
[x\-y]
[0-9A-Fa-f] corrisponde a una cifra esadecimale[^abc] corrisponde esattamente a un carattere che NON è a, b o c,[^x-y] corrisponde esattamente a un carattere che NON è nell'intervallo da x a y
[[:character_class:]] corrisponde a un carattere che appartiene alla «character_class» indicata
lower: lettere minuscoleupper: lettere maiuscolealpha: letteredigit: cifrealnum: lettere e cifrespace: spazi bianchiblank: spazi bianchi orizzontali (spazio e tabulazione)xdigit: cifre esadecimaliword: caratteri ammessi in un identificatore (lettere, numeri, underscore)punct: punteggiaturacntrl: caratteri di controllograph: caratteri «visibili»print: caratteri visibili più lo spazio[[:alpha:]] è composta da [[:lower:][:upper:]]
[[:alnum:]] è composta da [[:alpha:][:digit:]]
[[:word:]] è composta da [[:alnum:]_]
[[:word:]] sia a [[:punct:]]
[[:graph:]] è composta da [[:alnum:][:punct:]]
[[:print:]] è composta da [[:graph:] ]: solo lo spazio (ottale 040), non qualsiasi altro spazio bianco.Puoi iterare su un elenco di file con un ciclo for
for file in *.csv; do
do_something_with "$file"
done
Nota che la variabile "$file" è racchiusa tra virgolette.
Il pattern glob può restituire file che contengono spazi nel nome.
Dobbiamo mettere la variabile tra virgolette, in modo che il comando do_something_with riceva esattamente un argomento, il nome del file.
I pattern glob non sono espressioni regolari.
.
?
.*
*
.* significa «individuare un nome di file in cui il primo carattere è un punto letterale, seguito da zero o più caratteri qualsiasi»..+
?*
Bash fornisce i pattern estesi, che sono più vicini alle espressioni regolari.
Ci sono casi in cui vuoi sopprimere l'espansione dei nomi di file. La gestione dell'input dell'utente è uno di questi. Considera questo frammento di codice
read -p "Enter something: " user_input
echo $user_input
Se l'utente digita un asterisco (*), qual è l'output?
L'elenco dei file nella directory corrente.
Probabilmente non è quello che vuoi fare.
Un altro esempio è passare dei pattern come argomenti a programmi che si aspettano dei pattern.
Il programma tr è spesso usato per manipolare il testo. Per convertire in minuscolo il valore di una variabile, puoi fare così
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
L'output è:
my puppet
Un errore comune è dimenticare di mettere tra virgolette gli argomenti di tr: tr [:upper:] [:lower:]
Perché è un problema?
I pattern glob senza virgolette vengono espansi nell'elenco dei file che corrispondono.
Supponi che chi usa lo script abbia nella directory corrente dei file chiamati p, u e w:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
Questo genera un errore!
Il pattern [:upper:] senza virgolette corrisponde a uno qualsiasi tra :, u, p, e o r.
Il pattern [:lower:] senza virgolette corrisponde a uno qualsiasi tra :, l, o, w, e o r.
Bash fa corrispondere i file p e u al primo pattern e il file w al secondo, e tr viene chiamato così
echo "$var" | tr p u w
E questo è il numero sbagliato di argomenti per tr.
Puoi usare il comando set per disattivare l'espansione dei nomi di file.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
Ci sono diverse impostazioni per il comando integrato shopt che controllano come funziona l'espansione dei nomi di file.
Un paio di impostazioni interessanti sono:
shopt -s nocaseglob: esegue la corrispondenza senza distinguere maiuscole e minuscoleshopt -s extglob: abilita i pattern estesishopt -s nullglob: se nessun file corrisponde, sostituisce il pattern con nulla.
Il comportamento predefinito è lasciare il pattern al suo posto come stringa letterale.