15.4. Comandi per l'elaborazione del testo

Comandi riguardanti il testo ed i file di testo

sort

Utility per la classificazione di file, spesso usata come filtro in una pipe. Questo comando ordina un flusso di testo, o un file, in senso crescente o decrescente, o secondo le diverse interpretazioni o posizioni dei caratteri. Usato con l'opzione -m unisce, in un unico file, i file di input precedentemente ordinati. La sua pagina info ne elenca le funzionalità e le molteplici opzioni. Vedi Esempio 10-9, Esempio 10-10 e Esempio A-8.

tsort

Esegue un ordinamento topologico di stringhe lette in coppia secondo i modelli forniti nell'input. Originariamente lo scopo di tsort era quello di ordinare un elenco di dipendenze per una versione obsoleta del linker ld in una "antiquata" versione di UNIX.

I risultati di un tsort solitamente differiscono in modo marcato da quelli del precedente comando sort.

uniq

Questo filtro elimina le righe duplicate di un file che è stato ordinato. È spesso usato in una pipe in coppia con sort.

cat lista-1 lista-2 lista-3 | sort | uniq > listafinale
# Vengono concatenati i file lista, 
# ordinati,
# eliminate le righe doppie,
# ed infine il risultato viene scritto in un file di output.

L'opzione -c premette ad ogni riga del file di input il numero delle sue occorrenze.

bash$ cat fileprova
Questa riga è presente una sola volta.
Questa riga è presente due volte.
Questa riga è presente due volte.
Questa riga è presente tre volte.
Questa riga è presente tre volte.
Questa riga è presente tre volte.


bash$ uniq -c fileprova
      1 Questa riga è presente una sola volta.
      2 Questa riga è presente due volte.
      3 Questa riga è presente tre volte.


bash$ sort fileprova | uniq -c | sort -nr
      3 Questa riga è presente tre volte.
      2 Questa riga è presente due volte.
      1 Questa riga è presente una sola volta.
	      

La sequenza di comandi sort FILEINPUT | uniq -c | sort -nr produce un elenco delle frequenze di occorrenza riferite al file FILEINPUT (le opzioni -nr di sort generano un ordinamento numerico inverso). Questo modello viene usato nell'analisi dei file di log e nelle liste dizionario, od ogni volta che è necessario esaminare la struttura lessicale di un documento.

Esempio 15-11. Analisi di frequenza delle parole

#!/bin/bash
#  wf.sh: Un'analisi sommaria, su un file di testo, della 
#+ frequenza delle parole.
#  È una versione più efficiente dello script "wf2.sh".


# Verifica la presenza di un file di input passato da riga di comando.
ARG=1
E_ERR_ARG=65
E_NOFILE=66

if [ $# -ne "$ARG" ]  #  Il numero di argomenti passati allo script è corretto?
then
  echo "Utilizzo: `basename $0` nomefile"
  exit $E_ERR_ARG
fi

if [ ! -f "$1" ]      # Verifica se il file esiste.
then
  echo "Il file \"$1\" non esiste."
  exit $E_NOFILE
fi



###############################################################################
# main ()
sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
/g' "$1" | tr 'A-Z' 'a-z' | sort | uniq -c | sort -nr
#                           =========================
#                           Frequenza delle occorrenze

#  Filtra i punti e le virgole, e cambia gli spazi tra le parole in
#+ linefeed, quindi trasforma tutti i caratteri in caratteri minuscoli ed
#+ infine premette il conteggio delle occorrenze e le ordina in base al numero.

#  Arun Giridhar suggerisce di modificare il precedente in:
#  . . . | sort | uniq -c | sort +1 [-f] | sort +0 -nr
#  In questo modo viene aggiunta una chiave di ordinamento secondaria, per cui
#+ nel caso di occorrenze uguali queste vengono ordinate alfabeticamente.
#  Ecco la spiegazione:
#  "In effeti si tratta di un ordinamento di radice, prima sulla
#+ colonna meno significativa
#+ (parola o stringa, opzionalmente senza distinzione minuscolo-maiuscolo)
#+ infine sulla colonna più significativa (frequenza)."
#
#  Frank Wang spiega che il precedente equivale a
#+       . . . | sort | uniq -c | sort +0 -nr
#+ così pure il seguente:
#+       . . . | sort | uniq -c | sort -k1nr -k
###############################################################################

exit 0

# Esercizi:
# ---------
# 1)  Aggiungete dei comandi a 'sed' per filtrare altri segni di
#   + punteggiatura, come i punti e virgola.
# 2)  Modificatelo per filtrare anche gli spazi multipli e gli altri
#   + caratteri di spaziatura.

bash$ cat fileprova
 Questa riga è presente una sola volta.
 Questa riga è presente due volte.
 Questa riga è presente due volte.
 Questa riga è presente tre volte.
 Questa riga è presente tre volte.
 Questa riga è presente tre volte.


bash$ ./wf.sh fileprova
       6 riga
       6 questa
       6 presente
       6 è
       5 volte
       3 tre
       2 due
       1 volta
       1 una
       1 sola
	       

expand, unexpand

Il filtro expand trasforma le tabulazioni in spazi. È spesso usato in una pipe.

Il filtro unexpand trasforma gli spazi in tabulazioni. Esegue l'azione opposta di expand.

cut

Strumento per estrarre i campi dai file. È simile alla serie di comandi print $N di awk, ma con capacità più limitate. In uno script è più semplice usare cut che non awk. Particolarmente importanti sono le opzioni -d (delimitatore) e -f (indicatore di campo - field specifier).

Usare cut per ottenere l'elenco dei filesystem montati:

cut -d ' ' -f1,2 /etc/mtab

Uso di cut per visualizzare la versione del SO e del kernel:

uname -a | cut -d" " -f1,3,11,12

Usare cut per estrarre le intestazioni dei messaggi da una cartella e-mail:

bash$ grep '^Subject:' read-messages | cut -c10-80
Re: Linux suitable for mission-critical apps?
 MAKE MILLIONS WORKING AT HOME!!!
 Spam complaint
 Re: Spam complaint

Usare cut per la verifica di un file:

# Elenca tutti gli utenti presenti nel file /etc/passwd.

FILE=/etc/passwd

for utente in $(cut -d: -f1 $FILE)
do
  echo $utente
done

# Grazie, Oleg Philon per il suggerimento.

cut -d ' ' -f2,3 nomefile equivale a awk -F'[ ]' '{ print $2, $3 }' nomefile

Nota

È anche possibile usare l'a_capo come delimitatore. Il trucco consiste nell'inserire veramente un a_capo (INVIO) nella sequenza dei comandi.

bash$ cut -d'
 ' -f3,7,19 filetesto
Questa è la riga 3 di filetesto.
Questa è la riga 7 di filetesto.
Questa è la riga 19 di filetesto.
	      

Grazie a Jaka Kranjc per la precisazione.

Vedi anche Esempio 15-43.

paste

Strumento per riunire più file in un unico file impaginato su diverse colonne. In combinazione con cut è utile per creare file di log di sistema.

join

Lo si può considerare il cugino specializzato di paste. Questa potente utility consente di fondere due file in modo da fornire un risultato estremamente interessante. Crea, in sostanza, una versione semplificata di un database relazionale.

Il comando join opera solo su due file, ma unisce soltanto quelle righe che possiedono una corrispondenza di campo comune (solitamente un'etichetta numerica) e visualizza il risultato allo stdout. I file che devono essere uniti devono essere anche ordinati in base al campo comune, se si vuole che l'abbinamento delle righe avvenga correttamente.

File: 1.dat

100 Scarpe
200 Lacci
300 Calze

File: 2.dat

100 EUR 40.00
200 EUR 1.00
300 EUR 2.00

bash$ join 1.dat 2.dat
File: 1.dat 2.dat

 100 Scarpe EUR 40.00
 200 Lacci  EUR 1.00
 300 Calze  EUR 2.00
	      

Nota

Il campo comune, nell'output, compare una sola volta.

head

visualizza la parte iniziale di un file -- il valore preimpostato è di 10 righe, ma questo valore può essere modificato -- allo stdout. Possiede un certo numero di opzioni interessanti.

Esempio 15-12. Quali file sono degli script?

#!/bin/bash
# script-detector.sh: Rileva gli script presenti in una directory.

VERCAR=2          # Verifica i primi 2 caratteri.
INTERPRETE='#!'   # Gli script iniziano con "#!".

for file in *     # Verifica tutti i file della directory corrente.
do
  if [[ `head -c$VERCAR "$file"` = "$INTERPRETE" ]]
  #      head -c2                   #!
  #  L'opzione '-c' di "head" agisce sul numero di caratteri specificato 
  #+ anziché sulle righe (comportamento di default).
  then
      echo "Il file \"$file\" è uno script."
  else
      echo "Il file \"$file\"  *non* è uno script."
  fi
done
  
exit 0

#  Esercizi:
#  --------
#  1) Modificate lo script in modo che possa avere come argomento opzionale
#+    la directory dove ricercare gli script
#+    (invece della sola directory di lavoro corrente).
#
#  2) Così com'è, lo script rileva dei "falsi positivi" in presenza
#+    di script Perl, awk e di altri linguaggi di scripting.
#     Correggete questa falla.

Esempio 15-13. Generare numeri casuali di 10 cifre

#!/bin/bash
# rnd.sh: Visualizza un numero casuale di 10 cifre

# Script di Stephane Chazelas.

head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'


# ===================================================================== #

# Analisi
# --------

# head:
# l'opzione -c4 considera solamente i primi 4 byte.

# od:
# L'opzione -N4 limita l'output a 4 byte.
# L'opzione -tu4 seleziona, per l'output, il formato decimale senza segno.

# sed:
#  L'opzione -n, in combinazione con l'opzione "p" del comando "s", prende 
#+ in considerazione, per l'output, solo le righe verificate.



# L'autore di questo script spiega l'azione di 'sed' come segue.

# head -c4 /dev/urandom | od -N4 -tu4 | sed -ne '1s/.* //p'
# ----------------------------------> |

# Assumiamo che l'output fino a "sed">|
# sia 0000000 1198195154\n

#  sed inizia leggendo i caratteri: 0000000 1198195154\n.
#  Qui trova il carattere di ritorno a capo, quindi è pronto per elaborare 
#+ la prima riga (0000000 1198195154), che assomiglia alla sua direttiva 
#+ <righe><comandi>. La prima ed unica è

#   righe     comandi
#   1         s/.* //p

#  Il numero della riga è nell'intervallo, quindi entra in azione: 
#+ cerca di sostituire la stringa più lunga terminante con uno spazio 
#+ ("0000000 ") con niente "//" e in caso di successo, visualizza il risultato 
#+ ("p" è l'opzione del comando "s", ed è differente dal comando "p").

#  sed ora continua la lettura dell'input. (Notate che prima di continuare, se 
#+ non fosse stata passata l'opzione -n, sed avrebbe visualizzato la riga 
#+ un'altra volta).

#  sed adesso legge la parte di caratteri rimanente, e trova la fine del file.
#  Si appresta ad elaborare la seconda riga (che può anche essere numerata
#+ con '$' perché è l'ultima).
#  Constata che non è compresa in <righe> e quindi termina il lavoro.

#  In poche parole, questo comando sed significa: "Solo sulla prima riga, togli 
#+ qualsiasi carattere fino allo spazio, quindi visualizza il resto."

# Un modo migliore per ottenere lo stesso risultato sarebbe stato:
#           sed -e 's/.* //;q'

#  Qui abbiamo due <righe> e due <comandi> (si sarebbe potuto anche scrivere
#           sed -e 's/.* //' -e q):

#   righe                       comandi
#   niente (verifica la riga)   s/.* //
#   niente (verifica la riga)   q (quit)

#  In questo esempio, sed legge solo la sua prima riga di input.
#  Esegue entrambi i comandi e visualizza la riga (con la sostituzione) prima 
#+ di uscire (a causa del comando "q"), perché non gli è stata passata 
#+ l'opzione "-n".

# ======================================================================= #

#  Un'alternativa ancor più semplice al precedente script di una sola riga,
#+ potrebbe essere:
#          head -c4 /dev/urandom| od -An -tu4

exit 0
Vedi anche Esempio 15-35.

tail

visualizza la parte finale di un file -- il valore preimpostato è di 10 righe -- allo stdout . Viene comunemente usato per tenere traccia delle modifiche al file di log di sistema con l'uso dell'opzione -f, che permette di visualizzare le righe accodate al file.

Esempio 15-14. Utilizzare tail per controllare il log di sistema

#!/bin/bash

nomefile=sys.log

cat /dev/null > $nomefile; echo "Creazione / cancellazione del file."
#  Crea il file nel caso non esista, mentre lo svuota se è già stato creato.
#  vanno bene anche : > nomefile   e   > nomefile.

tail /var/log/messages > $nomefile
# /var/log/messages deve avere i permessi di lettura perché lo script funzioni.

echo "$nomefile contiene la parte finale del log di sistema."

exit 0

Suggerimento

Per individuare una riga specifica in un file di testo, si colleghi con una pipe l'output di head a tail -n 1. Per esempio head -n 8 database.txt | tail -n 1 rintraccia l'8va riga del file database.txt.

Per impostare una variabile ad un determinato blocco di un file di testo:

var=$(head -n $m $nomefile | tail -n $n)

# nomefile = nome del file
# m = dall'inizio del file, numero di righe mancanti alla fine del blocco
# n = numero di righe a cui va impostata la variabile (dalla fine del blocco)

Nota

Le più recenti implementazioni di tail deprecano l'uso del precedente tail -$RIGHE nomefile. Il comando standard tail -n $RIGHE nomefile è corretto.

Vedi anche Esempio 15-5, Esempio 15-35 e Esempio 29-6.

grep

Strumento di ricerca multifunzione che fa uso delle Espressioni Regolari. In origine era un comando/filtro del venerabile editor di linea ed: g/re/p -- global - regular expression - print.

grep modello [file...]

Ricerca nel/nei file indicato/i l'occorrenza di modello, dove modello può essere o un testo letterale o un'Espressione Regolare.

bash$ grep '[rst]ystem.$' osinfo.txt
The GPL governs the distribution of the Linux operating system.
	      

Se non vengono specificati i file, grep funziona come filtro sullo stdout, come in una pipe.

bash$ ps ax | grep clock
765 tty1     S      0:00 xclock
901 pts/1    S      0:00 grep clock
	      

L'opzione -i abilita una ricerca che non fa distinzione tra maiuscole e minuscole.

L'opzione -w verifica solo le parole esatte.

L'opzione -l elenca solo i file in cui la ricerca ha avuto successo, ma non le righe verificate.

L'opzione -r (ricorsivo) ricerca i file nella directory di lavoro corrente e in tutte le sue sottodirectory.

L'opzione -n visualizza le righe verificate insieme al loro numero.

bash$ grep -n Linux osinfo.txt
2:This is a file containing information about Linux.
6:The GPL governs the distribution of the Linux operating system.
	      

L'opzione -v (o --invert-match) scarta le righe verificate.

grep modello1 *.txt | grep -v modello2

# Verifica tutte le righe dei file "*.txt" contenenti "modello1",
# ma ***non*** quelle contenenti "modello2".

L'opzione -c (--count) fornisce il numero delle occorrenze, ma non le visualizza.

grep -c txt *.sgml   # ((numero di occorrenze di "txt" nei file "*.sgml")


#   grep -cz .
#            ^ punto
# significa conteggio (-c) zero-diviso (-z) elementi da cercare "."
# cioè, quelli non vuoti (contenenti almeno 1 carattere).
#
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz .     # 3
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '$'   # 5
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -cz '^'   # 5
#
printf 'a b\nc  d\n\n\n\n\n\000\n\000e\000\000\nf' | grep -c '$'    # 9
# Per default, i caratteri di a capo (\n) separano gli elementi da cercare.

# Notate che l'opzione -z è specifica del "grep" di GNU.


# Grazie, S.C.

Quando viene invocato con più di un file, grep specifica qual'è il file contenente le occorrenze.

bash$ grep Linux osinfo.txt misc.txt
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
misc.txt:The Linux operating system is steadily gaining in popularity.
	      

Suggerimento

Per forzare grep a visualizzare il nome del file quando ne è presente soltanto uno, si deve indicare come secondo file /dev/null

bash$ grep Linux osinfo.txt /dev/null
osinfo.txt:This is a file containing information about Linux.
osinfo.txt:The GPL governs the distribution of the Linux operating system.
	      

Se la ricerca ha avuto successo, grep restituisce come exit status 0. Questo lo rende utile per un costrutto di verifica in uno script, specialmente in abbinamento con l'opzione -q che sopprime l'output.

SUCCESSO=0                       # se la ricerca di grep è riuscita
parola=Linux
file=file.dati

grep -q "$parola" "$file"
# L'opzione "-q" non visualizza nulla allo stdout.

if [ $? -eq $SUCCESSO ]
# if grep -q "$parola" "$file"   può sostituire le righe 5 - 8.
then
  echo "$parola è presente in $file"
else
  echo "$parola non è presente in $file"
fi

L'Esempio 29-6 dimostra come usare grep per cercare una parola in un file di log di sistema.

Esempio 15-15. Simulare grep in uno script

#!/bin/bash
# grp.sh: Una reimplementazione molto sommaria di 'grep'.

E_ERR_ARG=65

if [ -z "$1" ]    # Verifica se sono stati passati argomenti allo script.
then
  echo "Utilizzo: `basename $0` modello"
  exit $E_ERR_ARG
fi

echo

for file in *     # Verifica tutti i file in $PWD.
do
  output=$(sed -n /"$1"/p $file)  # Sostituzione di comando.

  if [ ! -z "$output" ]           #  Cosa succede se si usa "$output"
                                  #+ senza i doppi apici?
  then
      echo -n "$file: "
      echo $output
  fi              # sed -ne "/$1/s|^|${file}: |p" equivale al precedente.

  echo
done

echo

exit 0

# Esercizi:
# ---------
#  1) Aggiungete nuove righe di output nel caso ci sia più di una
#+    occorrenza per il file dato.
#  2) Aggiungete altre funzionalità.

È possibile far ricercare a grep due (o più) differenti modelli? Cosa si può fare se volessimo che grep visualizzi tutte le righe di un file o i file che contengono sia "modello1" che "modello2"?

Un metodo consiste nel collegare con una pipe il risultato di grep modello1 a grep modello2.

Ad esempio, dato il file seguente:

# File: tstfile

Questo è un file d'esempio.
Questo è un file di testo ordinario.
Questo file non contiene testo strano.
Questo file non è insolito.
Altro testo.

Ora cerchiamo nel file le righe contenenti entrambe le parole "file" e "testo" . . .

 bash$ grep file tstfile
 # File: tstfile
 Questo è un file d'esempio.
 Questo è un file di testo ordinario.
 Questo file non contiene testo strano.
 Questo file non è insolito..

 bash$ grep file tstfile | grep testo
 Questo è un file di testo ordinario.
 Questo file non contiene testo strano.

--

egrep - (extended grep) grep esteso - è uguale a grep -E. Tuttavia usa una serie leggermente diversa ed estesa di Espressioni Regolari che possono rendere la ricerca un po' più flessibile. Permette anche l'uso dell'operatore booleano | (or).

bash $ egrep 'matches|Matches' file.txt
Line 1 matches.
Line 3 Matches.
Line 4 contains matches, but also Matches
              

fgrep -- fast grep grep veloce -- è uguale a grep -F. Esegue la ricerca letterale della stringa (niente Espressioni Regolari), il che solitamente accelera un po' l'operazione.

Nota

In alcune distribuzioni Linux, egrep e fgrep sono link simbolici, o alias, di grep, invocato però con le opzioni -E e -F, rispettivamente.

Esempio 15-16. Cercare una definizione nel Webster's Dictionary ed. 1913

#!/bin/bash
# dict-lookup.sh

#  Questo script ricerca delle definizioni nel Webster's Dictionary ed. 1913.
#  Si tratta di un dizionario di Dominio Pubblico disponibile per il download
#+ presso vari siti, compreso il
#+ Project Gutenberg (http://www.gutenberg.org/etext/247).
#
#  Prima di utilizzarlo va convertito dal formato DOS a quello UNIX 
#+ (solo gli LF a fine riga).
#  Deve essere salvato nel formato testo ASCII non compresso.
#  Impostate la variabile DEFAULT_DICTFILE a percorso/nome_file.


E_ERR_ARG=65
MAXRIGHE=50                               #  Numero massimo di righe 
                                          #+ da visualizzare.
DEFAULT_DICTFILE="/usr/share/dict/webster1913-dict.txt"   
                                          #  Percorso/nome del dizionario 
                                          #+ preimpostato. 
                                          #  Modificatelo se necessario.
#  Nota:
#  ----
#  In questa particolare edizione del 1913 del Webster
#+ ogni voce inizia con una lettera maiuscola
#+ (la parte restante in caratteri minuscoli).
#  Solo la "prima riga" di ciascuna voce inizia in questo modo
#+ ed è per questo motivo che l'algoritmo di ricerca seguente funziona.



if [[ -z $(echo "$1" | sed -n '/^[A-Z]/p') ]]
#  Deve essere specificata almeno una voce da ricercare e
#+ deve iniziare con una lettera maiuscola.
then
  echo "Utilizzo: `basename $0` Voce [file-dizionario]"
  echo
  echo "Nota: La voce da ricercare deve iniziare con una lettera maiuscola,"
  echo "la parte rimanente in minuscolo."
  echo "--------------------------------------------"
  echo "Esempi: Abandon, Dictionary, Marking, ecc."
  exit $E_ERR_ARG
fi


if [ -z "$2" ]                            #  Potete specificare un dizionario
                                          #+ diverso come argomento
                                          #+ dello script.
then
  dictfile=$DEFAULT_DICTFILE
else
  dictfile="$2"
fi

# ---------------------------------------------------------
Definizione=$(fgrep -A $MAXRIGHE "$1 \\" "$dictfile")
#        Definizioni nella forma "Voce \..."
#
#  E, sì, "fgrep" è sufficientemente veloce 
#+ anche nella ricerca di un file di testo molto grande.


# Ora seleziona la parte inirente alla definizione.

echo "$Definizione" |
sed -n '1,/^[A-Z]/p' |
#  Visualizza dalla prima riga della definizione
#+ fino alla prima riga della voce successiva.
sed '$d' | sed '$d'
#  Cancellando le ultime due righe
#+ (la riga vuota e la prima riga della voce successiva).
# ---------------------------------------------------------

exit 0

# Esercizi:
# --------
# 1)  Modificate lo script in modo che accetti un input alfabetico arbitrario
#   + (lettere maiuscole, minuscole o alternate) che verrà convertito
#   + nel formato usato per l'elaborazione.
#
# 2)  Trasformate lo script in un'applicazione GUI,
#   + usando qualcosa tipo "gdialog" . . .
#     Lo script non riceverà più, di conseguenza, lo/gli argomento(i)
#   + da riga di comando.
#
# 3)  Modificate lo script per una verifica in uno degli altri Dizionari
#   + di Dominio Pubblico disponibili, quale il U.S. Census Bureau Gazetteer.

agrep (approximate grep) grep d'approssimazione, estende le capacità di grep per una ricerca per approssimazione. La stringa da ricercare differisce per un numero specifico di caratteri dalle occorrenze effettivamente risultanti. Questa utility non è, di norma, inclusa in una distribuzione Linux.

Suggerimento

Per la ricerca in file compressi vanno usati i comandi zgrep, zegrep o zfgrep. Sebbene possano essere usati anche con i file non compressi, svolgono il loro compito più lentamente che non grep, egrep, fgrep. Sono invece utili per la ricerca in una serie di file misti, alcuni compressi altri no.

Per la ricerca in file compressi con bzip si usa il comando bzgrep.

look

Il comando look opera come grep, ma la ricerca viene svolta in un "dizionario", un elenco di parole ordinate. In modo predefinito, look esegue la ricerca in /usr/dict/words. Naturalmente si può specificare un diverso dizionario.

Esempio 15-17. Verificare la validità delle parole con un dizionario

#!/bin/bash
# lookup: Esegue una verifica di dizionario di tutte le parole di un file dati.

file=file.dati     # File dati le cui parole devono essere controllate.

echo

while [ "$Parola" != fine ] # Ultima parola del file dati.
do
  read parola      #  Dal file dati, a seguito della redirezione a fine ciclo.
  look $parola > /dev/null  #  Per non visualizzare le righe del
                            #+ file dizionario.
  verifica=$?      # Exit status del comando 'look'.

  if [ "$verifica" -eq 0 ]
  then
     echo "\"$parola\" è valida."
  else
     echo "\"$parola\" non è valida."
  fi

done <"$file"      #  Redirige lo stdin a $file, in modo che "read" agisca 
                   #+ su questo.

echo

exit 0

# ----------------------------------------------------------------
#  Le righe di codice seguenti non vengono eseguite a causa del
#+ precedente comando "exit".


# Stephane Chazelas propone la seguente, e più concisa, alternativa:

while read parola && [[ $parola != fine ]]
do if look "$parola" > /dev/null
   then echo "\"$parola\" è valida."
   else echo "\"$parola\" non è valida."
   fi
done <"$file"

exit 0
sed, awk

Linguaggi di scripting particolarmente adatti per la verifica di file di testo e dell'output dei comandi. Possono essere inseriti, singolarmente o abbinati, nelle pipe e negli script di shell.

sed

"Editor di flusso" non interattivo, consente l'utilizzo di molti comandi ex in modalità batch. Viene impiegato principalmente negli script di shell.

awk

Analizzatore e rielaboratore programmabile di file, ottimo per manipolare e/o localizzare campi (colonne) in file di testo strutturati. Ha una sintassi simile a quella del linguaggio C.

wc

wc fornisce il "numero di parole (word count)" presenti in un file o in un flusso I/O:

bash $ wc /usr/share/doc/sed-4.1.2/README
13  70  447 README
[13 lines  70 words  447 characters]

wc -w fornisce solo il numero delle parole.

wc -l fornisce solo il numero di righe.

wc -c fornisce solo il numero dei byte.

wc -m fornisce solo il numero dei caratteri.

wc -L fornisce solo la dimensione della riga più lunga.

Uso di wc per contare quanti file .txt sono presenti nella directory di lavoro corrente:

$ ls *.txt | wc -l
#  Il conteggio si interrompe se viene trovato un carattere di
#+ linefeed nel nome di uno dei file "*.txt".

# Modi alternativi per svolgere lo stesso compito:
#      find . -maxdepth 1 -name \*.txt -print0 | grep -cz .
#      (shopt -s nullglob; set -- *.txt; echo $#)

# Grazie, S.C.

Uso di wc per calcolare la dimensione totale di tutti i file i cui nomi iniziano con le lettere comprese nell'intervallo d - h.

bash$ wc [d-h]* | grep total | awk '{print $3}'
71832
	      

Uso di wc per contare le occorrenze della parola "Linux" nel file sorgente di questo libro.

bash$ grep Linux abs-book.sgml | wc -l
50
	      

Vedi anche Esempio 15-35 e Esempio 19-8.

Alcuni comandi possiedono, sotto forma di opzioni, alcune delle funzionalità di wc.

... | grep foo | wc -l
#  Questo costrutto, frequentemente usato, può essere reso in modo più conciso.

... | grep -c foo
# Un semplice impiego dell'opzione "-c" (o "--count") di grep.

# Grazie, S.C.

tr

filtro per la sostituzione di caratteri.

Attenzione

Si deve usare il "quoting" e/o le parentesi quadre, in modo appropriato. Il quoting evita la reinterpretazione dei caratteri speciali nelle sequenze di comandi tr. Va usato il quoting delle parentesi quadre se si vuole evitarne l'espansione da parte della shell.

Sia tr "A-Z" "*" <nomefile che tr A-Z \* <nomefile cambiano tutte le lettere maiuscole presenti in nomefile in asterischi (allo stdout). Su alcuni sistemi questo potrebbe non funzionare. A differenza di tr A-Z '[**]'.

L'opzione -d cancella un intervallo di caratteri.

echo "abcdef"                 # abcdef
echo "abcdef" | tr -d b-d     # aef


tr -d 0-9 <nomefile
# Cancella tutte le cifre dal file "nomefile".

L'opzione --squeeze-repeats (o -s) cancella tutte le occorrenze di una stringa di caratteri consecutivi, tranne la prima. È utile per togliere gli spazi in eccesso.

bash$ echo "XXXXX" | tr --squeeze-repeats 'X'
X

L'opzione -c "complemento" inverte la serie di caratteri da verificare. Con questa opzione, tr agisce soltanto su quei caratteri che non verificano la serie specificata.

bash$ echo "acfdeb123" | tr -c b-d +
+c+d+b++++

È importante notare che tr riconosce le classi di caratteri POSIX. [1]

bash$ echo "abcd2ef1" | tr '[:alpha:]' -
----2--1
	      

Esempio 15-18. toupper: Trasforma tutte le lettere di un file in maiuscole

#!/bin/bash
# Modifica tutte le lettere del file in maiuscole.

E_ERR_ARG=65

if [ -z "$1" ]  # Verifica standard degli argomenti da riga di comando.
then
  echo "Utilizzo: `basename $0` nomefile"
  exit $E_ERR_ARG
fi

tr a-z A-Z <"$1"

# Stesso effetto del precedente, ma usando la notazione POSIX:
#        tr '[:lower:]' '[:upper:]' <"$1"
# Grazie, S.C.


exit 0

#  Esercizio:
#  Riscrivete lo script in modo che accetti come opzione il nome, "sia" 
#+ in lettere maiuscole che minuscole, del file da madificare, .

Esempio 15-19. lowercase: Cambia in lettere minuscole tutti i nomi dei file della directory corrente

#! /bin/bash
#
#  Cambia ogni nome di file della directory di lavoro in lettere minuscole.
#
#  Ispirato da uno script di John Dubois, 
#+ che è stato tradotto in Bash da Chet Ramey  
#+ e semplificato considerevolmente dall'autore di Guida ABS.


for file in *                    # Controlla tutti i file della directory.
do
   fnome=`basename $file`
   n=`echo $fnome | tr A-Z a-z`  #  Cambia il nome del file in tutte
                                 #+ lettere minuscole.
   if [ "$fnome" != "$n" ]       #  Rinomina solo quei file che non
                                 #+ sono già in minuscolo.
   then
      mv $fnome $n
   fi
done

exit $?


#  Il codice che si trova oltre questa riga non viene eseguito a causa
#+ del precedente "exit".
#---------------------------------------------------------------------#
# Se volete eseguirlo, cancellate o commentate le righe precedenti.

#  Lo script visto sopra non funziona con nomi di file conteneti spazi
#+ o ritorni a capo.
# Stephane Chazelas, quindi, suggerisce l'alternativa seguente:


for file in *        #  Non è necessario usare basename, perché "*" non
                     #+ restituisce i nomi di file contenenti "/".

do n=`echo "$file/" | tr '[:upper:]' '[:lower:]'`
#                    Notazione POSIX dei set di caratteri.
#                    È stata aggiunta una barra, in modo che gli
#                    eventuali ritorni a capo non vengano cancellati
#                    dalla sostituzione di comando.
  # Sostituzione di variabile:
  n=${n%/}           #  Rimuove le barre, aggiunte precedentemente, dal
                     #+ nome del file.

  [[ $file == $n ]] || mv "$file" "$n"

                     # Verifica se il nome del file è già in minuscolo.

done

exit $?

Esempio 15-20. du: Conversione di file di testo DOS al formato UNIX

#!/bin/bash
# Du.sh: converte i file di testo DOS in formato UNIX .

E_ERR_ARG=65

if [ -z "$1" ]
then
  echo "Utilizzo: `basename $0` nomefile-da-convertire"
  exit $E_ERR_ARG
fi

NUOVONOMEFILE=$1.unx

CR='\015'  # Ritorno a capo.
           # 015 è il codice ottale ASCII di CR
           # Le righe dei file di testo DOS terminano con un CR-LF.
           # Le righe dei file di testo UNIX terminano con il solo LF.

tr -d $CR < $1 > $NUOVONOMEFILE
# Cancella i CR e scrive il file nuovo.

echo "Il file di testo originale DOS è \"$1\"."
echo "Il file di testo tradotto in formato UNIX è \"$NOMENUOVOFILE\"."

exit 0

# Esercizio:
#-----------
# Modificate lo script per la conversione inversa (da UNIX a DOS).

Esempio 15-21. rot13: cifratura ultra debole

#!/bin/bash
# rot13.sh: Classico algoritmo rot13, cifratura che potrebbe beffare solo un 
#           bambino di 3 anni.

# Utilizzo: ./rot13.sh nomefile
# o         ./rot13.sh <nomefile
# o         ./rot13.sh e fornire l'input da tastiera (stdin)

cat "$@" | tr 'a-zA-Z' 'n-za-mN-ZA-M'  # "a" corrisponde a "n", "b" a "o", ecc.
#  Il costrutto 'cat "$@"' consente di gestire un input proveniente sia dallo 
#+ stdin che da un file.

exit 0

Esempio 15-22. Generare "Rompicapi Cifrati" di frasi celebri

#!/bin/bash
# crypto-quote.sh: Cifra citazioni

#  Cifra frasi famose mediante una semplice sostituzione monoalfabetica.
#  Il risultato è simile ai rompicapo "Crypto Quote" delle pagine Op Ed
#+ del Sunday.


chiave=ETAOINSHRDLUBCFGJMQPVWZYXK
# La "chiave" non è nient'altro che l'alfabeto rimescolato.
# Modificando la "chiave" cambia la cifratura.

# Il costrutto 'cat "$@"' permette l'input sia dallo stdin che dai file.
# Se si usa lo stdin, l'input va terminato con un Control-D.
# Altrimenti occorre specificare il nome del file come parametro da riga 
# di comando.

cat "$@" | tr "a-z" "A-Z" | tr "A-Z" "$chiave"
#        |  in maiuscolo  |     cifra
# Funziona con frasi formate da lettere minuscole, maiuscole o entrambe.
# I caratteri non alfabetici non vengono modificati.


# Provate lo script con qualcosa di simile a
# "Nothing so needs reforming as other people's habits."
# --Mark Twain
#
# Il risultato è:
# "CFPHRCS QF CIIOQ MINFMBRCS EQ FPHIM GIFGUI'Q HETRPQ."
# --BEML PZERC

# Per decodificarlo:
# cat "$@" | tr "$chiave" "A-Z"


#  Questa semplice cifratura può essere spezzata da un dodicenne con il
#+ semplice uso di carta e penna.

exit 0

#  Esercizio:
#  ---------
#  Modificate lo script in modo che sia in grado sia di cifrare che di
#+ decifrare, in base al(i) argomento(i) passato(i) da riga di comando.
fold

Filtro che dimensiona le righe di input ad una larghezza specificata. È particolarmente utile con l'opzione -s che interrompe le righe in corrispondenza degli spazi tra una parola e l'altra (vedi Esempio 15-23 e Esempio A-1).

fmt

Semplice formattatore di file usato come filtro, in una pipe, per "ridimensionare" lunghe righe di testo per l'output.

Esempio 15-23. Dimensionare un elenco di file

#!/bin/bash

AMPIEZZA=40                 # Ampiezza di 40 colonne.

b=`ls /usr/local/bin`       # Esegue l'elenco dei file...

echo $b | fmt -w $AMPIEZZA

# Si sarebbe potuto fare anche con
#    echo $b | fold - -s -w $AMPIEZZA

exit 0

Vedi anche Esempio 15-5.

Suggerimento

Una potente alternativa a fmt è l'utility par di Kamil Toman, disponibile presso http://www.cs.berkeley.edu/~amc/Par/.

col

Questo filtro, dal nome fuorviante, rimuove i cosiddetti line feed inversi dal flusso di input. Cerca anche di sostituire gli spazi con caratteri di tabulazione. L'uso principale di col è quello di filtrare l'output proveniente da alcune utility di elaborazione di testo, come groff e tbl.

column

Riordina il testo in colonne. Questo filtro trasforma l'output di un testo, che apparirebbe come un elenco, in una "graziosa" tabella, inserendo caratteri di tabulazione in posizioni appropriate.

Esempio 15-24. Utilizzo di column per impaginare un elenco di directory

#!/bin/bash
#  L'esempio seguente corrisponde, con piccole modifiche, a quello
#+ contenuto nella pagina di manuale di "column".

(printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \
; ls -l | sed 1d) | column -t

#  "sed 1d" nella pipe cancella la prima riga di output, che sarebbe 
#+ "total        N", 
#+ dove "N" è il numero totale di file elencati da "ls -l".

# L'opzione -t di "column" visualizza l'output in forma tabellare.

exit 0
colrm

Filtro per la rimozione di colonne. Elimina le colonne (caratteri) da un file. Il risultato viene visualizzato allo stdout. colrm 2 4 <nomefile cancella dal secondo fino al quarto carattere di ogni riga del file di testo nomefile.

Attenzione

Se il file contiene caratteri non visualizzabili, o di tabulazione, il risultato potrebbe essere imprevedibile. In tali casi si consideri l'uso, in una pipe, dei comandi expand e unexpand posti prima di colrm.

nl

Filtro per l'enumerazione delle righe: nl nomefile visualizza nomefile allo stdout inserendo, all'inizio di ogni riga non vuota, il numero progressivo. Se nomefile viene omesso, l'azione viene svolta sullo stdin.

L'output di nl assomiglia molto a quello di cat -b, perché, in modo predefinito, nl non visualizza le righe vuote.

Esempio 15-25. nl: Uno script che numera le proprie righe

#!/bin/bash
# line-number.sh

# Questo script si auto-visualizza due volte con le righe numerate.

#  'nl' considera questa riga come la nr. 4 perché le righe
#+ vuote vengono saltate.
# 'cat -n' vede la riga precedente come la numero 6.

nl `basename $0`

echo; echo  # Ora proviamo con 'cat -n'

cat -n `basename $0`
# La differenza è che 'cat -n' numera le righe vuote.
# Notate che lo stesso risultato lo si ottiene con  'nl -ba'.

exit 0
#-----------------------------------------------------------------
pr

Filtro di formato di visualizzazione. Impagina i file (o lo stdout) in sezioni adatte alla visualizzazione su schermo o per la stampa hard copy. Diverse opzioni consentono la gestione di righe e colonne come, tra l'altro, abbinare e numerare le righe, impostare i margini, aggiungere intestazioni ed unire file. Il comando pr riunisce molte delle funzionalità di nl, paste, fold, column e expand.

pr -o 5 --width=65 fileZZZ | more visualizza sullo schermo una piacevole impaginazione del contenuto del file fileZZZ con i margini impostati a 5 e 65.

L'opzione -d è particolarmente utile per forzare la doppia spaziatura (stesso effetto di sed -G).

gettext

Il pacchetto GNU gettext è una serie di utility per la localizzazione e traduzione dei messaggi di output dei programmi in lingue straniere. Originariamente progettato per i programmi in C, ora supporta diversi linguaggi di scripting e di programmazione.

Il programma gettext viene usato anche negli script di shell. Vedi la relativa pagina info.

msgfmt

Programma per generare cataloghi di messaggi in formato binario. Viene utilizzato per la localizzazione.

iconv

Utility per cambiare la codifica (set di caratteri) del/dei file. Utilizzato principalmente per la localizzazione.

# Converte una stringa dal formato UTF-8 a UTF-16 visualizzandola nella BookList
function scrivi_stringa_utf8 {
    STRINGA=$1
    BOOKLIST=$2
    echo -n "$STRINGA" | iconv -f UTF8 -t UTF16 | cut -b 3- | tr -d \\n >> "$BOOKLIST"
}

#  Dallo script di Peter Knowles "booklistgen.sh"
#+ per la conversione di file nel formato Sony Librie.
#  (http://booklistgensh.peterknowles.com)

recode

Va considerato come la versione più elaborata del precedente iconv. Questa versatile utility viene usata per modificare la codifica di un file. Occorre notare che recode> non fa parte dell'installazione standard di Linux.

TeX, gs

TeX e Postscript sono linguaggi per la composizione di testo usati per preparare copie per la stampa o per la visualizzazione a video.

TeX è l'elaborato sistema di composizione di Donald Knuth. Spesso risulta conveniente scrivere uno script di shell contenente tutte le opzioni e gli argomenti che vanno passati ad uno di questi linguaggi.

Ghostscript (gs) è l'interprete Postscript rilasciato sotto licenza GPL.

texexec

Utility per l'elaborazione di file TeX e pdf. Si trova in /usr/bin su molte distribuzioni Linux, e in realtà si tratta di un shell wrapper che richiama Perl per invocare Tex.

texexec --pdfarrange --result=Concatenato.pdf *pdf
						
#  Concatena tutti i file pdf della directory di lavoro corrente
#+ nel'''unico file Concatenato.pdf . . .
#  (L'opzione  --pdfarrange reimpagina il file pdf. Vedi anche --pdfcombine.)
#  La precedente riga di comando potrebbe essere parametrizzata e
#+ inserita in uno script di shell.

enscript

Utility per la conversione in PostScript di un file in formato testo.

Per esempio, enscript nomefile.txt -p nomefile.ps dà come risultato il file PostScript nomefile.ps.

groff, tbl, eqn

Un altro linguaggio a marcatura e visualizzazione formattata di testo è groff. Si tratta della versione GNU, migliorata, dell'ormai venerabile pacchetto UNIX roff/troff. Le pagine di manuale utilizzano groff.

L'utility per l'elaborazione delle tabelle tbl viene considerata come parte di groff perché la sua funzione è quella di trasformare le istruzioni per la composizione delle tabelle in comandi groff.

Anche l'utility per l'elaborazione di equazioni eqn fa parte di groff e il suo compito è quello di trasformare le istruzioni per la composizione delle equazioni in comandi groff.

Esempio 15-26. manview: visualizzazione formattata di pagine di manuale

#!/bin/bash
#  manview.sh: impagina il sorgente di una pagina di manuale 
#+ per la visualizzazione.

#  Lo script è utile nella fase di scrittura di una pagina di manuale.
#  Permette di controllare i risultati intermedi al volo,
#+ mentre ci si sta lavorando.

E_ERRARG=65

if [ -z "$1" ]
then
  echo "Utilizzo: `basename $0` nomefile"
  exit $E_ERRARG
fi

# ---------------------------
groff -Tascii -man $1 | less
# Dalla pagina di manuale di groff.
# ---------------------------

#  Se la pagina di manuale include tabelle e/o equazioni,
#+ allora il precedente codice non funzionerà.
#  La riga seguente è in grado di gestire tali casi.
#
#   gtbl < "$1" | geqn -Tlatin1 | groff -Tlatin1 -mtty-char -man
#
#   Grazie, S.C.

exit 0
lex, yacc

L'analizzatore lessicale lex genera programmi per la verifica d'occorrenza. Sui sistemi Linux è stato sostituito dal programma non proprietario flex.

L'utility yacc crea un analizzatore lessicale basato su una serie di specifiche. Sui sistemi Linux è stato sostituito dal non proprietario bison.

Note

[1]

Questo è vero solo per la versione GNU di tr, non per la versione generica che si trova spesso sui sistemi commerciali UNIX.