\

Aqui temos um livro livre e completo sobre Shell

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

Controles: EDITAR ANEXAR MAIS MAIS ALTERACOES IMPRIMIR - Última Atualização: [05 May 2014 - V.2]

Stuzzichini per l'Aperitivo

Shell in bocconi piccoli e gustosi


In continuo aggiornamento


Queste pagine non sono mai state pubblicata su Linux Magazine, sebbene ricadano nell'ambito di Chiacchiere da Bar. Si tratta di articoli scritti per essere diffusi in altro modo, suggerimenti utili letti in rete (e in questo caso con le dovute attribuzioni), contributi da parte degli appassionati di Software Livre, persone sempre pronte ad aiutare, e dell'imperdibile Lista de Shell Script

Passare parametri con xargs

Esiste un comando la cui funzione fondamentale è generare elenchi di parametri da dare in pasto ad altri programmi o istruzioni. Questo comando è xargs e deve essere usato nel modo seguente:

    xargs [comando [argomento iniziale]]

Xargs combina argomento iniziale con gli argomenti ricevuti dallo standard output in modo da eseguire il comando specificato una o più volte.

Esempio:

Cerchiamo, all'interno dei file presenti in una directory, una stringa di caratteri tramite il comando find con l'opzione -type f per esaminare soltanto i file normali e scartare le directory, i file speciali, i collegamenti e quant'altro, e rendiamola più generica assegnandole il nome della directory iniziale e la stringa da cercare come parametro.:

$ cat grepr # # Grep ricorsivo # Esamina la stringa di caratteri definita in $2 partendo dalla directory $1 # find $1 -type f -print|xargs grep -l "$2"

Eseguendo questo script ricerchiamo, a partire dalla directory definita nella variabile $1, tutti i file che contengono la stringa definita nella variabile $2.

Avremmo potuto fare la stessa cosa se la riga del programma fosse stata la seguente:

    find $1 -type f -exec grep -l "$2" {} \;

Il primo processo ha due gravi svantaggi rispetto al secondo:

  • Il primo è ben visibile: il tempo di esecuzione è assai maggiore dell'altro perché grep ispezionerà ogni singolo file passatogli da find, mentre con xargs sarà passato tutto o, nella migliore ipotesi, la maggior parte dell'elenco dei file generato da find;

  • In base al numero dei file trovati da find potremmo ottenere il fatidico e famoso messaggio di errore Too many arguments che indica un sovraccarico nell'esecuzione di grep. Come abbiamo detto in precedenza, se usiamo xargs esso passerà a grep il numero più alto possibile di parametri, tale da non generare questo errore, ed eventualmente eseguirà grep più volte.

Pinguim com placa de atenção A tutti gli utenti di linux che utilizzano ls con tutti i colori dell'arcobaleno: negli esempi che riguarderanno questa istruzione dovete usare l'opzione --color=none altrimenti i risultati saranno verosimilmente diversi da quelli attesi.

Vediamo adesso un esempio che più o meno è il contrario di quello appena visto. Stavolta creiamo uno script per eliminare tutti i file della directory corrente che appartengono a un determinato utente.

La prima soluzione che viene in mente è, come nel caso di prima, usare il comando find in questo modo:

    find . -user pippo -exec rm -f {} \;

Il problema è che, così facendo, elimineremmo i file di pippo non solo dalla directory corrente, ma anche da tutte le sue sottodirectory. Osserva:

    ls -l | grep " pippo " | cut -c55- | xargs rm

In questo modo grep seleziona tutti file della directory corrente elencati da ls -l che contengono la stringa pippo. Il comando cut cattura soltanto il nome dei file e li passa a rm il quale li rimuove utilizzando il comando xargs come ponte

Il comando xargs è anche uno strumento eccellente per creare gli one-liner (script composti da una sola riga). Osserva lo script seguente: esso elenca tutti i proprietari dei file (link compresi) presenti nella directory /bin e nelle sue sottodirectory.

$ find /bin -type f -follow | \ xargs ls -al | tr -s ' ' | cut -f3 -d' ' | sort -u

Spesso /bin è un link (per lo meno lo è in Solaris) e l'opzione -follows obbliga find a seguire il link. Il comando xargs alimenta ls -al e la successiva sequenza di comandi serve a catturare soltanto il terzo campo (proprietario) e classificarlo presentandolo solo una volta (opzione -u del comando sort che equivale al comando uniq).

Opzioni di xargs

Si possono utilizzare le opzioni di xargs per costruire comandi molto potenti.

Opzione -i

Per dare un esempio e iniziare a comprendere le principali opzioni dell'istruzione supponiamo di dover rimuovere tutti i file con estensione .txt dalla directory corrente e mostrare i loro nomi sullo schermo. Osserva:

$ find . -type f -name "*.txt" | \ xargs -i bash -c "echo rimuovo {}; rm {}"

L'opzione -i di xargs scambia una coppia di parentesi graffe ({}) con la stringa che riceve dalla pipe (|). In questo caso, le parentesi graffe ({}) saranno scambiate con i nomi dei file che soddisfano il comando find.

Opzione -n

Da' un'occhiata a questo scherzo messo su con xargs:

$ ls | xargs echo > file.ls $ cat file.ls file.ls file1 file2 file3 $ cat file.ls | xargs -n1 file.ls file1 file2 file3

Quando indirizziamo l'output di ls nel file utilizzando xargs, comproviamo ciò che abbiamo detto in precedenza, cioè che xargs invia ciò che gli è possibile (la quantità di dati sufficiente per non generare uno stack overflow) in un'unica volta. A seguire, utilizziamo l'opzione -n 1 per elencare un elemento alla volta. Per non lasciare dubbi, osserva il seguente esempio in cui elencheremo due elementi in ogni riga:

$ cat file.ls | xargs -n 2 file.ls file1 file2 file3

Ma la riga precedente avrebbe potuto (e dovuto) essere scritta senza l'uso della pipe (|):

$ xargs -n 2 < file.ls

Opzione -p

Un'altra opzione interessante di xargs è -p, grazie alla quale il sistema chiede se vuoi davvero eseguire il comando. Diciamo che in una directory ci sono file con estensione .bug e .ok, che i file .bug presentano dei problemi e che, una volta corretti tali problemi, i file corretti sono salvati con estensione .ok. Osserva l'elenco di questa directory:

$ ls dir file1.bug file1.ok file2.bug file2.ok ... file9.bug file9.ok

Per confrontare i file buoni con quelli difettosi scriviamo:

$ ls | xargs -p -n2 diff -c diff -c file1.bug file1.ok ?...y .... diff -c file9.bug file9.ok ?...y

Opzione -t

Per concludere, xargs ha anche l'opzione -t, tramite la quale mostra le istruzioni scritte prima di eseguirle. Questa opzione mi piace in special modo quando devo ripulire un comando.

Riassunto

Possiamo riassumere l'uso del comando nella seguente tabella:

-t   Mostra la riga di comando scritta prima di eseguirla  
  Opzione     Azione
-i   Sostituisce la coppia di parentesi graffe ({}) con le stringhe ricevute  
-nNum   Invia il più alto numero possibile di parametri ricevuti, fino a un massimo pari a Num, al comando da eseguire  
-lNum   Invia il più alto numero di righe ricevute, fino a un massimo pari a Num, al comando da eseguire  
-p   Mostra la riga comando scritta e chiede se si desidera eseguirla  

Here Strings

Ho appena tenuto un corso all'Instituto de Pesquisas Eldorado, nel quale ho avuto una classe di 12 alunni molto ben preparati. Grazie a ciò, il livello del corso è stato così alto che ho avuto la possibilità di affrontare concetti poco usati come il redirezionamento tramite here strings.

Allora ho pensato: se è così utile perché non farla vedere a tutti? Ed eccola qua...

All'inizio, un programmatore con un complesso di inferiorità creò il redirezionamento dell'input e decise di indicarlo con il segno di minore (<) per meglio rappresentare i suoi sentimenti. Più tardi, un altro programmatore che si sentiva ancor peggio del primo creò here document e decise di indicarlo con due segni di minore (<<) perché la sua fossa era più profonda. Un terzo programmatore pensò: "Questi due non sanno cosa vuol dire avere il morale sotto i piedi"... Ed ecco che creò here strings e lo indicò con tre segni di minore (<<<).

Scherzi a parte, here strings è utilissimo ma, chissà perché, è anche un perfetto sconosciuto. Nella scarsissima letteratura sull'argomento, si nota here strings è frequentemente citato come una variante di here document, teoria con la quale non sono d'accordo poiché le occasioni in cui si utilizza sono totalmente differenti dall'altra. La sintassi è semplice:

    $ comando <<< $stringa

dove stringa è espansa e alimenta l'input primario (stdin) di comando.

Come sempre, vediamo subito un esempio dei due usi più comuni affinché si possa tirare le proprie conclusioni.

  • Uso #1. Sostituire il famigerato costrutto echo "cadeia" | comando, che forza un fork, crea una sottoshell e allunga il tempo di esecuzione.

Esempi:

$ a="1 2 3" $ cut -f 2 -d ' ' <<< $a # Normalmente si fa: echo $a | cut -f 2 -d ' ' 2 $ echo $NomeFile I Miei Documenti # Arrrghhh! $ tr "A-Z " "a-z_" <<< $NomeFile # Sostituzione di echo $NomeFile | tr "A-Z " "a-z_" i_miei_documenti $ bc <<<"3 * 2" 6 $ bc <<<"scale = 4; 22 / 7" 3.1428

Per mostrare quanto l'esecuzione migliora, creiamo un loop di 500 cicli utilizzando l'esempio dato per il comando tr: Osserva questa sequenza di comandi con unità di tempo:

$ time for ((i=1; i<= 500; i++)); { tr "A-Z " "a-z_" <<< $NomeFile >/dev/null; } real 0m3.508s user 0m2.400s sys 0m1.012s $ time for ((i=1; i<= 500; i++)); { echo $NomeFile | tr "A-Z " "a-z_" >/dev/null; } real 0m4.144s user 0m2.684s sys 0m1.392s

Osserva quest'altra sequenza di comandi con unità di tempo:

$ time for ((i=1;i<=100;i++)); { who | cat > /dev/null; } real 0m1.435s user 0m1.000s sys 0m0.380s $ time for ((i=1;i<=100;i++)); { cat <(who) > /dev/null; } real 0m1.552s user 0m1.052s sys 0m0.448s $ time for ((i=1;i<=100;i++)); { cat <<< $(who) > /dev/null; } real 0m1.514s user 0m1.056s sys 0m0.412s

Puoi vedere che nel primo esempio abbiamo utilizzato la forma convenzionale, nel secondo abbiamo impiegato una named pipe temporanea per eseguire una sostituzione di processi, mentre nel terzo abbiamo usato here strings. Noterai anche che, contrariamente all'esempio precedente, l'uso di here strings non è il più rapido. Osserva, tuttavia, che nell'ultimo caso il comando who è in esecuzione in una sottshell e ciò ha appesantito il processo.

Vediamo adesso un modo veloce per inserire una riga come intestazione di un file:

$ cat num 1 2 3 4 5 6 7 8 9 10 $ cat - num <<< "Dispari Pari" Dispari Pari 1 2 3 4 5 6 7 8 9 10

  • Uso #2. Un altro modo interessante di usare here strings è quello di associarlo a un comando read, senza perdere di vista ciò che abbiamo imparato riguardo IFS (osserva la spiegazione del comando for). Il comando cat con le opzioni -vet mostra <ENTER> come $, <TAB> come ^I e gli altri caratteri di controllo con la notazione ^L dove L è una lettera qualsiasi. Osserviamo il contenuto di una variabile quindi leggiamo ognuno dei suoi campi:

Esempli:

$ echo "$Riga" Leonardo Mello (21)3313-1329 $ cat -vet <<< "$Riga" Leonardo Mello^I(21)3313-1329$ # I separatori sono spazio e <TAB> (^I) $ read Nom SNom Tel <<< "$Riga" $ echo "${Nom}_$S{Nom}_$Tel" # Verifichiamo se ha letto entrambi i campi Leonardo_Mello_(21)3313-1329 # Letti perché i separatori coincidevano con IFS

Possiamo anche leggere direttamente un vettore (array). Osserva:

$ echo $Frutta Pera:Uva:Mela $ IFS=: $ echo $Frutta Pera Uva Mela # Senza le virgolette la shell mostra IFS come uno spazio $ echo "$Frutta" Pera:Uva:Mela # Ahhh, adesso sì! $ read -a aFrutta <<< "$Frutta" # L'opzione -a di read legge un vettore $ for i in 0 1 2 > do > echo ${aFrutta[$i]} # Stampa di ogni elemento del vettore > done Pera Uva Mela

Rotatório Peczenyj

Stavo leggendo, come faccio ogni giorno, le email di "Lista de Shell Script" , quando ho visto una "scoperta" totalmente inattesa da parte di Tiago Barcellos Peczenyj.

Quando decisi di mettere in piedi questa raccolta di suggerimenti mi sono ricordato di quella scoperta e chiesi all'autore di spedirmi di nuova quella email. Il testo che segue è proprio quella email, alla quale ho aggiunto l'ultimo esempio e dalla quale ho eliminato le abbreviazioni.

Julio ha scoperto un modo secondo il quale la Shell genera combinazioni tramite rotazione degli elementi stabiliti. Possiamo generare tutti i numeri binari compresi tra 0000 a 1111 così:

$ A={0,1} $ eval echo $A$A$A$A 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

Un'applicazione pratica di questo esempio è la combinazione di valori diversi senza dover utilizzare cicli di loopseq

$ A={`seq -s , -f "_%g" 3`} $ eval echo -e $A$A$A |tr ' _' '\n ' | grep -vE '.+?(\b[0-9]+\b).+?\1' 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1

In questo caso ho combinato i numeri da 1 a 3 eliminando le ripetizioni tramite l'uso di grep. Ho utilizzato un tr 'molle' per trattare meglio i dati saltando una riga.

Come puoi notare, l'uso di grep è semplice: vedo se una determinata parte della combinazione (.+?(\b[0-9]+\b).+?) esiste altrove (\1) e, in tal caso, non la faccio stampare grazie all'opzione -v. In tal modo,

    1 1 2
    1 2 1
    1 1 1
non saranno stampati.

Ecco il mio esempio: l'one-liner che segue genererà tutti i permessi possibili (in sistema ottale) per il file file (l'esempio è stato interrotto perché esistono 512 possibili combinazioni).

$ A={`seq -s , 0 7`} $ eval echo -e $A$A$A | tr ' ' '\n' | xargs -i bash -c "chmod {} arq; ls -l arq" ---------- 1 julio julio 100 2006-11-27 11:50 arq ---------x 1 julio julio 100 2006-11-27 11:50 arq --------w- 1 julio julio 100 2006-11-27 11:50 arq --------wx 1 julio julio 100 2006-11-27 11:50 arq -------r-- 1 julio julio 100 2006-11-27 11:50 arq -------r-x 1 julio julio 100 2006-11-27 11:50 arq -------rw- 1 julio julio 100 2006-11-27 11:50 arq -------rwx 1 julio julio 100 2006-11-27 11:50 arq . . . . . . . . . . . . . . . . . . -rwxrwxrw- 1 julio julio 100 2006-11-27 11:50 arq -rwxrwxrwx 1 julio julio 100 2006-11-27 11:50 arq

Osserviamo l'esempio che segue passo-passo per capire meglio come funziona:

$ echo $A {0,1,2,3,4,5,6,7} $ eval echo -e $A$A$A 000 001 002 003 004 005 006 007 010 ... ... 767 770 771 772 773 774 775 776 777 $ eval echo -e $A$A$A | tr ' ' '\n' # Il comando tr sostituirà ogni spazio con un <ENTER> 000 001 002 003 . . . 774 775 776 777

Subito dopo xargs (clicca sulle tip di xargs) esegue il comando bash -c (che serve a esguire una riga di comando) il quale, a sua volta, esegue chmod e ls -l per far vedere che i permessi saranno modificati.

Aritmetica nella Shell

Prima utilizzavamo il comando expr per compiere operazioni aritmetiche e molti ancora lo utilizzano poiché è compatibile con qualsiasi ambiente.

Esempio:

$ expr 7 \* 5 / 3 # 7 volte 5 = 35 diviso per 3 = 11 11

Qui, tuttavia, vedremo altri modi poco conosciuti ma più semplici da usare, più elaborate e più precise.

L'uso di bc

Un modo davvero forte di eseguire dei calcoli nella Shell – è utilizzata normalmente quando l'espressione aritmetica è più complessa o quando è necessario lavorare con posizioni decimali – è utilizzare le istruzioni calcolo di UNIX/LINUX. O bc. Osserva:

Esempio:

$ echo "(2 + 3) * 5" | bc # Parentesi utilizzate per indicare precedenza nel calcolo 25

Per trattare numeri reali (non necessariamente interi), puoi specificare la precisione (quantità di decimali) con l'opzione scale del comando bc. Vediamo un altro esempio:

$ echo "scale=2; 7*5/3" | bc 11.66

Ecco altri esempi:

$ echo "scale=3; 33.333*3" | bc 99.999 $ num=5 $ echo "scale=2; ((3 + 2) * $num + 4) / 3" | bc 9.66

È ovvia che tutti gli esempi precedenti, in ambiente linux, possano e debbano essere scritti utilizzando degli Here String. Osserva:

$ bc <<< "scale=3; 33.333*3" 99.999 $ num=5 $ bc <<< "scale=2; ((3 + 2) * $num + 4) / 3" 9.66

Una volta, nella mailing list di Yahoo della Shell script (http://br.groups.yahoo.com/group/shell-script/) è comparso un tizio che ha sottoposto questo problema: "ho un file i cui campi sono separati tramite <TAB> e il terzo comprende dei numeri. Como posso calcolare la somma di tutti i numeri di questa colonna del file?"

Bene, gli ho inviato questa riposta:

$ echo $(cut -f3 num | tr '\n' +)0 | bc 20.1

Vediamone un pezzo alla volta per capirlo meglio e osserviamo, prima di tutto, com'è fatto un file di controllo:

$ cat num a b 3.2 a z 4.5 w e 9.6 q w 2.8

Como si può vedere, l'esempio è calzante poiché nel terzo campo ci sono numeri reali. Vediamo cosa fa la prima parte della riga di comando, nella quale i caratteri <ENTER> (nuova-riga) sono trasformati in un segno di maggiore (+):

$ cut -f3 num | tr '\n' + 3.2+4.5+9.6+2.8+

Se inviassi i dati a bc così come sono, esso restituirebbe un errore a causa di quel segno più (+) alla fine. La soluzione è sommare ad essi uno zero poiché il risultato non cambia. Osserva:

$ echo $(cut -f3 num | tr '\n' +)0 3.2+4.5+9.6+2.8+0

Ottimo, il problema è risolto, ma nella Shell si preferisce scrivere codice ancor più ridotto. Osserva:

$ cut -f3 num | paste -sd+ 3.2+4.5+9.6+2.8

Vediamo: cut ritaglia la terza colonna e invia i numeri a paste, la cui opzione -s trasforma colonne in righe e -d+ definisce il segno più (+) come delimitatore. A questo punto, è sufficiente inviare tutto a bc, il quale procederà a sommare i numeri reali.

$ cut -f3 num | paste -sd+ | bc 20.1

Questo è ciò che si chiama one-liner, cioè codici complicati da scrivere in altri linguaggi (normalmente sarebbe necessario creare dei contatori e fare un loop di lettura aggiungendo il terzo campo al contatore), mentre nelle Shell sono scritti in un unica riga.

C'è anche chi chiama tutto ciò metodo KISS, l'acronimo di Keep It Simple Stupid. smile

Ma il potenziale di questa calcolatrice non si limita a questo: ci sono molte cose che può fare. Osserva:

$ echo "pbase=16; 11579594" | bc B0B0CA? $ echo "abase=16; B0B0CA?" | bc # B, zero, B, zero, C, e A 11579594

In questo esempio possiamo vedere il cambio di base grazie all'uso di bc. Nella prima riga indichiamo che la base di partenza (pbase) è 16 (esadecimale) e, nella seconda, che la base di arrivo (abase) è 10 (decimale).

Altri modi di trattare i numeri interi

Un altro modo davvero forte di fare calcoli è utilizzare la notazione $((espressione aritmetica)). Presta molta attenzione però, poiché la sintassi del comando non è standard. La Bourne Shell (sh), ad esempio, non la riconosce.

Esempio:

Rielaboriamo quanto fatto prima:

$ echo $(((2+3)*5)) # Le parentesi più all'interno danno priorità a 2+3 25

Osserva questo giochetto:

$ tre=3 $ echo $(((2+tre)*5)) # Variabile tre non preceduta da $ 25 $ echo $(((2+$tre)*5)) # Variabile tre preceduta da $ 25

Evvai!! Ma non è il simbolo del dollaro ($) che distingue una variabile? Sì, ma in tutte le varianti di UNIX che ho testato, sia sotto bash che ksh, entrambi i costrutti producono ottimi calcoli.

Osserva bene quanto segue:

$ unset i # $i è moooooorto! $ echo $((i++)) 0 $ echo $i 1 $ echo $((++i)) 2 $ echo $i 2

Nota che, sebbene la variabile non è definita poiché abbiamo eseguito su di essa un unset, nessuno dei comandi ha generato errori poiché, anche se stiamo usando costrutti aritmetici, ogni volta che una variabile non esiste, essa è inizializzata come zero (0).

Osserva che i++ ha prodotto zero (0). Ciò accade poiché questo tipo di costrutto si chiama post-incremento, cioè prima si esegue il comando, poi si incrementa la variabile. Nel caso di ++i è stata eseguito un pre-incremento: prima si incrementa la variabile e poi si esegue il programma.

È valido anche quanto segue:

$ echo $((i+=3)) 5 $ echo $i 5 $ echo $((i*=3)) 15 $ echo $i 15 $ echo $((i%=2)) 1 $ echo $i 1

Queste tre operazioni sono identiche a:

    i=$((i+3))
    i=$((i*3))
    i=$((i%2))

Lo stesso vale per tutti gli operatori aritmetici, cosa che produce la tabella riportata sotto:

Espansione Aritmetica
||   O logico
  Espressione     Risultato  
id++ id--   post-incremento e post-decremento di variabili
++id -–id   pre-incremento e pre-decremento di variabili
**   elevamento a potenza
* / %   moltipliczione, divisione, resto della divisione
+ -   addizione, sottrazione
<= >= < >   comparazione
== !=   uguaglianza, disuguaglianza
&&   E logico

Ma l'apoteosi di questo costrutto a doppia parentesi è:

$ echo $var 50 $ var=$((var>40 ? var-40 : var+40)) $ echo $var 10 $ var=$((var>40 ? var-40 : var+40)) $ echo $var 50

Questo tipo di costrutto deve essere letto così: se la variabile var è maggiore di 40 (var>40), allora (?) var è uguale a var meno 40 (var-40), altrimenti (:) var è uguale a var più 40 (var+40). Ciò significa che i caratteri punto-interrogativo (?) e due-punti (:) svolgono la funzione, rispettivamente, di "allora" e "altrimenti": in tal modo possono essere utilizzati per costruire operazioni aritmetiche condizionali.

Così come utilizziamo l'espressione $((...)) per compiere operazioni aritmetiche, potremmo utilizzare il comando interno (built-in) let oppure un costrutto di tipo $[...].

Gli operatori sono gli stessi per tutte e tre le modalità: ciò che varia leggermente è l'operazione aritmetica condizionale con l'uso di let. Osserva:

$ echo $var 50 $ let var='var>40 ? var-40 : var+40' $ echo $var 10 $ let var='var>40 ? var-40 : var+40' $ echo $var 50

Basi di numerazione

Se vuoi lavorare con basi diverse da quella decimale, è sufficiente utilizzare il formato:

    base#numero

Dove base è un numero decimale tra 2 e 64 che rappresenta il sistema di numerazione, mentre numero è un numero nel sistema numerico definito da base. Se base# è omessa, allora 10 è il valore di default. Gli algoritmi maggiori di 9 sono rappresentati da lettere minuscole, maiuscole, @ e _ in questo preciso ordine.

Se base è minore o uguale a 36, è possibile utilizzare indifferentemente maiuscole o minuscole per definire algoritmi maggiori di 9 (il sistema di algoritmi esadecimale, ad esempio, varia tra 0 (zero) e F, e non si tratta di un errore). Ecco come funziona:

$ echo $[2#11] 3 $ echo $((16#a)) 10 $ echo $((16#A)) 10 $ echo $((2#11 + 16#a)) 13 $ echo $[64#a] 10 $ echo $[64#A] 36 $ echo $((64#@)) 62 $ echo $((64#_)) 63

In questi esempi ho utilizzato indistintamente le notazioni $((...)) e $[...] per dimostrare entrambe le funzioni.

Allo stesso modo, funziona anche una variazione automatica in base decimale, sempre che si utilizzi la convenzione numerica del linguaggio C, cioè, in base 0xNN, NN sarà trattato come un numero esadecimale e, in base 0NN, NN sarà visto come un numero in base ottale. Osserva:

Esempio

$ echo $((10e))e # decimale 10 $ echo $((010)) # ottale 8 $ echo $((0x10)) # esadecimale 16 $ echo $((10+010+0x10)) # Decimale + ottale + esadecimale 64

Ah, quasi mi dimenticavo! Le espressioni aritmetiche con i formati $((...)), $[...] e con il comando let utilizzano gli stessi operatori usati nell'istruzione expr, oltre agli operatori unari (++, --, +=, *=, ...) e condizionali che abbiamo appena visto.

Test con espressioni regolari

In Chiacchiere da Bar 004, abbiamo parlato di comandi condizionali ma ne abbiamo lasciato da parte uno che allora nn esisteva. In quelle stesse Chiacchiere da Bar, nella sezione E tome de test abbiamo parlato di un costrutto del tipo:

    [[ Espressione ]] && cmd

in cui il comando cmd sarà eseguito se l'espressione condizionale Espressione è vera. Ho detto anchre che Espressione può essere scritta d'accordo con le regole della Generazione dei Nomi dei File (File Name Generation). A partire dalla bash versione 3, a questo tipo di test è stato incorporato un operatore rappresentato da =~, il cui scopo è fare comparazioni con le Espressioni Regolari.

Esempio:

$ echo $BASH_VERSION # Verifica se la versione di Bash è uguale o superiore a 3.0.0 3.2.17(15)-release $ Carica=Senatore $ [[ $Carica =~ ^(Governa|Sena|Amministra)trice$ ]] && echo È politico È politico $ Incarico=Senatrice $ [[ $Carica =~ ^(Governa|Sena|Amministra)trice$ ]] && echo È politico È politico $ Carica=Direttore $ [[ $Incarico =~ ^(Governa|Sena|Amministra)trice$ ]] && echo È politico $

Diamo un'occhiata all'Espressione Regolare ^(Governa|Sena|Amministra)trice?$: si combina con tutto ciò che inizia (^) per Governa, o (|) Sena, o (|) Amministra, seguito da trice. Il simbolo del dollaro ($) serve a indicare la fine. In altre parole, questa Espressione Regolare si combina con Governatore, Senatore, Amministratore, Governatrice, Senatrice e Amministratrice.

Uno schermo a colori

Come abbiamo visto in Chiacchiere da Bar 007, il comando tput serve a fare quasi tutto ciò che concerne la formattazione dello schermo, ma ciò che non è stato detto è che con esso possiamo utilizzare colori di foreground e di background per i caratteri. Ci sono altri modi di fare la stessa cosa, ma ritengo che quella che stiamo per vedere sia più intuitiva (o meno “contro-intuitiva”). La tabella che segue mostra i comandi per specificare gli standard dei colori dei caratteri (foreground) e del relativo sfondo (background):

Ottenere i colori tramite il comando tput
tput setab n Specifica n come colore di fondo (background)
  Comando     Effetto  
tput setaf n Specifica n come colore del carattere (foreground)

Adesso sappiamo come indicare i due colori, ma ancora non conosciamo i colori veri e propri. La tabella seguente mostra i valori che n deve assumere per ogni singolo colore:

Valori dei colori con il comando tput
7 Grigio chiaro
  Valore     Colore  
0 Nero
1 Rosso
2 Verde
3 Marrone
4 Blu
5 Porpora
6 Celeste

Adesso puoi metterti a giocare con i colori.

- Aspetta, sono troppo pochi!

- Hai ragione... Il fatto è che non ti ho ancora detto che se metti il terminale in modalità grassetto (tput bold), questi colori ne generano altri otto. Costruiamo la tabella definitiva dei colori:

Valore dei colori con il comando tput
7 Grigio chiaro Bianco
  Valor     Cor     Cor após tput bold  
0 Nero Grigio scuro
1 Rosso Rosso chiaro
2 Verde Verde chiaro
3 Marrone Giallo
4 Blu Blu brillante
5 Porpora Rosa
6 Celeste Celeste chiaro

Exemplo

Come esempio, vediamo uno script che cambierà i colori sullo schermo a tuo piacimento.

$ cat cambiacolore.sh #!/bin/bash tput sgr0 clear

# Inserimento degli 8 colori di base in un vettore Colori=(Nero Rosso Verde Marrone Blu Porpora Celeste "Grigio chiaro")

# Elenco del menu dei colori echo " Opz Colore = ===" # La riga seguente significa: per i partendo da 1; #+ finché i è minore o uguale alla grandezza del vettore Colori; #+ aumenta il valore di i di 1 in 1 for ((i=1; i<=${#Colori[@]}; i++)) { printf "%02d %s\n" $i "${Colori[i-1]}" }

CC= until [[ $CC == 0[1-8] || $CC == [1-8] ]] do read -p " Scegli il colore del carattere: " CC done

# Per chi ha la bash a partire dalla versione 3.2 #+ il test di until può essere eseguito #+ utilizzando le Espressioni Regolari. Osserva: #+ until [[ $CC =~ 0?[1-8] ]] #+ do #+ read -p " #+ Scegli il colore del carattere: " CC #+ done

CS= until [[ $CS == 0[1-8] || $CS == [1-8] ]] do read -p " Scegli il colore dello sfondo: " CS done

let CC-- ; let CS-- # I colori variano da zero a sette tput setaf $CC tput setab $CS clear

Vincere la partita con più jolly

Stavo leggendo le mie email quando recebo ne ho ricevuta una da Tiago inviata a mailing-list di Shell Script (ho già parlato della mailing-list e di Tiago in Rotatorio Peczenyj). Ecco il testo:

Non so se tutti ne sono a conoscenza, ma la shell possiede, oltre al globbing normale (l'espansione di *, ? e [a-z] dei nomi dei file e delle directory), anche un globbing esteso.

Penso che, in alcuni casi, potrebbe essere MOLTO utile, per esempio nell'eliminazione di una pipe su grep.

Vediamo:

    ?(standard)
Si combina con zero o un'occorrenza di un determinato standard
    *(standard)
Si combina con zero o più occorrenze di un determinato standard
    +(standard)
Si combina con una o più occorrenze di un determinato standard
    @(standard)
Si combina con esattamente un'occorrenza di un determinato standard
    !(standard)
Si combina con tutto tranne con standard

Per utilizzarlo è necessario eseguire shopt secondo l'esempio che segue:

$ shopt -s extglob $ ls file filename filenamename fileutils $ ls file?(name) file filename $ ls file*(name) file filename filenamename $ ls file+(name) filename filenamename $ ls file@(name) filename $ ls file!(name) # divertido esse file filenamename fileutils $ ls file+(name|utils) filename filenamename fileutils $ ls file@(name|utils) # "ricorda" {name,utils} filename fileutils

Utilizzare awk per compiere ricerche per equivalenza

Ecco un'altra email che Tiago ha inviato a lista de Shell Script do Yahoo (ho già parlato della mailing-list e di Tiago in Rotatorio Peczenyj e in Vincere la partita con più jolly)

A chi non è mai capitato di cercare una parola ma una lettera accentata ha bloccato la ricerca?

Non ho scoperto come fare in modo che grep o sed accettino qualche di simile, ma gawk accetta classi di equivalenza!

È meglio spiegare con un esempio in cui elencherò il numero di riga e l'occorrenza della coincidenza:

$ cat dati éco eco èco êco ëco eço $ awk '/^eco/{print NR,$1}' dati 2 eco $ awk '/^e[[=c=]]o/{print NR,$1}' dati 2 eco 6 eço $ awk '/^[[=e=]]co/{print NR,$1}' dati 1 éco 2 eco 3 èco 4 êco 5 ëco

In poche parole, [=X=] fa in modo che l'espressione trovi la lettera X sia che abbia un accento, sia che non ce l'abbia (e ciò è importante per la localizzazione corrente!).

La sintassi è simile a quella delle classi POSIX: basta sostituire i due-punti (:) prima e dopo la classe con dei segni di uguale (=).

Ho trovato ciò molto curioso e deve servire in qualche caso simile a quello descritto.

find – Ricerca di file in base alle caratteristiche

Se, come Mr. Magoo, anche tu sei alla vana ricerca di un file, puoi utilizzare il comando find che serve a cercare file non solo con il nome, ma anche tramite diverse altre caratteristiche. La sua sintassi è la seguente:

    find [percorso ...] espressione [azione]

Parametri:

percorso      Percorso della directory a partire della quale ricercherà i file (essendo ricorsivo, esso tenterà sempre di ispezionarne tutte le sottodirectory) ;
espressione   Definisce quali criteri di ricerca. Può essere una combinazione tra vari tipi di ricerca;
azione           Definisce quale azione eseguire con i file che rientrano nei criteri di ricerca definiti da espressione.

I principali criteri di ricerca definiti da espressione sono quelli in tabella:

-name Ricerca i file che hanno il nome indicato. È possibile utilizzare metacaratteri o caratteri jolly, tuttavia questi caratteri dovranno essere posti tra parentesi o apostrofi oppure essere preceduti da una barra inversa poiché è find a doverli espandere: se fosse la Shell a farlo, la ricerca si limiterebbe alla directory corrente, annullando così la caratteristica ricorsiva di find;
-user Ricerca file che hanno l'utente come proprietario;
-group Ricerca file che hanno gruppo come gruppo proprietario;
-type c Ricerca file di tipo c come da tabella. I tipi di file accettati sono i seguenti:
Valori di c nell'opzione   precedente Tipo di file ricercato
b File speciale ispezionato a blocchi
c File speciale ispezionato a caratteri
d Directory
p Named pipe (FIFO)
f File normale
l Link simbolico
s Socket
-size ±n[unid] L'opzione -size ricerca file che usano più (+n) di n unità unità di spazio o meno (-n) di n unità unità di spazio.
Valori di unità nell'opzione   precedente Valore
b Blocco di 512 bytes (valore default)
c Caratteri
k Kilobytes (1024 bytes)
w Parole (2 bytes)
-atime ±d Ricerca file aperti da più (+d) di d giorni o da meno (-d) di d dias;
-ctime ±d Ricerca file il cui stato è stato variato da più (+d) di d giorni o da meno (-d) di d giorni;
-mtime ±d Ricerca file i cui dati sono stati modificati da più (+d) di d giorni o da meno (-d) di d giorni;

Per utilizzare più di un criterio di ricerca esegui quanto segue:
     espressione1 espressione2
oppure
     espressione1 –a espressione2
per seguire i criteri specificati da espressione1 e espressione2;
     espressione1 –o espressione2
per seguire i criteri specificati in espressione1 o espressione2.

Le principali azioni definite da azione sono:

-print Questa opzione fa sì che i file trovati siano mostrati sullo schermo. Questa è l'pzione de default in Linux. Negli altri sistemi Unix che conosco, se non è specificata alcuna azione si ottiene un errore;
-exec cmd {} \; Esegue il comando cmd. L'estensione del comando è considerata chiusa quando incontra un punto-e-virgola (;). La stringa {} è sistituita dal nome di ogni file che soddisfa il criterio di ricerca e la riga così formata è eseguita. Come detto in precedenza per l'opzione –name, il punto-e-virgola (;) deve essere preceduto da una barra inversa (\) oppure deve essere inserito tra virgolette o apostrofi;
-ok cmd {} \; Come sopra ma con la richiesta di poter eseguire l'istruzione cmd per ogni file che soddisfa il criterio di ricerca;
-printf formato  L'opzione -printf permette di scegliere i campi da elencare e formatta l'output secondo le specifiche presenti in formato.

Esempi:

Per elencare sullo schermo (-print) tutti i file che terminano con .sh iniziando dalla directory corrente, digita:

$ find . -name \*.sh Azione non specificata –print é default ./undelete.sh ./ntod.sh questi primi quattro file sono stati ./dton.sh trovati nella directory corrente. ./graph.sh ./tstsh/cotafs.sh ./tstsh/data.sh Questi quattro sono stati trovati nella ./tstsh/velha.sh directory tstsh dentro la directory corrente ./tstsh/charascii.sh

Ho bisogno di fare velocemente spazio in un determinato file system , quindi rimuoverò file più grandi di un megabyte e l'ultimo accesso ai quali è avvenuto più di 60 giorni fa. A questo scopo entro nel file system e digito:

$ find . –type f –size +1000000c –atime +60 –exec rm {} \;

Osserva che nell'esempio precedente ho utilizzato tre criteri di ricerca, cioè:

-type f Tutti i file regolari (normali)
-size +1000000c   Grandezza superiore ai 1000000 caratteri (+1000000c)
-atime +60 Ultimo accesso oltre 60 (+60) giorni fa.

Osserva anche che tra questi tre criteri è stato usato il connettore e, cioè file regolari e più grandi di 1MByte e con ultimo accesso oltre 60 giorni fa.

Per elencare tutti i file del disco che finiscono per .sh o .txt, digito:

$ find / -name \*.sh –o –name \*.txt –print

In questo esempio dobbiamo sottolineare, oltre alle barre inverse (\) prima degli asterischi (*), l'uso di –o per l'una o l'altra estensione e che la directory iniziale era radice (/); in questo modo, la ricerca è stata estesa all'intero disco (operazione di solito piuttosto lunga).

Con printf è possibile formattare l'output del comando find e specificare i dati che desideriamo. La formattazione di printf è molto simile a quella dello stesso comando in linguaggio C e interpreta caratteri di formattazione preceduti da un simbolo di percentuale (%). Vediamone gli effetti sulla formattazione:

Carattere Significato
%U Numero utente (UID) del proprietario del file
%f Nome del file (il percorso completo non compare)
%F Indica a quale tipo de file system appartiene il file
%g Gruppo al quale appartiene il file
%G Gruppo al quale appartiene il file (GID- Numerico)
%h Percorso completo del file (tutto eccetto il nome)
%i numero dell'inode del file (in base decimale)
%m Permessi del file (in base ottale)
%p Nome del file
%s Grandezza del file
%u Nome utente (username) del proprietario del file

È anche possibile formattare date e orari secondo le tabelle che seguono:

Carattere Significato
%a Data di ultimo accesso
%c Data di creazione
%t Data di modifica

I tre caratteri precedenti generano una data simile a quella del comando date.

Osserva:

$ find . -name ".b*" -printf '%t %p\n' Mon Nov 29 11:18:51 2004 ./.bash_logout Tue Nov 1 09:44:16 2005 ./.bash_profile Tue Nov 1 09:45:28 2005 ./.bashrc Fri Dec 23 20:32:31 2005 ./.bash_history

In questo esempio, %p è il responsabile della visualizzazione dei nomi dei file. Se è omesso, sono elencate solamente le date. Osserva anche che, alla fine, è stato collocato /n. Senza di esso non ci sarebbe salto di riga e l'elenco precedente sarebbe un guazzabuglio.

Anche le date posso essere formattate, è sufficiente trasformare le lettere della tabella in maiuscole (%A, %C e %T) e utilizzare uno dei formattatori della tabella seguente:

Tabella di formattazione del tempo
Z  Fuso orario (nella Città Meravigliosa BRST)
  Carattere     Significato  
H  Ora (00..23)
I  Ora (01..12)
k  Ora (0..23)
l  Ora (1..12)
M  Minuto (00..59)
p  AM o PM
r  Orario di 12 ore (hh:mm:ss) seguito da AM o PM
S  Secondi (00 ... 61)
T  Orario de 24 ore (hh:mm:ss)

Tabella di formattazione delle date
Y  Anno su 4 cifre
  Carattere     Significato  
a  Giorno della settimana abbreviato (Dom...Sab)
A  Giorno della settimana esteso (Domenica...Sabato)
b  Nome del mese abbreviato (Gen...Dic)
B  Giorno del mese esteso (Gennaio...Dicembre)
c  Data e ora completa (Fri Dec 23 15:21:41 2005)
d  Giorno del mese (01...31)
D  Data nel formato mm/gg/aa
h  Lo stesso che b
j  Numero del giorno dell'anno (001…366)
m  Mese (01...12)
U  Numero della settimana dell'anno. Domenica come 1º giorno della settimana (00...53)
w  Numero del giorno della settimana (0..6)
W   Numero della settimana dell'anno. Lunedì come 1º giorno della settimana (00...53)
x  Rappresentazione della data nel formato del paese (definito da $LC_ALL)
y  Anno su 2 cifre (00...99)

Per migliorare la situazione vediamo alcuni esempi; prima però vediamo quali sono i file della directory corrente che iniziano per .b:

$ ls -la .b* -rw------- 1 d276707 ssup 21419 Dec 26 17:35 .bash_history -rw-r--r-- 1 d276707 ssup 24 Nov 29 2004 .bash_logout -rw-r--r-- 1 d276707 ssup 194 Nov 1 09:44 .bash_profile -rw-r--r-- 1 d276707 ssup 142 Nov 1 09:45 .bashrc

Per elencare questi file in ordine di grandezza, possiamo digitare:

$ find . -name ".b*" -printf '%s\t%p\n' | sort -n 24 ./.bash_logout 142 ./.bashrc 194 ./.bash_profile 21419 ./.bash_history

Nell'esempio appena visto, \t è stato sostituito nell'output da in modo da rendere l'elenco più leggibile. Per elencare gli stessi file in base alla data e all'ora dell'ultima modifica basta digitare:

$ find . -name ".b*" -printf '%TY-%Tm-%Td %TH:%TM:%TS %p\n' | sort 2004-11-29 11:18:51 ./.bash_logout 2005-11-01 09:44:16 ./.bash_profile 2005-11-01 09:45:28 ./.bashrc 2005-12-26 17:35:13 ./.bash_history

Alcune implementazioni di Bash 4.0

In questa discussione parlerò soltanto delle novità presenti in Bash 4.0. Cioè che era già presente ma che è stato migliorato a partire da questa versione (come le espansioni di parametro), è stato pubblicato nella sezione corrispondente.

Co-processi

A partire dalla versione 4.0, Bash ha incorporato il comando coproc. Questo nuovo comando interno (builtin) permette a due processi asincroni di comunicare e interagire tra loro. Come afferma Chet Ramey in Bash FAQ, ver. 4.01:

"C'è una nuova parola riservata, coproc, che specifica un coprocesso: trattandosi un comando asincrono eseguito con 2 pipe collegate alla Shell creatrice. coproc può riceve un nome. I descrittori dei file di output e input e il PID del coprocesso sono a disposizione della Shell creatrice in variabili con nome specifico di coproc."

George Dimitriu spiega:

"=coproc= è uno strumento impiegato nella sostituzione di processi e adesso è pubblicamente disponibile."

Verificare quanto affermato da Dimitriu non è complicato, vuoi vedere? Osserva la seguente sostituzione di processi:

$ cat <(echo xxx; sleep 3; echo yyy; sleep 3)

Hai visto?! cat non ha atteso la conclusione dei comandi tra parentesi ma è stato eseguito alla fine di ognuno di essi. Ciò accade perché è stata costruita una pipe temporanea/dinamica e i comandi in esecuzione inviavano ad essa i loro output, la quale poi li inviava all'input di cat.

Ciò significa che i comandi di questa sostituzione di processi hanno girato in parallelo, sincronizzandosi solamente gli output dei comandi echo con l'input di cat.

La sintassi di un coprocesso è:

   coproc [nome] cmd redirezionamenti

Ciò creerà un coprocesso chiamato nome. Se nome è valorizzato, cmd dovrà essere un comando composto. In caso contrario (se nome non è valorizzato), cmd potrà essere un comando semplice o composto.

Quando un coproc è eseguito, esso genera un vettore con lo stesso nome nome nella Shell creatrice. Il suo standard output è legato tramite una pipe a un descrittore di file associato alla variabile ${nome[0]} allo standard input della Shell madre (ricordi che lo standard input di un processo è sempre associato per default al descrittore zero?). Allo stesso modo, l'input di coproc è legato allo standard output dello script, tramite una pipe pipe, a un descrittore di file chiamato ${nome[1]}. Per farla semplice, vediamo che lo script invia dati a coproc per mezzo della variabile ${nome[0]}, e riceve il suo output in ${nome[1]}. Nota che queste pipe sono create prima dei redirezionamenti specificati dal comando, poiché essi costituiranno gli input e gli output del coprocesso.

Da adesso in poi spiegherò rapidamente alcuni studi che ho fatto. In essi ci sono alcune divagazioni e molta teoria. Se vuoi saltare oltre queste ls non ti perderai niente, ma se mi segui potrai capire meglio il meccanismo di coproc.

Dopo aver avviato un coproc, poiché esso è associato a un descrittore di file, diamo un'occhiata a ciò che è in funzione della directory corrispondente:

$ ls -l /dev/fd lrwxrwxrwx 1 root root 13 2010-01-06 09:31 /dev/fd -> /proc/self/fd

Mmm, è un link a /proc/self/fd... Cosa ci sarà là dentro?

$ ls -l /proc/self/fd total 0 lrwx------ 1 julio julio 64 2010-01-06 16:03 0 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 16:03 1 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 16:03 2 -> /dev/pts/0 lr-x------ 1 julio julio 64 2010-01-06 16:03 3 -> /proc/3127/fd

Caspita, sapevo già che 0, 1 e 2 puntavano a /dev/pts/0, poiché sono lo standard input, lo standard output e lo standard error che puntano allo pseudo terminale corrente, ma cosa diavolo sarà quel device 3? Osserva:

$ ls -l /proc/$$/fd # $$ É o PID do Shell corrente total 0 lr-x------ 1 julio julio 64 2010-01-06 09:31 0 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 09:31 1 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 09:31 2 -> /dev/pts/0 lrwx------ 1 julio julio 64 2010-01-06 16:07 255 -> /dev/pts/0 l-wx------ 1 julio julio 64 2010-01-06 16:07 54 -> pipe:[168521] l-wx------ 1 julio julio 64 2010-01-06 16:07 56 -> pipe:[124461] l-wx------ 1 julio julio 64 2010-01-06 16:07 58 -> pipe:[122927] lr-x------ 1 julio julio 64 2010-01-06 16:07 59 -> pipe:[168520] l-wx------ 1 julio julio 64 2010-01-06 16:07 60 -> pipe:[121302] lr-x------ 1 julio julio 64 2010-01-06 16:07 61 -> pipe:[124460] lr-x------ 1 julio julio 64 2010-01-06 16:07 62 -> pipe:[122926] lr-x------ 1 julio julio 64 2010-01-06 16:07 63 -> pipe:[121301]

Ecco i link che puntano alle pipe. Questa montagna di pipe elencate è dovuto al fatto che stavo testando a fondo questo nuovo strumento della Bash.

Per concludere con questa noiosa teoria resta soltanto da dire che il PID della Shell generato per interpretare il coproc può essere preso dalla variabile $nome_PID e il comando wait può essere usato per attendere il termine del coprocesso. Il codice di ritorno del coprocesso ($?) è lo stesso di cmd.

Esempio:

Iniziamo con il più semplice: un esempio senza nome direttamente nel prompt:

$ coproc while read Entra # coproc attivo > do > echo -=-=- $Entra -=-=- > done [2] 3030 $ echo Ciao >&${COPROC[1]} # Invia Ciao alla pipe dell'output $ read -u ${COPROC[0]} Esce # Legge la pipe dell'input $ echo $esce -=-=- Ciao -=-=- $ kill $COPROC_PID # Questo non possiamo dimenticarlo...

Come puoi vedere, il vettore COPROC è associato a due pipe; ${COPROC[1]} , che contiene l'indirizzo della pipe di output (per questo l'output di echo è redirezionato in esso), e ${COPROC[0]} , che contiene l'indirizzo della pipe di input (ecco perché abbiamo usato l'opzione -u di read che legge dati a partire da un descrittore di file definito, al contrario dell'input standard.

Poiché il coprocesso utilizza la sintassi senza nome, lo standard del nome del vettore è COPROC.

Ancora un pizzico di noiosa teoria:

$ echo ${COPROC[@]} # Elenca tutti gli elementi del vettore 59 54

Come puoi vedere ${COPROC[0]} usa la pipe che punta a /proc/$$/fd/59 mentre ${COPROC[1]} usava /proc/$$/fd/54. Adesso basta teoria, davvero! Proviamo a usare nome nello stesso esempio per vedere le poche cose che cambiano:

$ coproc test { > while read Entra > do > echo -=-=- $Entra -=-=- > done > } [6] 3192 $ echo Ciao >&${test[1]} $ read -u ${test[0]} Esce $ echo $Esce -=-=- Ciao -=-=- $ kill $test_PID

Questo è il momento buono per mostrare una cosa interessante: quali sono i processi in esecuzione?

$ ps # Soltanto la Bash è in esecuzione PID TTY TIME CMD 1900 pts/0 00:00:01 bash 2882 pts/0 00:00:00 ps

Eseguiamo 2 coprocessi simultanei:

$ coproc nome1 { # Coprocesso nome1 > while read x > do > echo $x > done; } [1] 2883 $ coproc nome2 { # Coprocesso nome2 > while read y > do > echo $y > done; } bash: avviso: execute_coproc: coproc [2883:nome1] still exists [2] 2884

Evvaiiii! Ha funzionato! Ma davvero ha funzionato? Osserva che, oltre al PID 2883 di nome1, mi ha restituito anche PID 2884, che deve essere quello di nome2. Vediamo:

$ ps PID TTY TIME CMD 1900 pts/0 00:00:01 bash Questo c'era già 2883 pts/0 00:00:00 bash Questo sta eseguendo nome1 2884 pts/0 00:00:00 bash Questo sta eseguendo nome2 2885 pts/0 00:00:00 ps

Sembra che sia stato soltanto un avviso, poiché i due PID generati quando abbiamo avviato i due coprocessi sono attivi. Sottoponiamoli entrambi a una prova:

$ echo xxxxxxxxx >&${nome1[1]} # Invio la stringa a nome1 $ echo yyyyyyyyy >&${nome2[1]} # Invio la stringa a nome2 $ read -u ${nome1[0]} Riceve $ echo $Riceve xxxxxxxxx $ read -u ${nome2[0]} Riceve $ echo $Riceve yyyyyyyyy $ kill $nome1_PID $ kill $nome2_PID

Vettori associativi

A partire dalla Bash 4.0 è stato creato il vettore associativo. Si chiama vettore associativo quel vettore i cui indici sono alfabetici. Le regole che valgono per i vettori interi valgono anche per quelli associativi, benché sia obbligatorio dichiarare questi ultimi prima di valorizzarli.

Esempi:

$ declare -A Animali # Obbligatorio per i vettori associativi $ Animali[cavallo]=domestico $ Animali[zebra]=selvaggio $ Animali[gatto]=domestico $ Animali[tigre]=selvaggio

Pinguim com placa de atenção È impossibile generare tutti gli elementi allo stesso tempo come invece accade nei vettori interi. Stando così le cose, la sintassi non funziona:
Animali=([cavallo]=domestico [zebra]=selvaggio  [gatto]=domestico [tigre]=selvaggio)

$ echo ${Animali[@]} domestico selvaggio domestico selvaggio $ echo ${!Animali[@]} gatto zebra cavallo tigre

Osserva che i valori non sono ordinati, sono immagazzinati nell'ordine in cui sono creati, contrariamente ai vettori interi che sono in ordine numerico.

Supponendo che questi vettori contengano centinaia di elementi, per elencare separatamente gli animali domestici da quelli selvaggi, possiamo buttare giù uno script come questo:

$ cat animale.sh #!/bin/bash # Separa gli animali selvaggi da quelli e domestici declare -A Animali Animali[cavallo]=domestico # Creazione del vettore da testare Animali[zebra]=selvaggio # Creazione del vettore da testare Animali[gato]=domestico # Creazione del vettore da testare Animali[tigre]=selvaggio # Creazione del vettore da testare Animali[orso grigio]=selvaggio # Creazione del vettore da testare for Animale in "${!Animali[@]}" # Lettura del vettore in base all'indice do if [[ "${Animais[$Animal]}" == selvaggio ]] then Sel=("${Sel[@]}" "$Animale") # Creazione del vettore animali selvaggi else Dom=("${Dom[@]}" "$Animale") # Creazione del vettore animali domestici fi done # Operatore condizionale usato per scoprire quale #+ vettore ha più elementi. Vedi i dettagli nella sezione #+ L'interprete aritmetico della Shell Maggiore=$[${#Dom[@]}>${#Sel[@]}?${#Dom[@]}:${#Sel[@]}] clear tput bold; printf "%-15s%-15s\n" Domestici Selvaggi; tput sgr0 for ((i=0; i<$Maggiore; i++)) { tput cup $[1+i] 0; echo ${Dom[i]} tput cup $[1+i] 14; echo ${Sel[i]} }

Vorrei richiamare la tua attenzione su un dettaglio: in questo script ho fatto riferimento a un elemento del vettore associativo utilizzando ${Animali[$Animale]} mentre ho fatto riferimento a un elemento di un vettore intero usando ${Sel[i]}. In breve, quando utilizziamo una variabile come indice di un vettore intero, non c'è bisogno di anteporre come prefisso il simbolo del dollaro($), mentre nel vettore associativo esso è obbligatorio.

Leggere un file da un vettore

Parlando di Bash 4.0, ecco un'altra novità: il comando interno (builtin) mapfile, il cui scopo è inserire un file di testo dentro un vettore senza loop né sostituzione di comandi.

    - Diamine! Questo deve essere molto rapido!

    - Lo è. Fa' una prova!

Esempio:

$ cat frutti avocado mela fragola pera mandarino uva $ mapfile vet < frutti # Invio frutti al vettore vet $ echo ${vet[@]} # Elenco di tutti gli elementi di vet avocado mela fragola pera mandarino uva

Potremmo ottenere lo stesso risultato nel modo seguente:

$ vet=($(cat frutta))

ma girerebbe più lento perché la sostituzione di comandi è eseguita in una sottoshell. Un altro modo di fare la stessa cosa che può venirci subito in mente è leggere il file con l'opzione -a del comando read. Vediamo cosa succede:

$ read -a vet < frutti $ echo ${vet[@]} avocado

Come risulta evidente, è stato letto solamente il primo registro di frutti.

Trasformare minuscolo in maiuscolo e viceversa

Uso dell'espansione di parametro

  • ${parametro^}
  • ${parametro,}
Le espansioni di parametro sono state introdotte nella Bash a partire dalla versione 4.0: esse modificano il formato delle lettere del testo che verrà espanso. Quando usiamo il segno di accento circonflesso (^), l'espansione è fatta da minuscolo a maiuscolo, mentre se usiamo la virgola (,), l'espansione è fatta da maiuscolo a minuscolo.

Esempio:

$ Nome="botelho" $ echo ${Nome^} Botelho $ echo ${Nome^^} BOTELHO $ Nome="botelho carvalho" $ echo ${Nome^} Botelho carvalho # Peccato che non restituisca Botelho Carvalho...

Un frammento di script che può facilitarti la vita:

read -p "Vuoi continuare (s/n)? "
[[ ${REPLY^} == N ]] && exit

In questo modo possiamo evitare di verificare se la risposta data è N (maiuscolo) oppure n (minuscolo).

Uso di declare

Possiamo fare qualcosa di simile anche in un'altra maniera. Per ottenere soltanto lettere maiuscole all'interno di una variabile, possiamo dichiararla utilizzando l'opzione -u ( di uppercase = maiuscola). Osserva:

$ declare -u Maiusc

Una volta dichiarata la variabile, guarda questo esempio:

$ read -p "Digita qualcosa: " Maiusc # La variabile Maiusc riceverà quanto digitato Digita qualcosa: trasforma in maiuscole $ echo $Maiusc TRASFORMA IN MAIUSCOLE

Possiamo ottenere l'inverso con opzione -l (da lowercase = minuscola). Osserva:

$ declare -l Minusc $ read -p "Digita qualcosa: " Minusc # La variabile Maiusc riceverà quanto digitato Tecle algo: TRASFORMA IN MINUSCOLE $ echo $Minusc trasforma in minuscole

Tutto ciò che è stato detto riguardo a queste trasformazioni è valido soltanto dopo l'esecuzione di declare. Osserva:

$ var="xxxx" # Attribuzione prima della dichiarazione $ declare -u var $ echo $var xxxx # Non è cambiato niente, tutto resta minuscolo $ var="xxxx" # Attribuzione dopo la dichiarazione $ echo $var XXXX # Adesso ha funzionato...

Nuove implementazioni del comando case

La Bash 4.0 ha introdotto due nuovi strumenti nel comando case. A partire da questa versione, ci sono altri due indicatori di termine di blocco oltre a ;;: ;;& - Quando un blocco di comandi termina con questo indicatore, il programma non uscirà da case, ma testerà gli standard successivi; ;& - In questo caso, il blocco seguente sarà eseguito senza testarne lo standard.

Esempio:

Osserva questo frammento di codice adattato da http://tldp.org/LDP/abs/html/bashver4.html:

case "$1" in
    [[:print:]] )  echo $1 è un carattere stampabile;;&
    # L'indicatore di termine ;;& testerà il prossimo standard
    [[:alnum:]] )  echo $1 è un caratt. alfa/numerico;;&
    [[:alpha:]] )  echo $1 è un caratt. alfabetico   ;;&
    [[:lower:]] )  echo $1 è una lettera minuscola    ;;&
    [[:digit:]] )  echo $1 è un carattere numerico  ;&
    # L'indicatore di termine ;& eseguirà il blocco successivo...
    %%%@@@@@    )  echo "************************"  ;;
#   ^^^^^^^^  ... anche con uno standard folle.
esac

L'esecuzione di questo codice inserendo 3 come parametro produce quanto segue:

3 è un carattere stampabile 
3 è un caratt. alfa/numerico 
3 è un carattere numerico
********************************

Inserendo m:

m è un carattere stampabile 
m è un caratt. alfa/numerico 
m è un caratt. alfabetico
m è una lettera minuscola

Inserendo / :

/ è un carattere stampabile

Espansione di parentesi graffe

La Bash 4.0 adesso possiede due nuove modi di eseguire l'espansione di parentesi graffe:

  • Sequenza numerica con riempimento di zeri a sinistra;
  • Possibilità di utilizzare un incremento (passo) all'interno di una sequenza numerica.

Esempio:

$ echo {0010..0019} # Riempimento con 2 zeri a sinistra 0010 0011 0012 0013 0014 0015 0016 0017 0018 0019 $ echo {05..15} # Riempimento con zeri a sinistra 05 06 07 08 09 10 11 12 13 14 15 $ echo {-5..19..3} # Incremento di 3 in 3 (Passo 3) -5 -2 1 4 7 10 13 16 19 $ echo {000..100..10} # Riempimento con zeri e passo 10 000 010 020 030 040 050 060 070 080 090 100

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.