\

Aqui temos um livro livre e completo sobre Shell

Os sedentos do "saber livre" são muito benvindos.

Você está aqui: TWikiBar > TWikiBarChiacchiere005
Controles: EDITAR ANEXAR MAIS MAIS ALTERACOES IMPRIMIR - Última Atualização: [21 Feb 2013 - V.4]

Chiacchiere da Bar Parte V

     - Eccoti! Hai messo in ordine le idee? Ti si è fuso il cervello o ce la fai a sopportare ancora un po' di Shell?

     - Ce la faccio, ce la faccio! E mi piace un sacco! Mi piace così tanto che nel fare l'esercizio che mi hai assegnato mi sono sbizzarrito. Ricordi che mi hai chiesto di fare un programma che deve ricevere come parametro il nome di un file e che, quando viene eseguito, salva il file con il suo nome originale seguito da una tilde (~) e lo apre dentro vi?

     - Certo che mi ricordo, fammelo vedere e spiegami come hai fatto.

$ cat visf #!/bin/bash # # visf - vi salva file # == = =

# Verifica inserimento 1 parametro if [ "$#" -ne 1 ] then echo "Errore -> Uso: $0 " exit 1 fi

File=$1 # Se il file non esiste non c'è alcuna copia da salvare if [ ! -f "$File" ] then vi $File exit 0 fi

# Se non si può modificare il file che senso ha usare vi? if [ ! -w "$File" ] then echo "Non hai i privilegi di scrittura in $File" exit 2 fi

# Dato che è tutto OK, salvo una copia e avvio vi cp -f $File $File~ vi $File exit 0

     - Una meraviglia! Ma dimmi un po': perché hai terminato il programma con un exit 0?

     - Ahhh! Ho scoperto che il numero dopo exit risulterà nel codice di ritorno del programma (ricordi $?, vero?), per cui, visto che è filato tutto liscio, il programma si chiuderà con $? = 0. Tuttavia, se guardi bene, ti accorgerai che, se il programma non riceve il nome del file o se l'utente non ha i privilegi di scrittura in questo file, il codice di ritorno ($?) sarà diverso da zero.

     - Bravo ragazzo, ha imparato bene, ma è meglio chiarire che mettendo exit 0, exit o anche niente, si ottiene comunque un codice di ritorno ($?) uguale a zero. Adesso parliamo delle istruzioni di loop o anello, ma prima definirò il concetto di blocco di programma.

Finora abbiamo visto alcuni blocchi di programma. In precedenza ti ho mostrato un esempio per eseguire cd allo scopo di entrare in una directory:

cd lmb 2> /dev/null ||
    {
    mkdir lmb
    cd lmb
    }

Il frammento compreso tra le due parentesi graffe ({}) costituisce un blocco di comandi. Anche nell'esercizio appena visto, nel quale abbiamo salvato un file prima di modificarlo, esistono diversi blocchi di comandi compresi tra i then e i fi di if.

Un blocco di comandi può trovarsi anche all'interno di case e di do e done.

     - Aspetta Julio, di quali do e done parli, non ricordo che tu mi abbia detto niente in merito e guarda che io sono molto attento...

     - Hai ragione, non te ne avevo parlato perché non era ancora arrivato il momento giusto. Tutte le istruzioni di loop o anello, eseguono i comandi del blocco compreso tra do e done.

Comandi di Loop (o anello)

Le istruzioni di loop o anello sono for, while e until che ti spiegherò una per una a partire da adesso.

Il comando for

Se sei abituato a programmare, di certo conosci già il comando for, ma ciò che non sai è che for, il quale è un'istruzione intrinseca di Shell (ciò significa che il codice sorgente del comando è parte del codice sorgente di Shell e per questo è detto anche built-in dai programmatori), è molto più potente dei suoi omologhi in altri linguaggi.

Cerciamo di capire la sua sintassi, dapprima in italiano poi nella pratica.

    a seconda di var in val1 val2 ... valn
    esegui
        cmd1
        cmd2
        cmdn
    fatto

La variabile var può assumere un qualsiasi valore dell'elenco val1 val2 ... valn e, in base a tali valori, il programma esegue il corrispondente blocco di comandi cmd1, cmd2 ... cmdn

Adesso che abbiamo visto il significato dell'istruzione in italiano, vediamone la corretta sintassi:

Prima sintassi del comando for

    for var in val1 val2 ... valn
    do
        cmd1
        cmd2
        cmdn
    done

Facciamo subito qualche esempio per capire il funzionamento del comando. Buttiamo giù uno script che restituisca l'elenco dei file presenti nella nostra directory separati da due-punti, però prima presta attenzione a questo:

$ echo * FileDiDOS.txt1 confuso canzoni elimcanz insercanz inserutente listcanz loggato

La Shell ha visto l'asterisco (*) e lo ha interpretato come il nome di tutti i file della directory e il comando echo li ha visualizzati separandoli con uno spazio. Adesso vediamo come affrontare il problema che dobbiamo risolvere:

$ cat testdifor1 #!/bin/bash # 1o. Programma didattico per capire for

for File in * do echo -n $File: # L'opzione -n serve per non saltare righe done

Proviamo a eseguirlo:

$ testdifor1 FileDoDOS.txt1:confuso:canzoni:elimcanz:insercanz:inserutente:listcanz:loggato:$

Come puoi vedere, la Shell ha trasformato l'asterisco (il quale odia essere chiamato asteristico) in un elenco di file separati da uno spazio. Quando for ha visto l'elenco, ha detto: "Perbacco, un elenco separato da spazi? Arrivo!"

Il blocco di comandi eseguito era soltanto quello di echo, il quale, grazie all'opzione -n, ha elencato la variabile $File seguita da due-punti (:) senza saltare righe. Il simbolo del dollaro ($) alla fine della riga di esecuzione è il prompt, il quale è rimasto nella stessa riga in base all'opzione -n. Altro esempio semplice (per adesso):

$ cat testdifor2 #!/bin/bash # 2o. Programma didattico per capire for

for Parola in Chiacchiere da Bar do echo $Parola done

Eseguendolo otteniamo:

$ testdifor2 Chiacchiere da Bar

Come puoi vedere, questo esempio è elementare come il precedente ma serve a illustrare il comportamento basico di for.

Osserva la forza di for: siamo ancora alla prima sintassi del comando e ti sto già mostrando nuovi modi di usarlo. In precedenza ti ho detto che for usa elenchi separati da spazi, ma questa è una mezza verità utile a facilitare la comprensione.

Inter Field Separator (IFS) con il trucco

Nella realtà, gli elenchi non sono necessariamente separati da spazi ma, prima di proseguire, lascia che ti illustri come si comporta una variabile di sistema chiamata $IFS. Osserva il suo contenuto:

$ echo "$IFS" | od -h 0000000 0920 0a0a 0000004

Ho richiesto il dump esadecimale della variabile (protetta dall'interpretazione della Shell grazie alle virgolette) e ho ottenuto ciò che segue:

Contenuto della Variabile $IFS
0a <ENTER>
  Esadecimale     Significato  
09 <TAB>
20 <SPAZIO>

L'ultimo 0a proviene dall' <ENTER> inserito alla fine del comando. Per capire ancora meglio vediamo un altro esempio:

$ echo ":$IFS:" | cat -vet : ^I$ :$

Presta attenzione al suggerimento seguente per capire il costrutto del comando cat appena visto:

Pinguim com placa de dica (em inglês) Nel comando cat, l'opzione -e rappresenta <ENTER> come un simbolo del dollaro ($) e l'opzione -t rappresenta <TAB> come ^I. Ho usato i due-punti (:) per mostrare l'inizio e la fine di echo. In questo modo possiamo notare ancora una volta che i tre caratteri sono presenti all'interno della variabile.

IFS significa Inter Field Separator o separatore di campo. Una volta compreso questo, posso affermare (e ne ho la prova) che il comando for non usa elenchi separati da spazi ma dal contenuto della variabile $IFS, il cui valore di default sono i caratteri che abbiamo appena visto. Come riprova, vediamo uno script che riceve il nome dell'artista come parametro ed elenca le relative canzoni, ma prima diamo uno sguardo al nostro file canzoni:

$ cat canzoni disco 1^Artista1~Canzone1:Artista2~Canzone2 disco 2^Artista3~Canzone3:Artista4~Canzone4 disco 3^Artista5~Canzone5:Artista6~Canzone6 disco 4^Artista7~Canzone7:Artista1~Canzone3 disco 5^Artista9~Canzone9:Artista10~Canzone10

In base a questo "leiaut" è stato sviluppato lo script che segue:

$ cat elencartista #!/bin/bash # Dato un artista, mostra le sue canzoni

if [ $# -ne 1 ] then echo Devi inserire un parametro exit 1 fi

IFS=" :"

for ArtCanz in $(cut -f2 -d^ canzoni) do echo "$ArtCanz" | grep $1 && echo $ArtCanz | cut -f2 -d~ done

Lo script inizia come sempre con un test sul corretto inserimento dei parametri, quindi si imposta IFS su <ENTER> e due-punti (:) (come dimostrano le virgolette disposte su due righe diverse), perché sono questi a separare i blocchi Artistan~Canzonem. In tal modo, la variabile $ArtCanz riceverà tutti i blocchi del file (nota che for già riceve i record senza il titolo del disco per via del cut presente nella stessa riga). Se trova il parametro ($1) nel blocco, il secondo cut elencherà solamente il titolo della canzone. Proviamo a eseguirlo:

$ elencartista Artista1 Artista1~Canzone1 Canzone1 Artista1~Canzone3 Canzone3 Artista10~Canzone10 Musica10

Mannaggia! Si sono verificati due eventi indesiderabili: sono stati elencati i blocchi e anche Canzone10. Inoltre, il nostro file di canzoni è molto semplice: nella realtà, sia il titolo della canzone che il nome dell'artista contengono più di una parola. Supponiamo che l'artista si chiami Pinco & Pallino (ho paura al solo pensiero che possa esistere davvero). In questo caso, $1 sarebbe Pinco e il resto di questo meraviglioso nome sarebbe ignorato.

Affinché questo non succeda, devo inserire il nome dell'artista tra virgolette (") o cambiare $1 in $@ (tutti i parametri inseriti), che di sicuro è la soluzione migliore, ma dovrei modificare il test sui parametri e grep. Il nuovo test non riguarderebbe l'inserimento di un parametro ma di almeno un parametro e, relativamente a grep, osserva cosa accade dopo aver sostituito $* (messo al posto di $1) con i parametri:

    echo "$ArtCanz" | grep pinco & pallino

In questo modo ci viene restituito un errore. La versione corretta è:

    echo "$ArtCanz" | grep -i "pinco & pallino"

È stata immessa l'opzione -i affinché la ricerca ignori maiuscole e minuscole e le virgolette sono state inserite perché il nome dell'artista sia visto come un'unica stringa.

Dobbiamo ancora sistemare l'errore costituito dall'aver elencato Artista10. Per fare ciò è meglio informare grep che la stringa si trova all'inizio di $ArtCanz (l'espressione regolare per indicare che si trova all'inizio è ^) e che, subito dopo, c'è una tilde (~). C'è bisogno anche di redirezionare l'output di grep in /dev/null affinché i blocchi non siano elencati. Da' un'occhiata alla nuova (e definitiva) versione del programma:

$ cat elencartista #!/bin/bash # Dato un artista, mostra le sue canzoni # versione 2

if [ $# -eq 0 ] then echo Devi inserire almeno un parametro exit 1 fi

IFS=" :"

for ArtCanz in $(cut -f2 -d^ canzoni) do echo "$ArtCanz" | grep -i "^$@~" > /dev/null && echo $ArtCanz| cut -f2 -d~ done

Ecco cosa otteniamo:

$ elencartista Artista1 Canzone1 Canzone3

Seconda sintassi del comando for

    for var
    do
        cmd1
        cmd2
        cmdn
    done

     - Ma senza in come fa a sapere quale valore assumere?

     - Buffo, vero? A prima vista questa costruzione sembra strana ma è piuttosto semplice. In questo caso, var assumerà come valore ognuno dei parametri passati al programma.

Vediamo subito qualche esempio per capire meglio. Buttiamo giù uno script che riceva come parametro un sacco di canzoni e ne elenchi gli autori:

$ cat elencanzone #!/bin/bash # Riceve come parametro parte dei titoli di canzoni e # elenca gli interpreti. Se il nome è composto, deve # essere inserito tra virgolette. # ex. "Non sono il tuo cagnolino" "Polpettina della mamma" # if [ $# -eq 0 ] then echo Uso: $0 canzone1 [canzone2] ... [canzonen] exit 1 fi IFS=" :" for Canzone do echo $Canzone Str=$(grep -i "$Canzone" canzoni) || { echo " Non esiste" continue } for ArtCanz in $(echo "$Str" | cut -f2 -d^) do echo " $ArtCanz" | grep -i "$Canzone" | cut -f1 -d~ done done

Così come abbiamo fatto per gli altri script, iniziamo l'esercizio testando i parametri ricevuti, poi eseguiamo un for nel quale la variabile $Canzone riceverà ognuno dei parametri passati inserendo in $Str tutti i dischi che contengono le canzoni immesse. Subito dopo, l'altro for prenderà ogni blocco Artista~Canzone dai registri presenti in $Str ed elencherà ogni artista che canta quella canzone.

Come sempre, eseguiamo lo script per vedere se funziona davvero:

$ elencanzone canzone3 Canzone4 "Dolce cavallino" canzone3 Artista3 Artista1 Canzone4 Artista4 Dolce cavallino Non esiste

L'elenco ha un aspetto piuttosto bruttino perché non sappiamo ancora formattare l'output, ma uno di questi giorni, quando saprai posizionare il cursore, usare il grassetto, inserire i colori, produrremo di nuovo lo stesso elenco usando tutti gli abbellimenti possibili e lo renderemo molto fashion.

Adesso ti starai chiedendo: "E il for degli altri linguaggi, che inizia a contare a partire da un numero e, con un determinato incremento, raggiunge una determinata condizione?"

E io ti rispondo: "Non ti ho forse detto che il nostro for è più avanti degli altri?" Possiamo fare ciò che hai appena detto in due modi diversi:

1 – Seguendo la prima sintassi che abbiamo visto, come nell'esempio che segue, direttamente dal prompt:

$ for i in $(seq 9) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9

Nello script la variabile i assume come valore gli interi da 1 a 9 generati dal comando seq e l'opzione -n di echo è utilizzata per non avanzare di una riga ogni volta che è elencato un numero (mi sento ecologicamente corretto perché non spreco troppa carta della rivista quando posso evitarlo). Possiamo anche utilizzare for con seq:

$ for i in $(seq 3 9) > do > echo -n "$i " > done 4 5 6 7 8 9

Oppure la forma completa di seq:

$ for i in $(seq 0 3 9) > do > echo -n "$i " > done 0 3 6 9

2 – L'altro modo di fare quel che vogliamo è tramite una sintassi molto simile al for del linguaggio C, come vederemo in seguito.

Terza sintassi del comando for

    for ((var=ini; cond; incr))
    do
        cmd1
        cmd2
        cmdn
    done

Nella quale:

var=ini - Significa che la variabile var partirà dal valore iniziale ini;
cond    - Significa che il loop o anello di for sarà eseguito finché var non raggiungerà la condizione cond;
incr    - Indica l'incremento che la variabile var subirà a ogni esecuzione del loop.

Come sempre è meglio vedere gli esempi che rendono tutto più semplice:

$ for ((i=1; i<=9; i++)) > do > echo -n "$i " > done 1 2 3 4 5 6 7 8 9

In questo caso la variabile i parte dal valore iniziale 1, il blocco di comando (qui soltanto echo) sarà eseguito finché i sarà minore o uguale (<=) a 9 e l'incremento di i sarà pari a 1 a ogni esecuzione del loop.

Osserva che nel for propriamente detto (e non nel blocco dei comandi) non ho messo un simbolo del dollaro ($) prima di i e che la notazione per l'incremento (i++) è diversa da quella vista finora. Ciò accade perché l'uso delle parentesi doppie (così come il comando let) richiama l'interprete aritmetico della Shell che è più tollerante.

Poiché ho fatto riferimento al comando let per mostrare sia il suo funzionamento sia la versatilità di for, faremo la stessa cosa omettendo l'ultima parte della portata di for e passandola al blocco di comandi.

$ for ((; i<=9;)) > do > let i++ > echo -n "$i " > done 1 2 3 4 5 6 7 8 9

Osserva che l'incremento è stato posto fuori dal ciclo di for ed è passsato al blocco di comandi e che, quando ho utilizzato let, non è stato necessario inizializzare la variabile $i. Da' un'occhiata ai comandi seguenti inseriti direttamente nel prompt per illustrare ciò che ho appena detto:

$ echo $j

$ let j++ $ echo $j 1

La variabile $j esiste e nel primo let assume il valore 0 (zero) quindi, dopo l'incremento, prende il valore 1.

Osserva come tutto si semplifica:

$ for file in * > do > let i++ > echo "$i -> $File" > done 1 -> FileDiDOS.txt1 2 -> canzoni 3 -> confuso 4 -> elencanzone 5 -> elencartista 6 -> elimcanz 7 -> insercanz 8 -> inserutente 9 -> listcanz 10 -> loggato 11 -> testdifor1 12 -> testdifor2

     - Amico mio, sono sicuro che del comando for ne hai fin sopra le orecchie. Per oggi basta, la prossima volta che ci vediamo parleremo delle altre istruzioni di loop, ma vorrei che tu realizzassi un piccolo script per contare il numero di parole di un file di testo il cui nome sarà inserito come parametro.

Osservazione: Il conteggio deve essere fatto utilizzando il comando for per abituarsi a usarlo. Non è permesso usare wc -w.

     - Chico! Il bicchiere della staffa!


Licença Creative Commons - Atribuição e Não Comercial (CC) 2017 Pelos Frequentadores do Bar do Júlio Neves.
Todo o conteúdo desta página pode ser utilizado segundo os termos da Creative Commons License: Atribuição-UsoNãoComercial-PermanênciaDaLicença.