\

Aqui temos um livro livre e completo sobre Shell

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

Você está aqui: TWikiBar > BatePapos > TWikiBarChiacchiere002
Controles: EDITAR ANEXAR MAIS MAIS ALTERACOES IMPRIMIR - Última Atualização: [19 Feb 2013 - V.7]

Chiacchiere da Bar Parte II

    - Cameriere! Una birra alla spina e due "pasteis". Oggi il mio amico non beve perché finalmente farà la conoscenza di un vero sistema operativo e ha ancora tanto da imparare!
    - Allora, amico mio, hai capito tutto quello che ti ho spiegato finora?
    - Per capire ho capito, ma non ci vedo niente di pratico...
    - Calma, calma, quello di cui ti ho parlato finora è la base di ciò che arriverà dopo. Useremo questi strumenti per costruire programmi strutturati, cosa che la Shell permette di fare. Allora capirai perché in TV hanno trasmesso il programma "La Shell è il Limite".
    - Inizieremo parlando dei comandi della famiglia grep.
    - grep? Non conosco nessuna parola del genere in inglese...
    - CI credo, grep è l'acronimo di Global Regular Expression Print e usa espressioni regolari per la ricerca di stringhe di caratteri nell'input definito (c'è una leggenda sul perché questo comando ha quel nome: nell'editor di testi "ed", il nonno di "vim", il comando di ricerca usato era g/_espressione regolare_/p o, in inglese, g/_re_/p.). Dato che parliamo di espressioni regolari (o regexp), Aurélio Marinho Jargas dà tutte le indicazioni necessarie (tutorial inclusi) nella sua pagina web. Se vuoi imparare a programmare in Shell, Perl, Python, ... ti consiglio di leggerteli per non avere difficoltà più avanti.

Io mi tengo grep, per te un groppo in gola

Quello del groppo non è altro che uno scherzo! È solo un pretesto per ordinare qualche caipirinha. Tornando a bomba, poco fa ti ho detto che grep ricerca stringhe di caratteri nell'input definito, ma che cos'è l'"input definito"? Esistono diversi modi di definire l'input del comando grep. Vediamo:

Ricerca all'interno di un file:

$ grep rafael /etc/passwd

Ricerca all'interno di più file:

$ grep grep *.sh

Ricerca nell'output di un comando:

$ who | grep Pelegrino

Nel primo esempio, il più semplice, ho cercato la parola rafael all'interno del file /etc/passwd. Se avessi voluto cercarla come un nome utente, cioè solo all'inizio dei registri del file, avrei dovuto digitare:

$ grep '^rafael' /etc/passwd

Di certo mi chiederai a cosa servono l'accento circonflesso e gli apostrofi. Se tu avessi letto le indicazioni sulle espressioni regolari di cui ti ho parlato prima, sapresti che l'accento circonflesso (^) serve a limitare la ricerca all'inizio di ogni riga, mentre gli apostrofi (') fanno in modo che la Shell non interpreti l'accento circonflesso così che questi attraversi incolume il comando grep.

Guarda che figata! Il comando grep accetta come input l'output di un altro comando redirezionato da una pipe (ciò è molto comune nell'ambiente Shell e rende molto più rapida l'esecuzione di un comando: è come se l'output di un programma fosse salvato sul disco rigido e il secondo programma leggesse il file così generato). In questo modo, nel terzo esempio, il comando who ha generato l'elenco degli utenti "loggati" sulla tua stessa macchina (non dimenticarlo mai: Linux è multiutente) e grep è stato usato per verificare se Pelegrino lavorava o "si grattava".

La famiglia grep

Il comando grep è molto conosciuto, per cui è anche usato spessissimo. Ciò che molti non sanno è che esistono tre comandi nella famiglia grep, ossia:

  • grep
  • egrep
  • fgrep

Le principali differenze fra i tre sono le seguenti:

  • grep accetta come input anche espressioni regolari, ma se non le usiamo è meglio affidarci a fgrep perché è più veloce;
  • egrep (la "e" sta per extended, esteso) è molto potente se si usano le espressioni regolari. Poiché è il più lento della famiglia, è meglio usarlo solo quando è necessario elaborare un'espressione regolare non accettata da grep;
  • fgrep (la "f" sta per "fast", veloce, o per "file") è il più svelto della famiglia e, come dice il nome stesso, esegue il compito in modo molto rapido (a volte è circa il 30% più veloce di grep e il 50% più veloce di egrep), tuttavia non accetta come input le espressioni regolari nelle ricerche.

Penguin con un piatto di attenzione Quanto detto sulla velocità si applica soltanto alla famiglia di comandi grep di Unix. In Linux grep è sempre il più rapido visto che gli altri due (fgrep e egrep) sono scripts della Shell che richiamano il primo e, mi preme dirlo, questa soluzione non mi piace proprio per niente.
    - Adesso che conosci le differenze tra i membri della famiglia, dimmi: cosa pensi degli esempi che ti ho fatto prima delle spiegazioni?
    - Penso che fgrep avrebbe risolto il tuo problema più rapidamente di grep.
    - Perfetto! Vedo che stai attento e che hai capito tutto ciò che ti ho spiegato! Allora diamo un'occhiata a qualche altro esempio per rendere più chiare una volta per tutte le differenze d'uso tra i membri della famiglia.

Esempi

So che in un file c'è un testo che parla di=Linux= ma non sono sicuro se inizia con la L maiuscula o la l minuscula. Posso impostare la ricerca in due modi:

$ egrep (Linux | linux) file.txt

oppure

$ grep [Ll]inux file.txt

Nel primo caso, l'espressione regolare complessa "(Linux | linux)" fa uso delle parentesi per raggruppare le opzioni e della barra verticale (|) come di un "o" logico,cioè cerco Linux o linux.

Nel secondo, l'espressione regolare [Ll]inux significa: inizia per L o l ed è seguito da inux. Poiché questa espressione è più semplice, grep riesce a risolverla, per cui ritengo sia meglio usare la seconda forma visto che egrep renderebbe la ricerca più lenta.

Un altro esempio. Per avere l'elenco delle sottodirectory della directory corrente è sufficiente digitare:

$ ls -l | grep '^d' drwxr-xr-x 3 root root 4096 Dec 18 2000 doc drwxr-xr-x 11 root root 4096 Jul 13 18:58 freeciv drwxr-xr-x 3 root root 4096 Oct 17 2000 gimp drwxr-xr-x 3 root root 4096 Aug 8 2000 gnome drwxr-xr-x 2 root root 4096 Aug 8 2000 idl drwxrwxr-x 14 root root 4096 Jul 13 18:58 locale drwxrwxr-x 12 root root 4096 Jan 14 2000 lyx drwxrwxr-x 3 root root 4096 Jan 17 2000 pixmaps drwxr-xr-x 3 root root 4096 Jul 2 20:30 scribus drwxrwxr-x 3 root root 4096 Jan 17 2000 sounds drwxr-xr-x 3 root root 4096 Dec 18 2000 xine

Nell'esempio appena visto, l'accento circonflesso (^) serve per limitare la ricerca alla prima posizione dell'output esteso di ls. Gli apostrofi sono stati messi affinché la Shell non "vedesse" l'accento circonflesso (^).

Vediamo ancora un esempio. Sappiamo che le prime quattro posizioni possibili dell'output di ls -l per un file comune (un file comune, non una directory, un link o altro) devono essere:

Posizione  1ª   2ª   3ª   4ª 
      -
 Valori Possibili  - r w x
  - -   s (suid) 

Per scoprire tutti i file eseguibili presenti in una determinata directory dovrei digitare:

$ ls -la | egrep '^-..(x|s)' -rwxr-xr-x 1 root root 2875 Jun 18 19:38 rc -rwxr-xr-x 1 root root 857 Aug 9 22:03 rc.local -rwxr-xr-x 1 root root 18453 Jul 6 17:28 rc.sysinit

Il comando utilizza nuovamente l'accento circonflesso (^) per limitare la ricerca all'inizio di ogni riga, quindi le righe elencate saranno quelle che iniziano con un trattino (-) seguito da qualsiasi cosa (nelle espressioni regolari il punto significa qualsiasi cosa), ancora seguito da qualsiasi cosa , poi da x o da s.

Otterremmo lo stesso risultato se digitassimo:

$ ls -la | grep '^-..[xs]'

e avremmo reso la ricerca più veloce.

Costruiamo una "cdteca"

Iniziamo a scrivere qualche programma. Penso che costruire una banca dati musicale sia una figata per imparare (ed è anche utile in questi tempi di download di mp3 e "masterizzatori" di CD). Non dimenticare che, così come sviluppiamo dei programmi per catalogare i tuoi CD musicali, con piccoli adattamenti puoi fare la stessa cosa con i CD di software allegati a Linux Magazine e con tutti quelli che acquisti o masterizzi, rendendo disponibile questa banca dati a tutti quelli che lavorano con te (Linux è multiutente e così deve essere usato) e guadagnando dei punti nei confronti del tuo adorato capo.

    - Fermati un attimo! Da dove recupero i dati dei CD?
    - Per iniziare ti mostrerò come il programma può ottenere parametri da chi lo sta utilizzando e in breve ti insegnerò a leggere i dati dallo schermo o da un file.

Inserimento dei parametri

Il layout del file canzoni sarà il seguente:

     titolo del disco^interprete1~titolo della canzone1:..:interpreten~titolo della canzonen

cioè il titolo del disco sarà separato con un accento circonflesso (^) dal resto del record che è formato da diversi gruppi composti dall'interprete di ogni canzone del CD e dalle rispettive canzoni. Questi gruppi sono separati dai due-punti (:) e, al loro interno, l'interprete sarà separato con una tilde (~) dal titolo della canzone.

Voglio scrivere un programma chiamato insercanz che inserirà dei record nel file canzoni. Immetterò il contenuto di ogni disco come parametro nella chiamata del programma in questo modo:

$ insercanz "disco^interprete~canzone:interprete~canzone:..."

In questo modo il programma insercanz riceverà i dati di ogni disco come se fossero una variabile. L'unica differenza tra un parametro e una variabile è che i primi ricevono nomi numerici (nome numerico suona strano, vero? Significa che i nomi sono costituiti solo e soltanto da un numero), cioè $1, $2, $3, ..., $9. Prima di tutto facciamo una prova:

Esempi

$ cat test #!/bin/bash # Programma per testare l'immissione di parametri echo "1o. parm -> $1" echo "2o. parm -> $2" echo "3o. parm -> $3"

Proviamo a eseguirlo:

$ test inserimento parametri per prova bash: test: cannot execute

Ops! Ho dimenticato di renderlo eseguibile. Lo farò in modo che tutti possano eseguirlo e poi lo proverò:

$ chmod 755 test $ test inserimento parametri per prova 1o. parm -> inserimento 2o. parm -> parametri 3o. parm -> per

Nota che la parola prova, cioè il quarto parametro, non è stata elencata. Ciò è accaduto perché il programma elenca soltanto i primi tre parametri. Proviamo a eseguirlo in un altro modo:

$ test "inserimento parametri" per prova 1o. parm -> inserimento parametri 2o. parm -> per 3o. parm -> prova

Le virgolette hanno impedito alla Shell di vedere lo spazio tra le parole, le quali sono state considerate come un unico parametro.

Gruppi parametrici

Dato che parliamo di inserimento di parametri, permettimi di darti qualche dritta:

Significato delle Principali Variabili Riferite ai Parametri
$*   Contiene l'insieme di tutti i parametri (molto simile a $@)  
  Variabile     Significato  
$0   Contiene il nome del programma  
$#   Contiene il numero dei parametri immessi  

Esempi

Modifichiamo il programma test per usare le variabili che abbiamo appena visto. Digitiamo:

$ cat test #!/bin/bash # Programma per testare l'immissione di parametri (2a. Versione) echo Il programma $0 ha ricevuto $# parametri echo "1o. parm -> $1" echo "2o. parm -> $2" echo "3o. parm -> $3" echo Tutti in un solo \"colpo\": $*

Nota che prima delle virgolette ho usato una barra rovesciata per impedire alla Shell di interpretarla (se non usassi le barre rovesciate le virgolette non apparirebbero). Proviamo a eseguirlo:

$ test inserimento parametri per prova Il programma test ha ricevuto 4 parametri 1o. parm -> inserimento 2o. parm -> parametri 3o. parm -> per Tutti in un solo "colpo": inserimento parametri per prova

In base a quel che ti ho detto, i parametri ricevono un numero da 1 a 9: ciò non significa che non posso usare più di nove parametri, ma solo che posso indirizzarne al massimo nove. Vediamo:

Esempio:

$ cat test #!/bin/bash # Programma per testare l'immissione di parametri (3a. versione) echo Il programma $0 ha ricevuto $# parametri echo "11o. parm -> $11" shift echo "2o. parm -> $1" shift 2 echo "4o. Parm -> $1"

Eseguiamolo:

$ test inserimento parametri per prova Il programma test ha ricevuto 4 parametri: 11o. parm -> inserimento1 2o. parm -> parametri 4o. parm -> prova

In questo script ci sono due cose interessanti:

  1. Per far vedere che i nomi dei parametri variano da $1 a $9 ho eseguito un echo $11 e cos'è accaduto? La Shell lo ha interpretato come se fosse $1 seguito dal numero 1 e ha mostrato inserimento1;
  2. Il comando shift, la cui sintassi è shift n con n che può assumere un qualunque valore numerico (di default è 1 come nell'esempio), scarta i primi n parametri trasformando il parametro n+1 nel primo, cioè $1.

Bene, ora che di immissione di parametri ne sai più di me, torniamo alla nostra "cdteca" per realizzare lo script per inserire i CD nella mia banca dati chiamata canzoni. il programma è molto semplice (come, del resto, ogni cosa nella Shell) e lo commenterò per fartelo vedere:

Esempi

$ cat insercanz #!/bin/bash # Registra CD (versione 1) # echo $1 >> canzoni

Lo script è semplice e funzionale: mi limito ad aggiungere in coda al file canzoni il parametro ricevuto. Registriamo tre dischi per vedere se funziona (per non tirarla per le lunghe facciamo conto che ogni CD contenga solo due canzoni):

$ insercanz "disco 3^Artista5~Canzone5:Artista6~Canzone6" $ insercanz "disco 1^Artista1~Canzone1:Artista2~Canzone2" $ insercanz "disco 2^Artista3~Canzone3:Artista4~Canzone4"

Elenchiamo il contenuto di canzoni.

$ cat canzoni disco 3^Artista5~Canzone5:Artista6~Canzone6 disco 1^Artista1~Canzone1:Artista2~Canzone2 disco 2^Artista3~Canzone3:Artista4~Canzone4

Non è funzionale come pensavo... avrebbe potuto venire meglio. I dischi non sono in ordine e ciò rende difficile la ricerca. Modifichiamo il nostro script e vediamo come gira:

$ cat insercanz #!/bin/bash # Registra CD (versione 2) # echo $1 >> canzoni sort canzoni -o canzoni

Registriamo ancora un CD:

$ insercanz "disco 4^Artista7~Canzone7:Artista8~Canzone8"

Vediamo cosa succede al file canzoni:

$ cat canzoni disco 1^Artista1~Canzone1:Artista2~Canzone2 disco 2^Artista3~Canzone3:Artista4~Canzone4 disco 3^Artista5~Canzone5:Artista6~Canzone5 disco 4^Artista7~Canzone7:Artista8~Canzone8

Ho semplicemente inserito una riga che riordina il contenuto del file canzoni e redirige il risultato nel file stesso (a questo serve l'opzione -o) dopo l'inserimento di ogni disco.

Evvai! Ora è davvero a posto e quasi funzionale. Ma sta' attento, non disperare! Questa non è la versione finale. Il programma sarà migliore e più usabile nella nuova versione che scriveremo dopo aver capito come acquisire i dati dallo schermo e a formattare l'input.

Esempi

Fare un elenco con il comando cat non va bene, perciò scriveremo un programma chiamato listcanz per ottenere l'elenco delle canzoni di un disco il cui titolo sarà immesso come parametro:

$ cat listcanz #!/bin/bash # Sfoglia CD (versione 1) # grep $1 canzoni

Eseguiamolo cercando il disco 2. Come abbiamo visto prima, per inserire la stringa disco 2 è necessario fare in modo che la Shell non la interpreti come due parametri. Digitiamo:

$ listcanz "disco 2" grep: can't open 2 canzoni: disco 1^Artista1~Canzone1:Artista2~Canzone2 canzoni: disco 2^Artista3~Canzone3:Artista4~Canzone4 canzoni: disco 3^Artista5~Canzone5:Artista6~Canzone6 canzoni: disco 4^Artista7~Canzone7:Artista8~Canzone8

Che schifezza! E l'errore dov'è? Mi sono anche assicurato di aver messo il parametro tra virgolette affinché la Shell non lo spezzasse in due!

Certo, ma guarda come viene eseguito grep:

     grep $1 canzoni

Anche mettendo disco 2 tra virgolette per farlo vedere come un unico parametro, quando $1 passa dalla Shell al comando grep si è ormai trasformato in due argomenti. In questo modo, il contenuto finale della riga che il comando grep ha eseguito è:

     grep disco 2 canzoni

Poiché la sintassi di grep è:

    =grep [file1, file2, ..., filen]=

grep ha capito di dover cercare la stringa di caratteri disco nei file 2 e canzoni. Dato che non esiste alcun file chiamato 2, ha generato l'errore e, poiché ha trovato la parola disco in tutti i record di canzoni, li ha elencati tutti.

Pinguim com placa de dica (em inglês) Se la stringa di caratteri da passare al comando comando grep contiene spazi o TAB anche all'interno di variabili, mettila sempre tra virgolette per evitare che le parole dopo il primo spazio o TAB siano interpretate come nomi di file.

D'altro canto, nella ricerca è meglio ignorare maiuscole e minuscole. Risolveremmo entrambi i problemi se il programma fosse così:

$ cat listcanz #!/bin/bash # Sfoglia CD (versione 2) # grep -i "$1" canzoni

In questo caso, abbiamo usato l'opzione -i di grep che, come già abbiamo visto, serve a ignorare maiuscole e minuscole, e abbiamo messo $1 tra virgolette affinché grep continuasse a vedere la stringa di caratteri risultante dall'espansione della riga effettuata dalla Shell come un unico argomento di ricerca.

$ listcanz "disco 2" disco2^Artista3~Canzone3:Artista4~Canzone4

Nota che grep localizza la stringa da cercare in qualunque parte del record, per cui possiamo fare ricerche per disco, canzone, interprete o anche per una parte di essi. Quando apprenderemo i comandi condizionali realizzeremo una nuova versione di listcanz che permetterà di specificare in quale campo fare la ricerca.

A questo punto mi dirai:

    - Cavolo, ma è una vera noia dover mettere l'argomento della ricerca tra virgolette quando cerchiamo il titolo del disco. Non è una cosa simpatica!
    - Hai ragione, e proprio per questo ti farò vedere un altro modo di fare quello che chiedi:

$ cat listcanz #!/bin/bash # Sfoglia CD (versione 3) # grep -i "$*" canzoni $ listcanz disco 2 disco 2^Artista3~Canzone3:Artista4~Canzone4

In questo modo, $*, che significa tutti i parametri, sarà sostituito dalla stringa disco 2 (vedi l'esempio precedente) proprio come volevi tu.

Non dimenticare che il problema della Shell non è se puoi o non puoi fare una certa cosa. Il problema è decidere qual'è il modo migliore di farla, visto che per svolgere un compito qualsiasi la quantità di opzioni è enorme.

Ah! Un bel giorno d'estate te ne sei andato in spiaggia, hai dimenticato il CD in macchina, il sole a 40 gradi ha rovinato il tuo CD e ora hai bisogno di uno strumento per rimuoverlo dalla banca dati? Nessun problema, buttiamo giù uno script chiamato elimcanz per cancellare il CD.

Prima di farlo, voglio mostrarti un'opzione piuttosto utile della famiglia di comandi grep. È l'opzione -v, la quale elenca tutti i record dell'input, tranne quelli localizzati dal programma. Vediamo:

Esempi

$ grep -v "disco 2" canzoni disco 1^Artista1~Canzone1:Artista2~Canzone2 disco 3^Artista5~Canzone5:Artista6~Canzone6 disco 4^Artista7~Canzone:Artista8~Canzone8

Secondo quel che ti ho detto prima, il grep dell'esempio ha elencato tutti i record di canzoni tranne quelli relativi a disco 2 perché riguardava l'argomento del comando. Adesso siamo pronti a realizzare lo script per rimuovere il CD rovinato dalla tua "CDteca". Eccolo qua:

$ cat elimcanz #!/bin/bash # Elimina CD (versione 1) # grep -v "$1" canzoni > /tmp/canz$$ mv -f /tmp/canz$$ canzoni

Nella prima riga ho inviato in /tmp/canz$$ il file canzoni, senza i record relativi alla ricerca fatta dal comando grep. Poi ho spostato (cioè ho rinominato) /tmp/canz$$ sul vecchio canzoni.

Ho usato il file /tmp/canz$$ come file di lavoro perché, come ho detto nella prima parte, il $$ conttiene il PID (Process Identification o identificativo del processo) e in questo modo chiunque modifichi il file canzoni lo farà su un file diverso, evitando possibili conflitti nell'uso.

    - Beh, i programmi che abbiamo scritto finora sono piuttosto rudimentali perché ci mancano alcuni strumenti. Ma va bene così. Ora, mentre io mi scolo un'altra birretta, tu te ne vai a casa a fare pratica con gli esempi perché in breve svilupperemo un bel programmino per tenere in ordine i tuoi CD.
    - Quando ci rivedremo ti insegnerò come funzionano i comandi condizionali e perfezioneremo quegli script.
    - Per oggi basta! Ho già chiacchierato troppo e ho bisogno di bagnarmi l'ugola, ho la gola secca!
    - Cameriere! Una birra alla spina senza schiuma!


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.