Guida avanzata di scripting Bash: Un'approfondita esplorazione dell'arte dello scripting di shell | ||
---|---|---|
Indietro | Capitolo 15. Filtri, programmi e comandi esterni | Avanti |
-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. |
L'opzione |
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.
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.
Un'interessante opzione di xargs
è ls | xargs -n 8 echo elenca i file della directory corrente su 8 colonne. |
Un'altra utile opzione è 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 |
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.
restituisce 8
restituisce 2
restituisce il messaggio d'errore: expr: divisione per zero
Non è permessa un'operazione aritmetica illecita.
restituisce 15
L'operatore di moltiplicazione deve essere usato con l'"escaping" nelle espressioni aritmetiche che impiegano expr.
Incrementa la variabile, con lo stesso risultato di let y=y+1 e y=$(($y+1)). Questo è un esempio di espansione aritmetica.
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 |
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.
|
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.
[1] | E anche quando xargs non sarebbe strettamente necessario, usarlo velocizza l'esecuzione di un comando impegnato nell'elaborazione batch di più file. |