\

Aqui temos um livro livre e completo sobre Shell

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

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

Chiacchiere da Bar Parte III

    - Cameriere, due birre alla spina per favore, oggi ho un sacco di cose da spiegare.

Lavorare con le stringhe

Non pensare che il titolo voglia dire che ti insegnerò a fare il calzolaio! Mi riferisco alle stringhe di caratteri!

Il Comando cut

Per prima cosa, ti voglio illustrare in modo pratico un'istruzione semplice da usare e molto utile: il comando cut. Questa istruzione è impiegata per tagliare una parte definita di un file e si usa in due modi diversi.

Il comando cut con l'opzione -c

Con questa opzione, il comando ha la seguente sintassi:

     cut -c PosIni-PosFin [arquivo]

Dove:

     PosIni = Posizione iniziale
     PosFin = Posizione finale

$ cat numeri 1234567890 0987654321 1234554321 9876556789 $ cut -c1-5 numeri 12345 09876 12345 98765 $ cut -c-6 numeri 123456 098765 123455 987655 $ cut -c4- numeri 4567890 7654321 4554321 6556789 $ cut -c1,3,5,7,9 numeri 13579 08642 13542 97568 $ cut -c -3,5,8- numeri 1235890 0986321 1235321 9875789

Come si vede, ci sono quattro sintassi distinte: nella prima (-c 1-5), ho specificato un intervallo, nella seconda (-c -6), ho specificato tutto fino a una certa posizione, nella terza (-c 4-) da una determinata posizione in poi e nella quarta (-c 1,3,5,7,9), determinate posizioni. L'ultima (-c -3,5,8-) è soltanto per farti vedere che possiamo anche mescolarle.

Il comando cut con l'opzione -f

Non pensare che sia tutto qui! Come avrai capito, questa forma di cut è utile quanto si maneggiano file con campi a grandezza fissa, ma nella realtà si hanno di solito file con campi a grandezza variabile ognuno dei quali termina con un delimitatore. Diamo un'occhiata al file canzoni che abbiamo iniziato a metter in piedi l'ultima volta che ci siamo visti qui al bar.

$ 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

Quindi, ricapitolando, il "leiaut" è 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 registro, il quale è costituito da diversi gruppi composti dall'interprete di ogni canzone del CD e dalla canzone interpretata. Questi gruppi sono separati l'uno dall'altro per mezzi di due-punti (:) e, al loro interno, il nome dell'interprete sarà separato dal titolo della canzone tramite una tilde (~).

Detto questo, per selezionare i dati relativi a tutte le seconde canzoni del file canzoni, dobbiamo digitare:

$ cut -f2 -d: canzoni Artista2~Canzone2 Artista4~Canzone4 Artista6~Canzone5 Artista8~Canzone8

Cioè, ritagliamo il secondo campo (-f dall'inglese field) delimitato (-d) dai due-punti (:). Ma, se vogliamo solamente gli interpreti, dobbiamo digitare:

$ cut -f2 -d: canzoni | cut -f1 -d~ Artista2 Artista4 Artista6 Artista8

Per capire meglio, prendiamo la prima riga di canzoni:

$ head -1 canzoni disco 1^Artista1~Canzone1:Artista2~Canzone2

Adesso osserva ciò che abbiamo fatto:

Delimitatore del primo cut (:)

     disco 1^Artista1~Canzone1:Artista2~Canzone2

In questo modo, nel primo cut, il primo campo del delimitatore (-d) due-punti (:) è disco 1^Artista1~Canzone1 e il secondo, che è quello che ci interessa, è Artista2~Canzone2.

Vediamo allora cosa succede con il secondo cut:

Nuovo delimitatore (~)

     Artista2~Canzone2

Adesso, il primo campo del delimitatore (-d) tilde (~), che è quello che ci interessa, è Artista2, mentre il secondo è Canzone2.

Se applicheremo al resto del file il ragionamento fatto per la prima riga, arriveremo alla risposta data in precedenza.

Se c'è cut c'è paste

Come potevamo aspettarci, il comando paste serve per incollare, solo che nella Shell si incollano file. Per iniziare a capire come funziona, digitiamo:

    paste file1 file2

In questo modo, il comando invierà allo Standard Output (stdout) tutti i record di file1 accanto a quelli corrispondenti di file2 e nel caso in cui non sia specificato alcun delimitatore, esso userà per default <TAB>.

Il comando paste non è molto usato perché la sua sintassi è poco conosciuta. Proviamo a giocare con 2 file generati in questo modo:

$ seq 10 > interi $ seq 2 2 10 > pari

Per vedere il contenuto dei file generati, usiamo paste nella forma semplice che abbiamo illustrato prima:

$ paste interi pari 1 2 2 4 3 6 4 8 5 10 6 7 8 9 10

Chi sta in piedi prima o poi si stende

Adesso trasformiamo la colonna dei pari in una riga:

$ paste -s pari 2 4 6 8 10

Utilizzare i separatori

Come abbiamo già detto, il separatore di default di paste è <TAB>, ma possiamo cambiarlo con l'opzione -d. Di conseguenza, per calcolare la somma del contenuto di pari digiteremo:

$ paste -s -d'+' pari # ma potrebbe anche essere -sd'+' 2+4+6+8+10

poi passeremo questa riga alla calcolatrice (bc), per cui otterremo:

$ paste -sd'+' pari | bc 30

Data questa premessa, per calcolare il fattoriale del numero contenuto in $Num, è sufficiente digitare:

$ seq $Num | paste -sd'*' | bc

Con il comando paste puoi anche generare formattazioni particolari come quella che segue:

$ ls | paste -s -d'\t\t\n' file1 file2 file3 file4 file5 file6

Vediamo cos'è accaduto: è stato richiesto al comando paste di trasformare le righe in colonne (tramite l'opzione -s) e i suoi separatori (Ha ha! Il comando ne accetta più di uno, ma soltanto uno alla volta dopo ogni colonna generata dal comando stesso) sono <TAB>, poi ancora <TAB> e infine <ENTER>: ciò ha prodotto la distribuzione dei dati in 3 colonne.

Ora che hai afferrato il concetto, vediamo come fare la stessa cosa in modo più semplice e meno confuso usando lo stesso comando ma con la seguente sintassi:

$ ls | paste - - - file1 file2 file3 file4 file5 file6

Questo risultato si ottiene perché, inserendo i segni meno (-) invece di specificare i file, il comando paste li sostituisce con lo Standard Output o lo Standard Input a seconda del caso. Nell'esempio precedente, i dati sono stati inviati allo Standard Output (stdout) poiché la pipe (|) redireziona l'output di ls allo Standard Input (stdin) di paste, ma da' un'occhiata all'esempio seguente:

$ cat file1 predisposizione privilegiato professionale $ cat file2 disporre mario motore $ cut -c-3 file1 | paste -d "" - file2 predisporre primario promotore

In questo caso, cut ha restituito le prime tre lettere di ogni record di file1 e paste è stato impostato senza separatore (-d"") per ricevere lo Standard Input (redirezionato dalla pipe) al posto del trattino (-) generando un output insieme a file2.

Il Comando tr

Un altro comando molto interessante è tr, il quale serve per sostituire, comprimere o rimuovere caratteri. La sua sintassi segue lo schema sottoindicato:

    tr [opzioni] stringa1 [stringa2]

Il comando tr copia il testo dello Standard Input (stdin), sostituisce le occorrenze dei caratteri di stringa1 con i corrispondenti di stringa2 o sostituisce occorrenze multiple di caratteri di stringa1 con un solo carattere, oppure rimuove caratteri da stringa1.

Le principali opzioni del comando sono:

Principali Opzioni del comando tr
-d   Rimuove i caratteri di stringa1  
  Opzione     Significato  
-s   Comprime n occorrenze di stringa1 in una soltanto  

Sostituire caratteri con tr

Per prima cosa ti farò un esempio un po' tonto:

$ echo tonto | tr o a tanta

Non ho fatto altro che sostituire tutte le occorrenze della lettera o con la lettera a.

Poniamo che in un determinato punto del mio script chieda all'operatore di digitare s o n (sì o no) e che la risposta sia immagazzinata nella variabile $Risp. Il contenuto di $Risp può essere maiuscolo o minuscolo, per cui dovrei fare diversi test per sapere se la risposta è S, s, N oppure n. La cosa migliore da fare è digitare:

$ Risp=$(echo $Risp | tr SN sn)

A questo punto ho la certezza che il contenuto di $Risp è s oppure n.

Se il mio file FileEnt è tutto in maiuscolo e voglio trasformarlo in minuscolo digiterò

$ tr A-Z a-z < FileEnt > /tmp/$$ $ mv -f /tmp/$$ FileEnt

Osserva che ho usato la notazione A-Z per non scrivere ABCD...YZ. Un altro tipo di notazione che può essere usata sono le cosiddette sequenze di escape (in inglese escape sequences), riconosciute anche da altri comandi e nel linguaggio C. Vedremo il loro significato più avanti:

Escape Sequences
\\   Una barra rovesciata   \0134
  Sequenza     Significato     Ottale  
\t   Tabulazione   \011
\n   Nuova riga   \012
\v   Tabulazione Verticale   \013
\f   Nuova Pagina   \014
\r   Inizio della riga <^M>   \015

Rimuovere caratteri con tr

Lascia che ti racconti un aneddoto: un alunno che ce l'aveva con me decise di complicarmi la vita e, in un esercizio pratico con tanto di voto che avevo dato da fare al computer, mi consegnò uno script con tutti i comandi separati da un punto-e-virgola (ricordi che ti ho detto che il punto-e-virgola serve per separare diversi comandi in una stessa riga?).

Eccoti un esempio facile facile di una roba del genere:

$ cat confuso echo leggi Programmazione Shell Linux di Julio Cezar Neves > libro;cat libro;pwd;ls;rm -f robaccia 2>/dev/null;cd ~

Io eseguivo il programma e quello funzionava così:

$ confuso leggi Programmazione Shell Linux di Julio Cezar Neves /home/jneves/LM confuso livro elimcanz canzoni insercanz listcanz numeri

Ma il voto è una cosa seria quindi, per capire ciò che l'alunno aveva fatto, lo chiamai e davanti ai suoi occhi digitai il seguente comando:

$ tr ";" "\n" < confuso echo leggi Programmazione Shell Linux do Julio Cezar Neves pwd ls rm -f robaccia 2>/dev/null cd ~

Il tizio ci restò male perché in 2 o 3 secondi avevo rovinato il bello scherzo per mettere su il quale aveva sprecato tanto tempo.

Ma attento! Se avessi avuto una macchina con Unix avrei dovuto digitare:

$ tr ";" "\012" < confuso

Espressioni con tr

Vediamo adesso la differenza tra due comandi date: ciò che ho fatto oggi e ciò che ho fatto due settimane fa:

$ date # Oggi Sun Sep 19 14:59:54 2004 $ date # Due settimane fa Sun Sep 5 10:12:33 2004

Per incollare l'ora dovrei digitare:

$ date | cut -f 4 -d ' ' 14:59:54

Ma due settimane fa avrei ottenuto questo:

$ date | cut -f 4 -d ' ' 5

Osserva bene il perché:

$ date # Due settimane fa Sun Sep 5 10:12:33 2004

Come puoi notare, ci sono 2 spazi prima di 5 (giorno), cosa che rovina tutto perché il terzo pezzo è vuoto e il quarto è il giorno (5). L'ideale sarebbe comprimere gli spazi in sequenza in uno soltanto per trattare le due stringhe risultanti dal comando date allo stesso modo, e ciò si ottiene digitando:

$ date | tr -s " " Sun Sep 5 10:12:33 2004

Come puoi vedere non ci sono più i due spazi, per cui posso abbreviare:

$ date | tr -s " " | cut -f 4 -d " " 10:12:33

Guarda come la Shell risolve i problemi. Da' un'occhiata a questo file scaricato da una macchina che ha quel sistema operativo che si becca i virus:

$ cat -ve FileDiDOS.txt Questo file^M$ è stato generato da^M$ DOS/Rwin ed è stato^M$ scaricato da un^M$ ftp mal fatto.^M$

Adesso voglio darti due dritte:

Pinguim com placa de dica (em inglês) Tip #1 - L'opzione -v di cat mostra i caratteri di controllo invisibili con la notazione ^L, nella quale ^ è il tasto control e L è la rispettiva lettera. L'opzione -e mostra la fine della riga come un dollaro ($).

Pinguim com placa de dica (em inglês) Tip #2 - Ciò accade perché nel DOS (ou rwin) la fine dei record è costituita da un carriage-return (\r) e un line-feed (\n). In Linux la fine del record ha soltanto il line-feed.

Adesso facciamo un po' di pulizia dentro al file.

$ tr -d '\r' < FileDiDOS.txt > /tmp/$$ $ mv -f /tmp/$$ FileDiDOS.txt

e vediamo cosa succede:

$ cat -ve FileDiDOS.txt Questo file$ è stato generato da$ DOS/Rwin ed è stato$ scaricato da un$ ftp mal fatto.$

L'opzione -d di tr rimuove il carattere specificato dall'intero file. In questo modo ho rimosso i caratteri indesiderati salvando il contenuto in un file di lavoro e ho poi rinominato lo stesso con il suo nome originale.

Nota: In Unix avrei dovuto digitare:

$ tr -d '\015' < FileDiDOS.txt > /tmp/$$

Penguin con un piatto di attenzione Ciò si è verificato perché l' ftp è stato fatto in modo binario (o image), cioè senza interpretazione del testo. Se prima della trasmissione del file fosse stata stabilita l'opzione ascii dell' ftp, ciò non si sarebbe verificato.

     - Dopo queste dritte la Shell inizia quasi a piacermi, ma ci sono ancora un sacco di cose che non so fare.

     - È vero, finora non ti ho detto quasi niente sulla programmazione Shell e hai ancora molto da imparare, ma ciò che sai è sufficiente per risolvere molti problemi una volta acquisito il “modo Shell di pensare”. Saresti capace di buttare giù uno script per dirmi chi sono le persone “loggate” da più di un giorno sul tuo server?

     - Certo che no! Per farlo dovrei conoscere i comandi condizionali che ancora non mi hai spiegato come funzionano.

    - Lasciami provare a cambiare il tuo modo di ragionare e di avvicinarlo al “modo Shell di pensare”, ma prima è meglio farsi una bella birra alla spina... Chico, un altro giro...

     - Adesso che mi sono bagnato l'ugola vediamo di risolvere il problema che ti ho proposto. Presta attenzione a come funziona il comando who:

$ who jneves pts/1 Sep 18 13:40 rtorres pts/0 Sep 20 07:01 rlegaria pts/1 Sep 20 08:19 lcarlos pts/3 Sep 20 10:01

Dai uno sguardo anche a date:

$ date Mon Sep 20 10:47:19 BRT 2004

Nota che il mese e l'anno si presentano nello stesso formato in entrambi i comandi.

Pinguim com placa de dica (em inglês) A volte il comando restituisce un output in italiano, altre volte in inglese. Quando ciò si verifica puoi usare questo trucco:
$ date Mon Sep 20 10:47:19 BRT 2004 $ LANG=it_IT date Lun Set 20 10:47:19 BRT 2004
In questo modo l'output del comando date è in italiano.

Detto questo, se non trovo la data di oggi in nessun record di who, ciò significa che la persona è "loggata" da più di un giorno poiché non può essersi “loggato” domani... Memorizziamo la parte che ci interessa della data di oggi per cercarla nell'output di who:

$ Data=$(date | cut -c 5-10)

Ho usato il costrutto $(...) per rendere prioritaria l'esecuzione dei comandi prima di attribuire il loro output alla variabile $Data. Vediamo se funziona:

$ echo $Data Sep 20

Una meraviglia! Adesso non ci resta che cercare i record del comando who che non hanno quella data.

     - Ah! Credo di aver capito! Hai detto cercare e mi è venuto in mente il comando grep, giusto?

     - Giustissimo! Solo che devo usare grep con l'opzione che mi permette di elencare i record nei quali non ha trovato la stringa. Ricordi di quale opzione si tratta?

     - Certo, è l'opzione -v...

     - Proprio lei! Allora stai imparando! Vediamo:

$ who | grep -v "$Data" jneves pts/1 Sep 18 13:40

     - E se volessi un tocco estetico digiterei:

$ who | grep -v "$Data" | cut -f1 -d ' ' jneves

     - Visto? Non abbiamo dovuto fare uso di alcun comando condizionale perché il più utilizzato tra essi, il famoso if, non testa condizioni, ma istruzioni, come adesso ti spiegherò.

Comandi Condizionali

Veja as linhas de comando a seguir:

$ ls canzoni canzoni $ echo $? 0 $ ls FileInesistente ls: FileInesistente: No such file or directory $ echo $? 1 $ who | grep jneves jneves pts/1 Sep 18 13:40 (10.2.4.144) $ echo $? 0 $ who | grep juliana $ echo $? 1

     - Cos'è quel $? lì? Dato che inizia con il dollaro ($) direi che è una variabile, giusto?

     - Sì, è una variabile che contiene il codice di ritorno dell'ultima istruzione eseguita. Ti posso assicurare che, se l'istruzione è stata eseguita bene, $? avrà valore zero, altrimenti il suo valore sarà diverso da zero.

Il Comando if

Ciò che il nostro comando condizionale if fa è testare la variabile $?. Vediamone la sintassi:

    if cmd
    then
        cmd1
        cmd2
        cmdn
    else
        cmd3
        cmd4
        cmdm
    fi

cioè: se il comando cmd è stato eseguito con successo, i comandi del blocco di then (cmd1, cmd2 e cmdn) saranno eseguiti, altrimenti i comandi da eseguire saranno quelli del blocco opzionale di else (cmd3, cmd4 e cmdm): il tutto termina con fi.

Vediamo come tutto ciò funziona utilizando un piccolo script che serve a inserire gli utenti in /etc/passwd:

$ cat inserutente #!/bin/bash # Versione 1 if grep ^$1 /etc/passwd then echo L'utente \'$1\' esiste già else if useradd $1 then echo L'utente \'$1\' è stato inserito in /etc/passwd else echo "Problemi di inserimento. Sei root?" fi fi

Osserva che if testa direttamente il comando grep ed è questo il suo scopo. Se if ha successo, cioè se l'utente (il cui nome è in $1) è presente in /etc/passwd, saranno eseguiti i comandi del blocco di then (nel nostro esempio soltanto echo), altrimenti saranno le istruzioni del blocco di else a essere eseguite, per cui un nuovo if testerà se il comando useradd è stato eseguito come si deve e quindi genererà il record dell'utente in /etc/passwd oppure presenterà un messaggio di errore.
Vediamone l'esecuzione, inizialmente inserendo un utente già registrato:

$ inserutente jneves jneves:x:54002:1001:Julio Neves:/home/jneves:/bin/bash L'utente 'jneves' è già presente

Come abbiamo già visto in precedenza (ma è sempre meglio ripetere affinché tu stia attento), nell'esempio è spuntata fuori una riga indesiderata che altro non è che l'output del comando grep. Per evitare che questo accada, dobbiamo redirigere l'output di questa istruzione in /dev/null:

$ cat inserutente #!/bin/bash # Versione 2 if grep ^$1 /etc/passwd > /dev/null # ou: if grep -q ^$1 /etc/passwd then echo L'utente \'$1\' esiste già else if useradd $1 then echo L'utente \'$1\' è stato inserito in /etc/passwd else echo "Problemi di inserimento. Sei root?" fi fi

Eseguiamolo adesso come utente normale (non root):

$ inserutente PincoPallino ./inserutente[6]: useradd: not found Problemi di inserimento. Sei root?

Cavoli, quell'errore non doveva uscire! Per evitarlo dobbiamo redirigere in /dev/null anche lo Standard Error (strerr, ricordi?) di useradd:

$ cat inserutente #!/bin/bash # Versione 3 if grep ^$1 /etc/passwd > /dev/null then echo L'utente \'$1\' esiste già else if useradd $1 2> /dev/null then echo L'utente \'$1\' è stato inserito in /etc/passwd else echo "Problemi di inserimento. Sei root?" fi fi

Vediamo come si comporta una volta apportate queste modifiche e aver eseguito su – (sono diventato root):

$ inserutente botelho L'utente 'botelho' è stato inserito in /etc/passwd

E ancora:

$ inserutente botelho L'utente 'botelho' esiste già

Ricordi che ti ho detto, tra una chiacchiera e una birra, che i nostri programmi sarebbero diventati più raffinati? Vediamo come possiamo migliorare il nostro programma per registrare le canzoni:

$ cat insercanz #!/bin/bash # Registra CDs (versione 3) # if grep "^$1$" canzoni > /dev/null then echo Questo disco è già stato registrato else echo $1 >> canzoni sort canzoni -o canzoni fi

Come puoi vedere, si tratta di una piccola variazione della versione precedente: adesso, prima di inserire un record (che nella versione precedente poteva essere duplicato), controlliamo se il record inizia (^) e finisce ($) allo stesso modo del parametro immesso ($1). L'accento circonflesso (^) all'inizio della stringa e il dollaro ($) alla fine servono a testare se il parametro immesso (il disco e i suoi dati) è esattamente uguale a qualche altro record già inserito e non soltanto uguale a una parte di qualche record.

Proviamo a eseguirlo inserendo un disco già registrato:

$ insercanz "disco 4^Artista7~Canzone7:Artista8~Canzone8" Questo disco è già stato registrato

E adesso con uno non registrato:

$ insercanz "disco 5^Artista9~Canzone9:Artista10~Canzone10" $ 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 disco 5^Artista9~Canzone9:Artista10~Canzone10

     - Come vedi, il programma funziona un po' meglio ma non è ancora pronto. Man mano che ti insegnerò a programmare in shell, la nostra CDteca funzionerà sempre meglio.

     - Ho capito tutto ciò che mi hai spiegato, ma non so ancora come usare if per testare condizioni, cioè l'uso normale del comando.

     - Per fare quello che dici esiste il comando test, è lui a testare le condizioni. Il comando if testa il comando test. Ma il discorso è un po' confuso e, dato che ho già parlato abbastanza, ho bisogno di qualche birretta per bagnarmi la gola. Per oggi ci fermiamo qui, la prossima volta ti spiego per bene l'uso di test e di diverse altre sintassi di if.

     - Sono d'accordo! Credo anch'io che sia meglio fermarci, anche perché mi sento un po' rintronato e così ho tempo per fare pratica su tutta quella roba di cui mi hai parlato oggi.

     - Per fare il punto di quel che hai imparato prova a buttare giù un piccolo script per determinare se un certo utente, che sarà inserito come parametro, è loggato (arghh!) oppure no.

     - Chico, altre due birre alla spina per favore...


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.