Difference: TWikiBarChiacchiere007 ( vs. 1)

Revision 127 Feb 2013 - AlbertoTaddei

Line: 1 to 1
Added:
>
>
META TOPICPARENT name="BatePapos"

Chiacchiere da Bar parte VII



     - Eccoti qua! Allora, ti sei spremuto ben bene le meningi per buttare giù lo script che ti ho chiesto?

     - Certo! Ho rimuginato un sacco davanti allo schermo nero ma credo di esserci riuscito! Se non altro, nelle verifiche che ho fatto sembra funzionare, ma tu trovi sempre il pelo nell'uovo!

     - Non è così, la programmazione shell è molto facile, l'importante è conoscere le dritte e i trucchi giusti. Le correzioni che faccio servono a metterli in evidenza. Piuttosto ordiniamo due birre mentre do un'occhiata al tuo script.

     - Chico, due alla spina. Ricordati che una è senza schiuma.


$ cat ripristina #!/bin/bash # # Ripristina file cancellati con erreemme #

if [ $# -eq 0 ] then echo "Uso: $0 <Nome del File da Ripristinare>" exit 1 fi # Aggiunge il nome della directory d'origine nell'ultima riga Dir=`tail -1 /tmp/$LOGNAME/$1` # Il comando grep -v elimina l'ultima riga e crea il # file con la directory e il nome originario grep -v $Dir /tmp/$LOGNAME/$1 > $Dir/$1 # Rimuove il file che stava per essere eliminato rm /tmp/$LOGNAME/$1

     - Aspetta aspetta, vediamo se ho capito. Per prima cosa immetti nella variabile Dir l'ultima riga del file il cui nome è formato da /tmp/nome dell'utente ($LOGNAME)/parametro passato con il nome del file da ripristinare ($1). Quindi il comando grep -v elimina la riga contenente il nome della directory, cioè l'ultima, invia il resto del file, cioè il file pulito, nella directory originaria e rimuove il file dal “cestino”; F A N T A S T I C O! Perfetto! Nessun errore! Hai visto? Stai già imparando i trucchi della Shell!

     - Allora bando alle ciance, di cosa mi parli oggi?

     - Vedo che la tarantola della Shell ti ha morso per benino. Ottimo, adesso vediamo come si possono (e si devono) leggere dati e formattare lo schermo, ma prima impariamo un comando che ti fornisce tutti gli strumenti per formattare l'input dei dati dallo schermo.

Il comando tput

L'utilizzo principale di questo comando riguarda il posizionamento del cursore sullo schermo, ma si usa spesso anche per cancellare dati dallo schermo, conoscere il numero di righe e colonne per posizionare correttamente un campo, cancellare un campo il cui controllo risulti errato. Infine, quasi tutte le operazioni di formattazione dello schermo sono eseguite tramite questo comando.

Certi attributi del comando tput possono non funzionare se il modello di terminale definito dalla variabile $TERM non ha questa caratteristica incorporata.

Nella tabella che segue sono presenti i principali attributi del comando e gli effetti che generano nel terminale, ma ricorda che ce ne sono molti altri. Osserva:

$ tput it 8

In questo esempio ho ottenuto la grandezza iniziale di <TAB> ( Initial T ab). Di certo ti chiederai: a che mi serve saperlo? Se vuoi conoscere a fondo il comando tput (e attento che non finiresti mai), vai sul sito http://www.cs.utah.edu/dept/old/texinfo/tput/tput.html#SEC4.

Principali Opzioni del Comando tput
rc   Restore Cursor position - Colloca il cursore nella posizione salvata dall'ultimo sc
Opzioni di tput   Effeito
cup rig col   CUrsor Position - Posiziona il cursore nella riga rig e nella colonna col. L'origine è zero
bold   Carattere in grassetto
rev   Carattere nero su sfondo colorato
smso   Come sopra
smul   A partire da questa istruzione i caratteri appariranno sottolineati
blink   I caratteri immessi saranno intermittenti
sgr0   Dopo aver usato uno d degli attributi sopraelencati, usa i seguenti per riportare lo schermo alla modalità normale
reset   Pulisce il terminale e ne ripristina le definizioni in base a terminfo, cioè il terminale ritorna ai parametri definiti dalla variabile $TERM  
lines   Restituisce il numero di righe al momento dell'istruzione
cols   Restituisce il numero di colonne al momento dell'istruzione
el   Erase Line – Cancella la riga a partendo dalla posizione del cursore
ed   Erase Display – Cancella lo schermo partendo dalla posizione del cursore
il n   Insert Lines - Inserisce n righe partendo dalla posizione del cursore
dl n   Delete Lines - Rimuove n righe partendo dalla posizione del cursore
ech n   Erase CHaracters – Cancella n caratteri partendo dalla posizione del cursore
sc   Save Cursor position - Salva la posizione del cursore

Scriveremo un bel programmino facile facile per mostrare alcune caratteristiche di questo comando. È il famoso e famigerato Hello World, soltanto che questa frase comparirà al centro dello schermo a caratteri neri con sfondo colorato, quindi il cursore tornerà nella posizione in cui si trovava prima che fosse scritta quella frasetta così creativa. Osserva:

$ cat hello.sh #!/bin/bash # Script stupido per testare # il comando tput (versione 1)

Colonne=`tput colonne` # Salvataggio numero colonne Righe=`tput righe` # Salvataggio numero righe Riga=$((Righe / 2)) # Qual'è la riga centrale dello schermo? Colonna=$(((Colonne - 11) / 2)) # Centratura messaggio sullo schermo tput sc # Salvataggio posizione del cursore tput cup $Riga $Colonna # Posizionamento per scrittura tput rev # Carattere inverso echo Hello World tput sgr0 # Ripristino dello schermo alla normalità tput rc # Ripristino cursore nella posizione originaria

Poiché il programma è interamente commentato, ritengo che l'unica spiegazione necessaria riguardi la riga in cui è creata la variabile Colonna. Ciò che vi è di strano è il numero 11, che non è altro che la lunghezza della stringa che voglio scrivere (Hello World).

Così com'è, il programma potrebbe centrare soltanto stringhe di 11 caratteri, ma osserva:

$ var=Chiacchiere $ echo ${#var} 11 $ var="Chiacchiere da Bar" $ echo ${#var} 18

Ah, ora va meglio! Adesso sappiamo che la costruzione ${#variabile} restituisce la quantità di caratteri di variabile. Detto questo, proviamo a migliorare il nostro programma affinché scriva, a caratteri inversi e al centro dello schermo, la stringa inserita come parametro e riporti il cursore nella posizione in cui si trovava prima dell'esecuzione dello script.

$ cat hello.sh #!/bin/bash # Script stupido per testare # il comando tput (versione 2)

Colonne=`tput colonne` # Salvataggio numero colonne Righe=`tput righe` # Salvataggio numero righe Riga=$((Righe / 2)) # Qual'è la riga centrale dello schermo? Colonna=$(((Colonne - ${#1}) / 2)) # Centratura del messaggio sullo schermo tput sc # Salvataggio della posizione del cursore tput cup $Riga $Colonna # Posizionamento per la scrittura tput rev # Carattere inverso echo $1 tput sgr0 # Ripristino dello schermo alla modalità normale tput rc # Ripristino del cursore nella posizione originaria

Questo script è uguale al precedente, soltanto che abbiamo cambiato il valore fisso della prima versione (11), con ${#1}, in cui 1 è $1 cioè questo costrutto restituisce la lunghezza del primo parametro passato al programma. Se il parametro che voglio passare contiene spazi devo inserirlo tra virgolette, altrimenti $1 conterrà soltanto la prima parte. Per evitare questa seccatura basta sostituire $1 con $* che, come sappiamo, è l'insieme di tutti i parametri. Di conseguenza, quella riga diventa come segue:

    Colonna=$(((Colonne - ${#*}) / 2))  #   Centratura del messaggio sullo schermo

e la riga echo $1 diventa echo $*. Ma non dimenticare, quando eseguirai il programma, di passare come parametro la frase che vuoi centrare.

E adesso possiamo leggere i dati sullo schermo

Adesso impareremo tutto sulla lettura. Purtroppo non posso insegnarti a leggere le carte e la mano perché se sapessi farlo sarei ricco e mi troverei in un pub londinese a sorseggiare scotch e non in un bar del genere bere birra. Ma andiamo avanti.

L'ultima volta che ci siamo visti ti ho dato giusto un assaggio del comando read. Per iniziarne l'analisi dettagliata osserva:

$ read var1 var2 var3 Chiacchiere da Bar $ echo $var1 Chiacchiere $ echo $var2 da $ echo $var3 Bar $ read var1 var2 Papo de Botequim $ echo $var1 Chiacchiere $ echo $var2 da Bar

Come puoi vedere, read riceve una lista di elementi separati da spazi e immette ognuno di essi in una variabile. Se il numero delle variabili è minore di quello degli elementi, l'ultima variabile conterrà gli elementi restanti.

Ho percaso detto lista di elementi separati da spazi? Adesso che sai tutto sul comando $IFS (Inter Field Separator) che ti ho fatto conoscere quando abbiamo parlavato del comando for, ci credi davvero? Proviamo direttamente nel prompt:

$ oIFS="$IFS" $ IFS=: $ read var1 var2 var3 Chiacchiere da Bar $ echo $var1 Chiacchiere da Bar $ echo $var2

$ echo $var3

$ read var1 var2 var3 Chiacchiere:da:Bar $ echo $var1 Chiacchiere $ echo $var2 da $ echo $var3 Bar $ IFS="$oIFS"

Hai visto, ecco il trucco! Analogamente a for, il comando read legge una lista di elementi separati dai caratteri della variabile $IFS. Osserva come ciò può renderti la vita più facile:

$ grep julio /etc/passwd julio:x:500:544:Julio C. Neves - 7070:/home/julio:/bin/bash $ oIFS="$IFS" # Salvataggio di IFS $ IFS=: $ grep julio /etc/passwd | read lname lixo uid gid coment home shell $ echo -e "$lname\n$uid\n$gid\n$coment\n$home\n$shell" julio 500 544 Julio C. Neves - 7070 /home/julio /bin/bash $ IFS="$oIFS" # Ripristino di IFS

Come vedi, l'output di grep è stato redirezionato nel comando read che ha letto tutti i campi in un colpo solo. L'opzione -e di echo è stata usata affinché \n fosse interpretato come un salto di riga (a capo), e non come una lettera.

In Bash esistono diverse opzioni di read che possono semplificarti la vita. Osserva la tabella che segue:

Opzioni del do comando read in Bash
  -s     Tutto ciò che è digitato non compare sullo schermo  
  Opzione     Azione
  -p prompt     Scrive il prompt prima di effettuare la lettura  
  -n num     Legge fino carattere numero num  
  -t sec     Attende sec secondi per completare la lettura  

Adesso vediamo alcuni rapidi esempi per mostrare l'effetto di queste opzioni.

Per leggere il campo "Matricola":

$ echo -n "Matricola: "; read Mat # -n non provoca un salto di riga Matricola: 12345 $ echo $Mat 12345

Oppure, in modo più semplice, con l'opzione -p:

$ read -p "Matricola: " Mat Matricola: 12345 $ echo $Mat 12345

Per leggere un determinato numero di caratteri:

$ read -n5 -p"CEP: " Num ; read -n3 -p- Compl CEP: 12345-678$ $ echo $Num 12345 $ echo $Compl 678

In questo esempio abbiamo eseguito due read: uno per la prima parte di CEP e l'altra per il suo complemento, formattando così l'input dei dati. Il simbolo del dollaro ($) dopo l'ultimo numero digitato è presente perché read non ha l'_a capo_ di default come invece ha echo.

Per continuare la lettura fino alla fine di un periodo di tempo determinato (detto time out):

$ read -t2 -p "Digita il tuo nome completo: " Nom || echo 'Animo!% Digita il tuo nome completo: JAnimo! $ echo $Nom

$

Ovviamente è soltanto uno scherzo poiché avevo appena 3 secondi per digitare il mio nome completo e ho avuto solo il tempo di digitare una J (quello attaccato a Animo), ma è servito per mostrare due cose:

  1. Il comando che segue le due barre verticali (||) (l'”O” logico, ricordi?) sarà eseguito nel caso in cui la digitazione non sia conclusa nel tempo stabilito;
  2. La variabile Nom è rimasta vuota. Essa sarà valorizzata soltanto quando sarà digitato <ENTER>.

Per leggere un dato senza farlo comparire sullo schermo:

$ read -sp "Password: " Password: $ echo $REPLY segredo :)

Sfrutto un errore per mostrarti un trucco. Quando ho scritto la prima riga, ho dimenticato di inserire il nome della variabile che avrebbe ricevuto la password e me ne sarei accorto solo al momento di mostrarne il valore. Fortunatamente, la variabile $REPLY di Bash contiene l'ultima stringa letta e ne ho approfittato per tirarmi fuori dai guai. Verifica tu stesso ciò che ho appena fatto.

L'ultimo esempio serviva a mettere in evidenza che l'opzione -s fa in modo che ciò che è digitato non compaia sullo schermo. Come nell'esempio precedente, l'assenza dell'_a capo_ ha fatto sì che il prompt del comando ($) rimanesse nella stessa riga.

Bene, adesso che sappiamo leggere dallo schermo vediamo come si leggono i dati dai file.

Leggere i file?

Come ti ho già detto e come dovresti ricordare, while testa un comando ed esegue un blocco di istruzioni fino a quando il comando è eseguito con successo. In base a quanto appena detto, quando leggi un file sul quale hai permesso di lettura, read non sarà eseguito con successo nel momento in cui raggiungerà l'=EOF= (end of file). In tal modo possiamo leggere un file in due modi:

1 - Redirezionando l'input del file nel blocco di while:

    while read Riga
    do
        echo $Riga
    done < file

2 - Redirezionando l'output di cat in while:

    cat file |
    while read Riga 
    do
        echo $Riga
    done

Ognuno dei processi ha vantaggi e svantaggi:

Vantaggi del primo processo:

  • È più veloce;
  • Non ha bisogno di una sottoshell a cui appoggiarsi;

Svantaggi del primo processo:

  • All'interno di un grosso blocco di istruzioni il redirezionamento è poco visibile e ciò talvolta pregiudica la leggibilità del codice;

Vantaggi del secondo processo:

  • Poiché il nome del file precede il comando while, la leggibilità del codice è migliore.

Svantaggi del secondo processo:

  • La Pipe (|) richiama una sottoshell per essere interpretata, ciò rende il processo più lento, pesante e talvolta problematico (vedi l'esempio seguente).

Per mostrarti meglio ciò che è stato detto, osserva gli esempi che seguono:

$ cat leggipipe.sh #!/bin/bash # leggipipe.sh # Esempio di passaggio di dati da read attraverso una pipe.

Ultimo="(vuoto)" cat $0 | # Passaggio del file dello script ($0) a while while read Riga do Ultimo="$Riga" echo "-$Ultimo-" done echo "Fine, Ultimo=:$Ultimo:"

Vediamone l'esecuzione:

$ leggipipe.sh -#!/bin/bash- -# leggipipe.sh- -# Esempio di passaggio di dati da read attraverso una pipe.- -- -Ultimo="(vuoto)"- -cat $0 | # Passaggio del file dello script ($0) a while- -while read Riga- -do- -Ultimo="$RIga"- -echo "-$Ultimo-"- -done- -echo "Fine, Ultimo=:$Ultimo:"- Fine, Ultimo=:(ultimo):

Come puoi vedere, lo script elenca tutte le sue righe anteponendo e posponendo un segno meno (-) e alla fine mostra il contenuto della variabile $Ultimo. Osserva che il suo contenuto rimane (vuoto).

     - Non sarà forse perché la variabile è rimasta invariata?

     - Vero, e ciò può essere dimostrato poiché la riga echo "-$Ultimo-" elenca correttamente le righe.

     - E perché è successo tutto questo?

     - Perché, come ti ho detto, il blocco di istruzioni redirezionato dalla pipe (|) è eseguito in una sottoshell ed è là che le variabili sono aggiornate. Quando questa sottoshell termina, gli aggiornamenti delle variabili scompaiono in un pozzo senza fondo insieme a essa. Osserva bene adesso: farò una piccola modifica che redirezionerà il file verso l'input e tutto inizierà a funzionare nel migliore dei modi:

$ cat redirread.sh #!/bin/bash # redirread.sh # Esempio di passaggio di dati da read attraverso una pipe.

Ultimo="(vuoto)" while read Riga do Ultimo="$Riga" echo "-$Ultimo-" done < $0 # Passaggio del file dello script ($0) a while echo "Fine, Ultimo=:$Ultimo:"

Osservane la perfetta esecuzione:

$ redirread.sh -#!/bin/bash- -# redirread.sh- -# Esempio di passaggio di dati da read attraverso una pipe.- -- -Ultimo="(vuoto)"- -while read Riga- -do- -Ultimo="$Riga"- -echo "-$Ultimo-"- -done < $0 # Passaggio del file dello script ($0) a while- -echo "Fine, Ultimo=:$Ultimo:"- Fine, Ultimo=:echo "Fine, Ultimo=:$Ultimo:":

Bene, Amico della Rete Shell, per chiudere il discorso sul comando read manca soltanto una piccola ma importante dritta che ti mostrerò tramite un esempio pratico. Supponi di voler mostrare sullo schermo un file e di volere che, ogni dieci record, il listato si fermi affinché l'operatore possa leggere il contenuto dello schermo e che, infine, esso riprenda a scorrere (scroll) dopo la pressione di un tasto da parte dell'operatore. Per non sprecare inutilmente carta (di Linux Magazine), farò in modo che l'elenco appaia in orizzontale e il mio file (numeri) contiene soltanto 30 record con numeri sequenziali. Osserva:

$ seq 30 | xargs -i echo riga {} > numeri # Generazione del file con i numeri $ paste -sd':' numeri # Questo paste serve a mostrare il contenuto senza mettere un #record in ogni riga grazie all'uso dei due-punti come separatore riga 1: riga 2: riga 3: riga 4: riga 5: riga 6: riga 7: riga 8: riga 9: riga 10: riga 11: riga 12: riga 13: riga 14: riga 15: riga 16: riga 17: riga 18: riga 19: riga 20: riga 21: riga 22: riga 23: riga 24: riga 25: riga 26: riga 27: riga 28: riga 29: riga 30 $ cat 10perpag.sh #!/bin/bash # Programma di test per scrivere # 10 righe e fermarsi per la lettura # Versione 1

while read Num do let ContRig++ # Conteggio... echo -n "$Num " # -n per non saltare alcuna riga ((ContRig % 10)) > /dev/null || read done < numeri

Nel tentativo di scrivere un programma generico, impostiamo la variabile $ContRig (perché nella vita reale i record non solo soltanto numeri sequenziali) e fermiamo l'elenco per la lettura quando il resto della divisione per 10 è zero (e inviando l'output in /dev/null così che non appaia sullo schermo riempiendolo di caratteri inutili). Tuttavia, quando l'ho mandato in esecuzione è uscito fuori questo obbrobrio:

$ 10perpag.sh riga 1 riga 2 riga 3 riga 4 riga 5 riga 6 riga 7 riga 8 riga 9 riga 10 riga 12 riga 13 riga 14 riga 15 riga 16 riga 17 riga 18 riga 19 riga 20 riga 21 riga 23 riga 24 riga 25 riga 26 riga 27 riga 28 riga 29 riga 30 $

Osserva che manca la = riga 11= e il listato non si è interrotto in corrispondenza di read. È accaduto che l'intero input di loop è stato redirezionato dal file numeri e la lettura è stata eseguito dall'inizio del file, perdendo così la = riga 11= (e anche la = riga 22= e qualsiasi riga multipla di 11).

Ecco come dovrebbe essere per funzionare a dovere:

$ cat 10perpag.sh #!/bin/bash # Programma di test per scrivere # 10 righe e fermarsi per la lettura # Versione 2

while read Num do let ContRig++ # Conteggio... echo -n "$Num " # -n per non saltare alcuna riga ((ContRig % 10)) > /dev/null || read < /dev/tty done < numeri

Osserva che adesso l'input di read è redirezionato in /dev/tty, che non è altro che il terminale corrente, esplicitando così che la lettura avverrà partendo dallo schermo e non sarà costituita da numeri. È bene sottolineare che ciò non accade solo quando utilizziamo un redirezionamento dell'input: sarebbe accaduta la stessa cosa se avessimo utilizzato un redirezionamento tramite una pipe (|).

Osservane l'esecuzione:

$ 10perpag.sh riga 1 riga 2 riga 3 riga 4 riga 5 riga 6 riga 7 riga 8 riga 9 riga 10 riga 11 riga 12 riga 13 riga 14 riga 15 riga 16 riga 17 riga 18 riga 19 riga 20 riga 21 riga 22 riga 23 riga 24 riga 25 riga 26 riga 27 riga 28 riga 29 riga 30

Così è davvero buono, tuttavia manca ancora un piccolo aggiustamento per renderlo perfetto. Miglioriamo un po' l'esempio affinché tu possa riprodurlo e testarlo (ma prima di farlo aumenta il numero dei record di numeri o riduci la larghezza dello schermo affinché ci sia un'interruzione).

$ cat 10perpag.sh #!/bin/bash # Programma di test per scrivere # 10 righe e fermarsi per la lettura # Versione 3

clear while read Num do ((ContRig++)) # Conteggio... echo "$Num" ((ContRig % (`tput righe` - 3))) || { read -n1 -p"Premi un Tasto" < /dev/tty # per leggere un carattere qualsiasi clear # pulisce lo schermo dopo la lettura } done < numeri

La modifica più importante fatta nell'esempio riguarda l'interruzione di pagina, poiché essa viene eseguita ogni numero-di-righe-sullo-schermo (tput righe) diminuito (-) di 3, cioè, se lo schermo contiene 25 righe, saranno mostrati 22 record e l'elenco si fermerà per la lettura. Anche nel comando read è stata apportata una modifica con l'inserimento di un -n1 per leggere soltanto un carattere che non deve essere necessariamente <ENTER> e l'opzione -p per visualizzare il messaggio.

     - Bene amico mio, per oggi basta, credo che tu sia cotto...

     - No no, continua pure...

     - Se non lo sei tu lo sono io... Ma visto che sei così preso dalla Shell, ti lascio un esercizio semplice semplice per migliorare la tua CDteca. Riscrivi il programma per l'inserimento dei CD montando l'intero schermo con un unico echo e posizionando il cursore all'inizio di ogni campo per ricevere i valori digitati dall'operatore.

 
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding Wiki-SL? Send feedback