Avanti Indietro Indice

8. Lezione 8 - Espansione

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.

8.1 Espansione del percorso

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

8.2 Espansione della tilde

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

8.3 Espansione aritmetica

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.

8.4 Espansione delle parentesi graffe

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!

8.5 Espansione di parametro

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 ~]$

8.6 Sostituzione di comando

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

8.7 Quoting

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.

Doppi apici

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.

Apici singoli

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.

Caratteri di protezione

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.

Atri trucchi con la barra rovesciata

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 escapeNome

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




Avanti Indietro Indice