Ogni volta che si scrive su una riga di comando e si preme il tasto invio,
bash
esegue diverse elaborazioni sul testo prima di eseguire il
comando. Abbiamo visto in un paio di casi come una sequenza di un solo
carattere, per esempio "*", può avere diversi significati per la shell.
Il processo che permette che questo succeda è chiamato espansione. Con
l'espansione, si digita qualcosa che si espande in qualcos'altro prima che la
shell agisca su di esso. Per dimostrare cosa si intende con questo, andiamo a
dare uno sguardo al comando echo
. Il comando echo
è un
comando incorporato della shell che esegue un compito molto semplice. Stampa i
suoi argomenti di testo sullo standard output:
[io@linuxbox io]$ echo questo è un test
questo è un test
Questo è abbastanza semplice. Qualsiasi argomento passato al comando
echo
viene visualizzato. Proviamo un altro esempio:
[io@linuxbox io]$ echo *
Desktop Documents ls-output.txt Music Pictures Public Templates Videos
Allora cosa è appena successo? Perché il comando echo
non ha stampato il carattere "*"? Come ci si ricorderà dal nostro lavoro
con "caratteri jolly", il carattere "*" significa trovare qualsiasi
carattere in un nome di file, ma quello che non abbiamo visto nella nostra
discussione originale è come la shell riesce a fare questo. La risposta
più semplice è che la shell espande il "*" in qualcos'altro (in
questo caso, i nomi dei file nella directory di lavoro corrente) prima che il
comando echo
sia eseguito. Quando si preme il tasto Invio, la shell
espande automaticamente ogni carattere qualificato sulla riga di comando prima
che il comando venga eseguito, quindi il comando echo
non ha mai visto
il "*", solo il suo risultato espanso. Sapendo questo, possiamo vedere che il
comando echo
si comporta come previsto.
Il meccanismo con cui i caratteri jolly funzionano si chiama espansione del percorso. Se proviamo alcune delle tecniche che abbiamo impiegato nelle nostre precedenti lezioni, vedremo che esse sono davvero espansioni. Data una directory di home che assomiglia a questo:
[io@linuxbox io]$ ls
Desktop
ls-output.txt
Documents Music
Pictures
Public
Templates
Videos
potremmo effettuare le seguenti espansioni:
[io@linuxbox io]$ echo D*
Desktop Documents
e:
[io@linuxbox io]$ echo *s
Documents Pictures Templates Videos
o anche:
[io@linuxbox io]$ echo [[:upper:]]*
Desktop Documents Music Pictures Public Templates Videos
e, guardando oltre la nostra directory di home:
[io@linuxbox io]$ echo /usr/*/share
/usr/kerberos/share /usr/local/share
Come ricorderete dalla nostra introduzione al comando cd
, il carattere
tilde (~) ha uno speciale significato. Quando viene utilizzato all'inizio di
una parola, si espande nel nome della directory di home dell'utente indicato o,
se non viene indicato nessun utente, la directory di home dell'utente corrente:
[io@linuxbox io]$ echo ~
/home/io
Se l'utente "pippo" ha un account:
[io@linuxbox io]$ echo ~pippo
/home/pippo
La shell permette di eseguire calcoli mediante espansione. Questo ci permette di utilizzare il prompt della shell come una calcolatrice:
[io@linuxbox io]$ echo $((2 + 2))
4
l'espansione aritmetica usa la forma:
$((espressione))
dove l'espressione è un'espressione aritmetica che consiste di valori e di operatori aritmetici.
L'espansione aritmetica può supportare solo numeri interi (numeri interi, non decimali), ma può realizzare un certo numero di operazioni differenti.
Gli spazi non sono significativi in espressioni aritmetiche e le espressioni possono essere nidificate. Ad esempio, per moltiplicare cinque al quadrato per tre:
[io@linuxbox io]$ echo $(($((5**2)) * 3))
75
Parentesi singole possono essere utilizzate per raggruppare più sottoespressioni. Con questa tecnica, possiamo riscrivere l'esempio precedente e ottenere lo stesso risultato usando un'unica espansione invece di due:
[io@linuxbox io]$ echo $(((5**2) * 3))
75
Ecco un esempio che utilizza gli operatori di divisione e di resto. Si noti l'effetto di divisione intera:
[io@linuxbox io]$ echo Cinque diviso due è uguale a $((5/2))
Cinque diviso due è uguale a 2
[io@linuxbox io]$ echo col resto di $((5%2)).
col resto di 1.
Forse l'espansione più strana è chiamata espansione delle parentesi graffe. Con essa, è possibile creare più stringhe di testo da un modello contenente le parentesi graffe. Ecco un esempio:
[io@linuxbox io]$ echo Front-{A,B,C}-Back
Front-A-Back Front-B-Back Front-C-Back
I modelli che usano l'espansione delle parentesi graffe possono contenere una porzione iniziale chiamata preambolo e una porzione terminale chiamata appendice. La stessa espressione tra parentesi può contenere un elenco di stringhe separate da virgole, o un intervallo di numeri interi o di caratteri singoli. Il modello non può contenere spazi bianchi incorporati. Ecco un esempio utilizzando un intervallo di numeri interi:
[io@linuxbox io]$ echo Numero_{1..5}
Numero_1 Numero_2 Numero_3 Numero_4 Numero_5
Un intervallo di lettere in ordine inverso:
[io@linuxbox io]$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
Le espressioni tra parentesi graffe possono essere annidate:
[io@linuxbox io]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
Quindi, per cosa va bene questo? L'applicazione più comune è quella di creare elenchi di file o directory. Ad esempio, se si fosse un fotografo e si avesse una grande collezione di immagini e si volesse organizzarla in anni e mesi, la prima cosa che si potrebbe fare è creare una serie di directory chiamate in formato numerico "Anno-Mese". In questo modo, i nomi delle directory sarebbero ordinate in ordine cronologico. Si potrebbe digitare un elenco completo di directory, ma questo sarebbe una gran quantità di lavoro e sarebbe soggetto a troppi errori. Invece, si potrebbe fare questo:
[io@linuxbox io]$ mkdir Foto
[io@linuxbox io]$ cd Foto
[io@linuxbox Photos]$ mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12}
[io@linuxbox Photos]$ ls
2007-01 2007-07 2008-01 2008-07 2009-01 2009-07
2007-02 2007-08 2008-02 2008-08 2009-02 2009-08
2007-03 2007-09 2008-03 2008-09 2009-03 2009-09
2007-04 2007-10 2008-04 2008-10 2009-04 2009-10
2007-05 2007-11 2008-05 2008-11 2009-05 2009-11
2007-06 2007-12 2008-06 2008-12 2009-06 2009-12
Piuttosto efficiente!
In questa lezione accenneremo brevemente all'espansione di parametro, che verrà affrontato più compiutamente in seguito. È una funzionalità che è più utile negli script di shell che direttamente sulla riga di comando. Molte delle sue proprietà hanno a che fare con la capacità del sistema di immagazzinare piccole porzioni di dati e di dare ad ogni porzione un nome. Molti di questi "pezzi", più propriamente detti variabili, sono disponibili per essere esaminate. Per esempio, la variabile denominata "USER" contiene il proprio nome utente. Per richiamare l'espansione di parametro e rivelare il contenuto di USER si dovrebbe fare questo:
[io@linuxbox io]$ echo $USER
io
Per vedere una lista di variabili disponibili, provare questo:
[io@linuxbox io]$ printenv | less
Avrete notato che con altri tipi di espansione, se si digita in modo errato un modello, l'espansione non avrà luogo e il comando echo semplicemente visualizzerà il modello digitato in modo errato. Con l'espansione di parametro, se si sbaglia a scrivere il nome di una variabile, l'espansione avrà comunque luogo, ma si tradurrà in una stringa vuota:
[io@linuxbox io]$ echo $SUER
[io@linuxbox ~]$
La sostituzione di comando ci permette di utilizzare l'uscita di un comando come un'espansione:
[io@linuxbox io]$ echo $(ls)
Desktop Documents ls-output.txt Music Pictures Public Templates Videos
Uno dei miei preferiti è qualcosa di simile a questo:
[io@linuxbox io]$ ls -l $(which cp)
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp
Qui abbiamo passato il risultato di which cp
come argomento al
comando ls
, ottenendo in tal modo l'elenco del programma cp
,
senza dover conoscere il suo percorso completo. Ma non ci limitiamo solo ai
comandi semplici. Possono essere usate intere pipeline (è mostrato
solo un output parziale):
[io@linuxbox io]$ file $(ls /usr/bin/* | grep bin/zip)
/usr/bin/bunzip2:
/usr/bin/zip: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
/usr/bin/zipcloak: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
/usr/bin/zipgrep: POSIX shell script text executable
/usr/bin/zipinfo: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
/usr/bin/zipnote: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
/usr/bin/zipsplit: ELF 32-bit LSB executable, Intel 80386, version 1
(SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
In questo esempio, il risultato della pipeline diventa la lista degli argomenti del comando file. Vi è una sintassi alternativa per la sostituzione dei comandi nei programmi di shell meno recenti, che è supportata anche in bash. Essa usa gli apici inversi al posto del simbolo del dollaro e le parentesi:
[io@linuxbox io]$ ls -l `which cp`
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp
Ora che abbiamo visto in quanti modi la shell è in grado di eseguire espansioni, è il momento di imparare come possiamo controllarli. Prendiamo ad esempio:
[io@linuxbox io]$ echo questa è una prova
questa è una prova
o:
[io@linuxbox io]$ [io@linuxbox ~]$ echo Il tolale è $100.00
Il tolale è 00.00
Nel primo esempio, la suddivisione in parole dalla shell ha rimosso gli spazi in più dalla lista di argomenti del comando echo. Nel secondo esempio, l'espansione di parametro ha sostituito una stringa vuota per il valore di "$1" perch era una variabile non definita. La shell fornisce un meccanismo chiamato quoting per sopprimere in modo selettivo le espansioni indesiderate.
Il primo tipo di quoting che vedremo è a doppi apici. Se si inserisce del testo all'interno di apici doppi, tutti i caratteri speciali usati dalla shell perdono il loro significato speciale e sono trattati come caratteri ordinari. Le eccezioni sono "$", "\" (barra rovesciata), e "` " (apice inverso). Ciò significa che la suddivisione in parole, l'espansione del percorso, l'espansione della tilde, e l'espansione delle parentesi graffe vengono soppresse, ma l'espansione di parametro, l'espansione aritmetica e la sostituzione di comando sono ancora svolte. Usando gli apici doppi, siamo in grado di far fronte ai nomi di file contenenti spazi incorporati. Mettiamo che siete stati la sfortunata vittima di un file chiamato due parole.txt. Se provaste a usarlo sulla riga di comando, la suddivisione in parole farebbe sì che questo file venga trattato come due argomenti distinti, piuttosto che come un unico argomento:
[io@linuxbox io]$ ls -l due parole.txt
ls: impossibile accedere a due: File o directory non esistente
ls: impossibile accedere a parole.txt: File o directory non esistente
Usando i doppi apici, si può fermare la suddivisione in parole e ottenere il risultato desiderato; inoltre, si possono anche riparare i danni:
[io@linuxbox io]$ ls -l "due parole.txt"
-rw-rw-r-- 1 io io 18 2008-02-20 13:03 due parole.txt
[io@linuxbox io]$ mv "due parole.txt" due_parole.txt
Fatto! Ora non si devono più inserire quei fastidiosi doppi apici. Ricordare, espansione di parametro, espansione aritmetica, e la sostituzione di comando avvengono ancora tra doppi apici:
[io@linuxbox io]$ echo "$USER $((2+2)) $(cal)"
io 4
February 2008
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29
Dovremmo fermarci un momento per osservare l'effetto dei doppi apici sulla sostituzione di comando. Prima diamo un'occhiata un po' più a fondo su come funziona la suddivisione in parole. Nel nostro esempio precedente, abbiamo visto come la suddivisione in parole sembra rimuovere gli spazi aggiuntivi nel nostro testo:
[io@linuxbox io]$ echo questa è una prova
questa è una prova
Per impostazione predefinita, la suddivisione in parole cerca la presenza di spazi, tabulazioni e ritorni a capo (caratteri di avanzamento riga) e li tratta come delimitatori tra le parole. Ciò significa che spazi, tabulazioni e ritorni a capo che non sono tra virgolette non sono considerati parte del testo. Servono solo come separatori. Dal momento che separano le parole in diversi argomenti, la nostra riga di comando di esempio contiene un comando seguito da quattro distinti argomenti. Se aggiungiamo dei doppi apici:
[io@linuxbox io]$ echo "this is a test"
this is a test
la suddivisione in parole è soppressa e gli spazi incorporati non sono trattati come delimitatori, anzi diventano parte dell'argomento. Una volta aggiunti i doppi apici, la nostra riga di comando contiene un comando seguito da un argomento unico. Il fatto che i ritorni a capo sono considerati delimitatori dal meccanismo di suddivisione in parole provoca un interessante, seppur tenue, effetto sulla sostituzione di comando. Si consideri il seguente:
[io@linuxbox io]$ echo $(cal)
February 2008 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
19 20 21 22 23 24 25 26 27 28 29
[io@linuxbox io]$ echo "$(cal)"
February 2008
Su Mo Tu We Th Fr Sa
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29
Nel primo caso, la sostituzione di comando senza doppi apici ha dato luogo a una riga di comando che contiene trentotto argomenti. Nel secondo, una riga di comando con un argomento che include gli spazi incorporati e i ritorni a capo.
Se c'è la necessità di sopprimere tutte le espansioni, si usano gli apici singoli. Di seguito un confronto tra un testo senza apici, con doppi apici e con apici singoli:
[io@linuxbox io]$ echo text ~/*.txt {a,b} $(echo pippo) $((2+2)) $USER
text /home/io/ls-output.txt a b pippo 4 io
[io@linuxbox io]$ echo "text ~/*.txt {a,b} $(echo pippo) $((2+2)) $USER"
text ~/*.txt {a,b} pippo 4 io
[io@linuxbox io]$ echo 'text ~/*.txt {a,b} $(echo pippo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo pippo) $((2+2)) $USER
Come si può vedere, a ogni livello successivo di quoting vengono soppresse sempre più espansioni.
A volte si desidera solo mettere tra apici un solo carattere. Per far questo, è possibile far precedere un carattere da una barra rovesciata, che in questo contesto si chiama il carattere di escape. Spesso questo viene fatto all'interno di doppi apici per impedire selettivamente un'espansione:
[io@linuxbox io]$ echo "Il saldo per l'utente $USER è: \$5.00"
Il saldo per l'utente io è: $5.00
È comune anche l'uso di caratteri di escape per eliminare il significato speciale di un carattere in un nome di file. Per esempio, è possibile usare nei nomi di file caratteri che normalmente hanno un significato speciale per la shell. Questi dovrebbero includere "$", "!", "&", "", e altri. Per includere un carattere speciale in un nome di file è possibile fare questo:
[io@linuxbox io]$ mv bad\&filename good_filename
Per consentire a un carattere di barra rovesciata di apparire, proteggerla digitando "\\". Si noti che all'interno di apici singoli, la barra rovesciata perde il suo significato speciale ed è trattata come un carattere normale.
Se si consultano le pagine man
per ogni programma scritto dal progetto
GNU, si noterà che, oltre a opzioni della riga di comando costituite da
un trattino e una sola lettera, ci sono anche nomi di opzioni lunghi che
iniziano con due trattini. Ad esempio, i seguenti comandi sono equivalenti:
ls -r
ls --reverse
Perché sono possibili entrambe le forme? La forma breve è per i dattilografi pigri sulla riga di comando e la forma lunga è usata principalmente per gli script anche se alcune opzioni potrebbero essere solo in forma lunga. A volte uso le opzioni oscure, e trovo utile la forma lunga se devo rivedere ancora uno script mesi dopo averlo scritto. Vedendo la forma lunga mi aiuta a capire che cosa fa l'opzione, mi risparmio un viaggio alla pagina man. Ora un po' più di battitura, più tardi molto meno lavoro. La pigrizia viene mantenuta.
Come si potrebbe sospettare, usando le opzioni in forma lunga si può ottenere un'unica riga di comando molto lunga. Per risolvere questo problema, è possibile usare una barra rovesciata affinché la shell ignori un carattere di ritorno a capo, come di seguito:
ls -l \
--reverse \
--human-readable \
--full-time
Utilizzando la barra rovesciata in questo modo ci permette di includere dei ritorno a capo nel nostro comando. Si noti che affinché questo trucco funzioni, il ritorno a capo dev'essere digitato immediatamente dopo la barra rovesciata. Se si mette uno spazio dopo la barra rovesciata, lo spazio verrà ignorato, non il ritorno a capo. Le barre rovesciate sono usate anche per inserire caratteri speciali dentro il nostro testo. Questi sono chiamati sequenze di escape. Ecco i più comuni:
Sequenza di escape | Nome |
Usi Possibili |
\n | a capo |
Aggiunge righe vuote al testo |
\t | tabulazione |
Inserisce tabulazioni orizzontali al testo |
\a | avviso |
Produce beep al proprio terminale |
\\ | barra rovesciata |
Inserisce una barra rovesciata |
\f | salto pagina |
Inviandolo alla stampante termina la pagina |
L'uso della barra rovesciata come carattere di protezione è molto
comune. Questa idea apparve per la prima volta nel linguaggio di programmazione
C. Oggi, la shell, C++, perl, python, awk, tcl e molti altri linguaggi di
programmazione usano questo concetto. L'uso del comando echo
con
l'opzione -e ci permetterà di mostrare l'uso della barra rovesciata:
[io@linuxbox io]$ echo -e "Inserire diverse righe vuote\n\n\n"
Inserire diverse righe vuote
[io@linuxbox io]$ echo -e "Parole\tseparate\tda\ttabulazioni\ttorizzontali."
Parole separate da tabulazioni orizzontali
[io@linuxbox io]$ echo -e "\aIl mio computer faceva \"beep\"."
Il mio computer faceva "beep".
[io@linuxbox io]$ echo -e "DEL C:\\WIN2K\\LEGACY_OS.EXE"
DEL C:\WIN2K\LEGACY_OS.EXE