15.2. Comandi complessi

Comandi per utenti avanzati

find

-exec COMANDO \;

Esegue COMANDO su ogni file verificato da find. La sintassi del comando termina con ; (il ";" deve essere preceduto dal carattere di escape per essere certi che la shell lo passi a find col suo significato letterale, evitandone la reinterpretazione come carattere speciale).

bash$ find ~/ -name '*.txt'
/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt
	      

Se COMANDO contiene {}, allora find sostituisce "{}" con il percorso completo del file selezionato.

find ~/ -name 'core*' -exec rm {} \;
# Cancella tutti i file core presenti nella directory home dell'utente.

find /home/bozo/projects -mtime 1
#  Elenca tutti i file della directory /home/bozo/projects
#+ che sono stati modificati il giorno precedente.
#
#  mtime = ora dell'ultima modifica del file in questione
#  ctime = ora dell'ultima modifica di stato (tramite 'chmod' o altro)
#  atime = ora dell'ultimo accesso

DIR=/home/bozo/junk_files
find "$DIR" -type f -atime +5 -exec rm {} \;
#                                      ^^
#  Le parentesi graffe rappresentano il percorso completo prodotto da "find."
#
#  Cancella tutti il file in "/home/bozo/junk_files"
#+ a cui non si è acceduto da almeno 5 giorni.
#
#  "-type tipofile", dove
#  f = file regolare
#  d = directory, ecc.
#  (La pagina di manuale di 'find' contiene l'elenco completo.)

find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;

#  Trova tutti gli indirizzi IP (xxx.xxx.xxx.xxx) nei file della directory /etc.
#  Ci sono alcuni caratteri non essenziali. Come possono essere rimossi?

# Ecco una possibilità:

find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
| grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
#
#  [:digit:] è una delle classi di caratteri
#+ introdotta con lo standard POSIX 1003.2.

# Grazie, Stéphane Chazelas.

Nota

L'opzione -exec di find non deve essere confusa con il builtin di shell exec.

Esempio 15-3. Badname, elimina, nella directory corrente, i file i cui nomi contengono caratteri inappropriati e spazi.

#!/bin/bash
# badname.sh
# Cancella i file nella directory corrente contenenti caratteri inadatti.

for nomefile in *
do
 nomestrano=`echo "$nomefile" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
# Anche in questo modo:
# nomestrano=`echo "$nomefile" | sed -n '/[+{;"\=?~()<>&*|$]/p'`
# Cancella i file contenenti questi caratteri:   + { ; " \ = ? ~ ( ) < > & * | $
#
 rm $nomestrano 2>/dev/null    
#               ^^^^^^^^^^^  Vengono eliminati i messaggi d'errore.
done

# Ora ci occupiamo dei file contenenti ogni tipo di spaziatura.
find . -name "* *" -exec rm -f {} \;
#  Il percorso del file che "find" cerca prende il posto di "{}".
#  La '\' assicura che il ';' sia interpretato correttamente come fine del
#+ comando.

exit 0

#------------------------------------------------------------------------
# I seguenti comandi non vengono eseguiti a causa dell'"exit" precedente.

# Un'alternativa allo script visto prima:
find . -name '*[+{;"\\=?~()<>&*|$ ]*' -exec rm -f '{}' \;
# (Grazie, S.C.)

Esempio 15-4. Cancellare un file tramite il suo numero di inode

#!/bin/bash
# idelete.sh: Cancellare un file per mezzo del suo numero di inode.

#  Questo si rivela utile quando il nome del file inizia con un
#+ carattere scorretto, come ? o -.

CONTA_ARG=1         #  Allo script deve essere passato come argomento
                    #+ il nome del file.
E_ERR_ARG=70
E_FILE_NON_ESISTE=71
E_CAMBIO_IDEA=72

if [ $# -ne "$CONTA_ARG" ]
then
  echo "Utilizzo: `basename $0` nomefile"
  exit $E_ERR_ARG
fi

if [ ! -e "$1" ]
then
  echo "Il file \""$1"\" non esiste."
  exit $E_FILE_NON_ESISTE
fi
inum=`ls -i | grep "$1" | awk '{print $1}'`
#  inum = numero di inode (index node) del file
#  ---------------------------------------------------------------
#  Tutti i file posseggono un inode, la registrazione che contiene
#+ informazioni sull'indirizzo fisico del file stesso.
#  ---------------------------------------------------------------

echo; echo -n "Sei assolutamente sicuro di voler cancellare \"$1\"(s/n)?"
# Anche 'rm' con l'opzione '-v' visualizza la stessa domanda.
read risposta
case "$risposta" in
[nN]) echo "Hai cambiato idea, vero?"
      exit $E_CAMBIO_IDEA
      ;;
*)    echo "Cancello il file \"$1\".";;
esac

find . -inum $inum -exec rm {} \;
#                           ^^
#        Le parentesi graffe sono il segnaposto
#+       per il testo prodotto da "find."
echo "Il file "\"$1"\" è stato cancellato!"

exit 0

Vedi Esempio 15-27, Esempio 3-4 ed Esempio 10-9 per script che utilizzano find. La relativa pagina di manuale fornisce tutti i dettagli di questo potente e complesso comando.

xargs

Un filtro per fornire argomenti ad un comando ed anche uno strumento per assemblare comandi. Suddivide il flusso di dati in parti sufficientemente piccole per essere elaborate da filtri o comandi. Lo si consideri un potente sostituto degli apici inversi. In situazioni in cui la sostituzione di comando potrebbe fallire con il messaggio d'errore too many arguments sostituendola con xargs, spesso, il problema si risolve. [1] Normalmente xargs legge dallo stdin o da una pipe, ma anche dall'output di un file.

Il comando predefinito per xargs è echo. Questo significa che l'input collegato a xargs perde i ritorni a capo o qualsiasi altro carattere di spaziatura.

bash$ ls -l
total 0
 -rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 file1
 -rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 file2


 
bash$ ls -l | xargs
total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file2



bash$ find ~/mail -type f | xargs grep "Linux"
./misc:User-Agent: slrn/0.9.8.1 (Linux)
./sent-mail-jul-2005: hosted by the Linux Documentation Project.
./sent-mail-jul-2005: (Linux Documentation Project Site, rtf version)
./sent-mail-jul-2005: Subject: Criticism of Bozo's Windows/Linux article
./sent-mail-jul-2005: while mentioning that the Linux ext2/ext3 filesystem
. . .	      
	      

ls | xargs -p -l gzip comprime con gzip tutti i file della directory corrente, uno alla volta, ed attende un INVIO prima di ogni operazione.

Suggerimento

Un'interessante opzione di xargs è -n NN, che limita a NN il numero degli argomenti passati.

ls | xargs -n 8 echo elenca i file della directory corrente su 8 colonne.

Suggerimento

Un'altra utile opzione è -0, in abbinamento con find -print0 o grep -lZ. Permette di gestire gli argomenti che contengono spazi o apici.

find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f

grep -rliwZ GUI / | xargs -0 rm -f

Entrambi gli esempi precedenti cancellano tutti i file che contengono "GUI". (Grazie, S.C.)

Esempio 15-5. Creare un file di log utilizzando xargs per verificare i log di sistema

#!/bin/bash

#  Genera un file di log nella directory corrente
#+ partendo dalla fine del file /var/log/messages.

#  Nota: /var/log/messages deve avere i permessi di lettura
#+ nel caso lo script venga invocato da un utente ordinario.
#         #root chmod 644 /var/log/messages

RIGHE=5

( date; uname -a ) >>logfile
# Data e nome della macchina
echo ----------------------------------------------------------- >>logfile
tail -n $RIGHE /var/log/messages | xargs |  fmt -s >>logfile
echo >>logfile
echo >>logfile

exit 0

#  Nota:
#  ----
#  Come ha sottolineato Frank Wang,
#+ gli apici non verificati (siano essi singoli o doppi) nel file sorgente
#+ potrebbero far fare indigestione ad xargs.
#
#  Suggerisce, quindi, di sostituire la riga 15 con la seguente:
#     tail -n $RIGHE /var/log/messages | tr -d "\"'" | xargs | fmt -s >>logfile



# Esercizio:
# ---------
#  Modificate lo script in modo che registri i cambiamenti avvenuti 
#+ in /var/log/messages ad intervalli di venti minuti.
#  Suggerimento: usate il comando "watch".

Come nel caso di find, le due parentesi graffe sostituiscono un testo.

Esempio 15-6. Copiare i file della directory corrente in un'altra

#!/bin/bash
# copydir.sh

#  Copia (con dettagli) tutti i file della directory corrente ($PWD)
#+ nella directory specificata da riga di comando.

E_NOARG=65

if [ -z "$1" ]   # Esce se non viene fornito nessun argomento.
then
  echo "Utilizzo: `basename $0` directory-in-cui-copiare"
  exit $E_NOARG
fi

ls . | xargs -i -t cp ./{} $1
#            ^^ ^^      ^^
#  -t è l'opzione "verbose" (invia la riga di comando allo stderr).
#  -i è l'opzione "sostituisci stringhe".
#  {} è il segnaposto del testo di output.
#  E' simile all'uso di una coppia di parentesi graffe in "find."
#
#  Elenca i file presenti nella directory corrente (ls .),
#+ passa l'output di "ls" come argomenti a "xargs" (opzioni -i -t),
#+ quindi copia (cp) questi argomenti ({}) nella nuova directory ($1).

#  Il risultato finale è l'equivalente esatto di
#    cp * $1
# a meno che qualche nome di file contenga caratteri di "spaziatura".

exit 0

Esempio 15-7. Terminare un processo usando il suo nome

#!/bin/bash
# kill-byname.sh: Terminare i processi tramite i loro nomi.
# Confrontate questo script con kill-process.sh.

#  Ad esempio,
#+ provate "./kill-byname.sh xterm" --
#+ e vedrete scomparire dal vostro desktop tutti gli xterm.

#  Attenzione:
#  ----------
#  Si tratta di uno script veramente pericoloso.
#  Eseguirlo distrattamente (specialmente da root)
#+ pu�causare perdita di dati ed altri effetti indesiderati.

E_NOARG=66

if test -z "$1"  # Nessun argomento fornito da riga di comando?
then
  echo "Utilizzo: `basename $0` Processo(i)_da_terminare"
  exit $E_NOARG
fi


NOME_PROCESSO="$1"
ps ax | grep "$NOME_PROCESSO" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
#                                                        ^^      ^^

# --------------------------------------------------------------------
# Note:
# -i �l'opzione "sostituisci stringhe" di xargs.
# Le parentesi graffe rappresentano il segnaposto per la sostituzione.
# 2&>/dev/null elimina i messaggi d'errore indesiderati.
# --------------------------------------------------------------------

exit $?

#  Il comando "killall" ha lo stesso effetto di questo script,
#+ ma non è altrettanto istruttivo.

Esempio 15-8. Analisi di frequenza delle parole utilizzando xargs

#!/bin/bash
# wf2.sh: Analisi sommaria della frequenza delle parole in un file di testo.

# Usa 'xargs' per scomporre le righe del testo in parole singole.
# Confrontate quest'esempio con lo script "wf.sh" che viene dopo.


# 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



########################################################
cat "$1" | xargs -n1 | \
#  Elenca il file una parola per riga.
tr A-Z a-z | \
#  Cambia tutte le lettere maiuscole in minuscole.
sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
/g' | \
#  Filtra i punti e le virgole, e
#+ cambia gli spazi tra le parole in linefeed.
sort | uniq -c | sort -nr
#  Infine premette il conteggio delle occorrenze e le
#+ ordina in base al numero.
########################################################

#  Svolge lo stesso lavoro dell'esempio "wf.sh",
#+ ma in modo un po' più greve e lento (perché?).

exit 0
expr

Comando multiuso per la valutazione delle espressioni: Concatena e valuta gli argomenti secondo le operazioni specificate (gli argomenti devono essere separati da spazi). Le operazioni possono essere aritmetiche, logiche, su stringhe o confronti.

expr 3 + 5

restituisce 8

expr 5 % 3

restituisce 2

expr 1 / 0

restituisce il messaggio d'errore: expr: divisione per zero

Non è permessa un'operazione aritmetica illecita.

expr 5 \* 3

restituisce 15

L'operatore di moltiplicazione deve essere usato con l'"escaping" nelle espressioni aritmetiche che impiegano expr.

y=`expr $y + 1`

Incrementa la variabile, con lo stesso risultato di let y=y+1 e y=$(($y+1)). Questo è un esempio di espansione aritmetica.

z=`expr substr $stringa $posizione $lunghezza`

Estrae da $stringa una sottostringa di $lunghezza caratteri, iniziando da $posizione.

Esempio 15-9. Utilizzo di expr

#!/bin/bash

# Dimostrazione di alcuni degli usi di 'expr'
# ===========================================

echo

# Operatori aritmetici
# --------- ----------

echo "Operatori aritmetici"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"

a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incremento di variabile)"

a=`expr 5 % 3`
# modulo
echo
echo "5 modulo 3 = $a"

echo
echo

# Operatori logici
# --------- ------

#  Restituisce 1 per vero, 0 per falso,
#+ il contrario della normale convenzione Bash.

echo "Operatori logici"
echo

x=24
y=25
b=`expr $x = $y`         # Verifica l'uguaglianza.
echo "b = $b"            # 0  ( $x -ne $y )
echo

a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, quindi...'
echo "Se a > 10, b = 0 ((falso)"
echo "b = $b"            # 0  ( 3 ! -gt 10 )
echo

b=`expr $a \< 10`
echo "Se a < 10, b = 1 (vero)"
echo "b = $b"            # 1  ( 3 -lt 10 )
echo
# Notate l'uso dell'escaping degli operatori.

b=`expr $a \<= 3`
echo "Se a <= 3, b = 1 (vero)"
echo "b = $b"            # 1  ( 3 -le 3 )
# Esiste anche l'operatore "\>=" (maggiore di o uguale a).


echo
echo



# Operatori per stringhe
# --------- --- --------

echo "Operatori per stringhe"
echo

a=1234zipper43231
echo "La stringa su cui opereremo è \"$a\"."

# length: lunghezza della stringa
b=`expr length $a`
echo "La lunghezza di \"$a\" è $b."

# index: posizione, in stringa, del primo carattere
#        della sottostringa verificato
b=`expr index $a 23`
echo "La posizione numerica del primo \"2\" in \"$a\" è \"$b\"."

#  substr: estrae una sottostringa, iniziando da posizione & lunghezza
#+ specificate
b=`expr substr $a 2 6`
echo "La sottostringa di \"$a\", iniziando dalla posizione 2,\
e con lunghezza 6 caratteri è \"$b\"."


#  Il comportamento preimpostato delle operazioni 'match' è quello
#+ di cercare l'occorrenza specificata all'***inizio*** della stringa.
#
#        usa le Espressioni Regolari
b=`expr match "$a" '[0-9]*'`     #  Conteggio numerico.
echo "Il numero di cifre all'inizio di \"$a\" è $b."
b=`expr match "$a" '\([0-9]*\)'` #  Notate che le parentesi con l'escape
#                   ==      ==   #+ consentono la verifica della sottostringa.
echo "Le cifre all'inizio di \"$a\" sono \"$b\"."

echo

exit 0

Importante

L'operatore : può sostituire match. Per esempio, b=`expr $a : [0-9]*` è l'equivalente esatto di b=`expr match $a [0-9]*` del listato precedente.

#!/bin/bash

echo
echo "Operazioni sulle stringhe usando il costrutto \"expr \$stringa : \""
echo "==================================================="
echo

a=1234zipper5FLIPPER43231

echo "La stringa su cui opereremo è \"`expr "$a" : '\(.*\)'`\"."
# Operatore di raggruppamento parentesi con escape. ==  ==

#       ***************************
#+       Le parentesi con l'escape
#+      verificano una sottostringa
#       ***************************


#  Se non si esegue l'escaping delle parentesi...
#+ allora 'expr' converte l'operando stringa in un intero.

echo "La lunghezza di \"$a\" è `expr "$a" : '.*'`."# Lunghezza della stringa

echo "Il numero di cifre all'inizio di \"$a\" è `expr "$a" : '[0-9]*'`."

# ------------------------------------------------------------------------- #

echo

echo "Le cifre all'inizio di \"$a\" sono `expr "$a" : '\([0-9]*\)'`."
#                                                      ==      ==
echo "I primi 7 caratteri di \"$a\" sono `expr "$a" : '\(.......\)'`."
#       =====                                          ==       ==
# Ancora, le parentesi con l'escape forzano la verifica della sottostringa.
#
echo "Gli ultimi 7 caratteri di \"$a\" sono `expr "$a" : '.*\(.......\)'`."
#         ======               operatore di fine stringa  ^^
#  (in realtà questo vuol dire saltare uno o più caratteri finché non viene
#+ raggiunta la sottostringa specificata)

echo

exit 0

Lo script precedente illustra come expr usa le parentesi con l'escaping -- \( ... \) -- per raggruppare operatori, in coppia con la verifica di espressione regolare, per trovare una sottostringa. Ecco un altro esempio, questa volta preso dal "mondo reale."

# Toglie le spaziature iniziali e finali.
LRFDATE=`expr "$LRFDATE" : '[[:space:]]*\(.*\)[[:space:]]*$'`

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

Perl, sed e awk possiedono strumenti di gran lunga superiori per la verifica delle stringhe. Una breve "subroutine" sed o awk in uno script (vedi la Sezione 33.2) è un'alternativa attraente ad expr.

Vedi la Sezione 9.2 per approfondimenti sull'uso di expr nelle operazioni sulle stringhe.

Note

[1]

E anche quando xargs non sarebbe strettamente necessario, usarlo velocizza l'esecuzione di un comando impegnato nell'elaborazione batch di più file.