Guida avanzata di scripting Bash: Un'approfondita esplorazione dell'arte dello scripting di shell | ||
---|---|---|
Indietro | Capitolo 15. Filtri, programmi e comandi esterni | Avanti |
Comandi che non possono essere inseriti in nessuna specifica categoria
Queste utility generano una sequenza di interi con un incremento stabilito dall'utente.
Il normale carattere di separazione tra ciascun
intero è il ritorno a capo, che può essere
modificato con l'opzione -s
bash$ seq 5 1 2 3 4 5 bash$ seq -s : 5 1:2:3:4:5 |
Sia jot che seq si rivelano utili in un ciclo for.
Esempio 15-49. Utilizzo di seq per generare gli argomenti di un ciclo
#!/bin/bash # Uso di "seq" echo for a in `seq 80` # oppure for a in $( seq 80 ) # Uguale a for a in 1 2 3 4 5 ... 80 (si risparmia molta digitazione!). #+ Si potrebbe anche usare 'jot' (se presente nel sistema). do echo -n "$a " done # 1 2 3 4 5 ... 80 # Esempio dell'uso dell'output di un comando per generare la [lista] di un #+ ciclo "for". echo; echo CONTO=80 # Sì, 'seq' può avere un parametro. for a in `seq $CONTO` # o for a in $( seq $CONTO ) do echo -n "$a " done # 1 2 3 4 5 ... 80 echo; echo INIZIO=75 FINE=80 for a in `seq $INIZIO $FINE` # Fornendo due argomenti "seq" inizia il conteggio partendo dal primo e #+ continua fino a raggiungere il secondo. do echo -n "$a " done # 75 76 77 78 79 80 echo; echo INIZIO=45 INTERVALLO=5 FINE=80 for a in `seq $INIZIO $INTERVALLO $FINE` # Fornendo tre argomenti "seq" inizia il conteggio partendo dal primo, usa il #+ secondo come passo (incremento) e continua fino a raggiungere il terzo. do echo -n "$a " done # 45 50 55 60 65 70 75 80 echo; echo exit 0 |
Un esempio più semplice:
# Crea 10 file #+ di nome file.1, file.2 . . . file.10. CONTO=10 PREFISSO=file for nomefile in `seq $CONTO` do touch $PREFISSO.$nomefile # O per effettuare altre operazioni, #+ con rm, grep, ecc. done |
Esempio 15-50. Conta lettere
#!/bin/bash # letter-count.sh: Conta le occorrenze di lettere in un file di testo. # Scritto da Stefano Palmeri. # Usato in Guida ABS con il consenso dell'autore. # Leggermente modificato dall'autore del libro. MINARG=2 # Lo script richiede almento due argomenti. E_ERR_ARG=65 FILE=$1 let LETTERE=$#-1 # Quantità di lettere specificate # (come argomenti da riga di comando). # (Sottrae 1 dal numero degli argomenti.) visualizza_help(){ echo echo Utilizzo: `basename $0` file lettere echo Nota: gli argomenti per `basename $0` sono \"case sensitive\". echo Esempio: `basename $0` foobar.txt G n U L i N U x. echo } # Verifica del numero degli argomenti. if [ $# -lt $MINARG ]; then echo echo "Argomenti insufficienti." echo visualizza_help exit $E_ERR_ARG fi # Verifica l'esistenza del file. if [ ! -f $FILE ]; then echo "Il file \"$FILE\" non esiste." exit $E_ERR_ARG fi # Conteggio delle occorrenze. for n in `seq $LETTERE`; do shift if [[ `echo -n "$1" | wc -c` -eq 1 ]]; then # Verifica dell'argomento. echo "$1" -\> `cat $FILE | tr -cd "$1" | wc -c` # Conteggio. else echo "$1 non è un carattere singolo." fi done exit $? # Lo script ha esattamente la stessa funzionalità di letter-count2.sh, #+ ma un'esecuzione più veloce. # Perché? # # [N.d.T.] case sensitive = differenziazione tra lettere minuscole e maiuscole. |
getopt verifica le
opzioni, precedute da un trattino,
passate da riga di comando. È il comando esterno corrispondente
al builtin di Bash getopts.
getopt, usato con l'opzione -l
,
permette la gestione delle opzioni estese nonché il
riordino dei parametri.
Esempio 15-51. Utilizzo di getopt per analizzare le opzioni passate da riga di comando
#!/bin/bash # Usare getopt # Provate ad invocare lo script nei modi seguenti: # sh ex33a.sh -a # sh ex33a.sh -abc # sh ex33a.sh -a -b -c # sh ex33a.sh -d # sh ex33a.sh -dXYZ # sh ex33a.sh -d XYZ # sh ex33a.sh -abcd # sh ex33a.sh -abcdZ # sh ex33a.sh -z # sh ex33a.sh a # Spiegate i risultati di ognuna delle precedenti esecuzioni. E_ERR_OPZ=65 if [ "$#" -eq 0 ] then # Lo script richiede almeno un argomento da riga di comando. echo "Utilizzo $0 -[opzioni a,b,c]" exit $E_ERR_OPZ fi set -- `getopt "abcd:" "$@"` # Imposta come parametri posizionali gli argomenti passati da riga di comando. # Cosa succede se si usa "$*" invece di "$@"? while [ ! -z "$1" ] do case "$1" in -a) echo "Opzione \"a\"";; -b) echo "Opzione \"b\"";; -c) echo "Opzione \"c\"";; -d) echo "Opzione \"d\" $2";; *) break;; esac shift done # Solitamente in uno script è meglio usare il builtin 'getopts', #+ piuttosto che 'getopt'. # Vedi "ex33.sh". exit 0 |
Per una simulazione semplificata di getopt vedi Esempio 9-13.
Il comando run-parts [1] esegue tutti gli script presenti nella directory di riferimento, sequenzialmente ed in ordine alfabetico. Naturalmente gli script devono avere i permessi di esecuzione.
Il demone cron invoca run-parts per eseguire gli script presenti nelle directory /etc/cron.*
Il comportamento predefinito del comando yes è quello di inviare allo stdout una stringa continua del carattere y seguito da un ritorno a capo. Control-c termina l'esecuzione. Può essere specificata una diversa stringa di output, come yes altra stringa che visualizzerà in continuazione altra stringa allo stdout.
Ci si potrebbe chiedere lo scopo di tutto questo. Sia da riga di comando che in uno script, l'output di yes può essere rediretto, o collegato per mezzo di una pipe, ad un programma che è in attesa di un input dell'utente. In effetti, diventa una specie di versione povera di expect
yes | fsck /dev/hda1 esegue fsck in modalità non-interattiva (attenzione!).
yes | rm -r nomedir ha lo stesso effetto di rm -rf nomedir (attenzione!).
Si faccia soprattutto attenzione quando si collega, con una pipe, yes ad un comando di sistema potenzialmente pericoloso come fsck o fdisk. Potrebbero esserci degli effetti collaterali imprevisti. |
Il comando yes analizza le variabili. Per esempio:
Questa "funzionalità" non è particolarmente utile. |
Visualizza gli argomenti allo stdout in forma di un ampio banner verticale, utilizzando un carattere ASCII (di default '#'), che può essere rediretto alla stampante per un hardcopy.
Visualizza tutte le variabili d'ambiente di un particolare utente.
bash$ printenv | grep HOME HOME=/home/bozo |
I comandi lp e lpr inviano uno o più file alla coda di stampa per l'hardcopy. [2] I nomi di questi comandi derivano da "line printer", stampanti di un'altra epoca.
bash$ lp file1.txt o bash lp <file1.txt
Risulta spesso utile collegare a lp, con una pipe, l'output impaginato con pr.
bash$ pr -opzioni file1.txt | lp
Pacchetti per l'impaginazione del testo, quali groff e Ghostscript, possono inviare direttamente i loro output a lp.
bash$ groff -Tascii file.tr | lp
bash$ gs -opzioni | lp file.ps
Comandi correlati sono lpq, per visualizzare la coda di stampa, e lprm, per cancellare i job dalla coda di stampa.
[UNIX prende a prestito un'idea dall'idraulica.]
È un operatore di redirezione, ma con una differenza. Come il raccordo a ti (T) dell'idraulico, consente di "deviare" in un file l'output di uno o più comandi di una pipe, senza alterarne il risultato. È utile per registrare in un file, o in un documento, il comportamento di un processo, per tenerne traccia a scopo di debugging.
(redirezione) |----> al file | =============================================== comando ---> comando ---> |tee ---> comando ---> ---> risultato della pipe =============================================== |
cat elencofile* | sort | tee file.verifica | uniq > file.finale |
(Il file file.verifica contiene i file ordinati e concatenati di "elencofile", prima che le righe doppie vengano cancellate da uniq.)
Questo misterioso comando crea una named pipe, un buffer first-in-first-out temporaneo, per il trasferimento di dati tra processi. [3] Tipicamente, un processo scrive nel FIFO e un altro vi legge. Vedi Esempio A-15.
#!/bin/bash # Breve script di Omair Eshkenazi. # Utilizzato in Guida ASB con il consenso dell'autore (grazie!). mkfifo pipe1 mkfifo pipe2 (cut -d' ' -f1 | tr "a-z" "A-Z") >pipe2 <pipe1 & ls -l | tr -s ' ' | cut -d' ' -f3,9- | tee pipe1 | cut -d' ' -f2 | paste - pipe2 rm -f pipe1 rm -f pipe2 # Non è più necessario terminare i processi in esecuzione in #+ background quando lo script termina (perché?). exit $? Ora eseguiamo lo script e controlliamo l'output: sh mkfifo-example.sh 4830.tar.gz BOZO pipe1 BOZO pipe2 BOZO mkfifo-example.sh BOZO Mixed.msg BOZO |
Questo comando verifica la validità del nome di un file. Viene visualizzato un messaggio d'errore nel caso in cui il nome del file ecceda la lunghezza massima consentita (255 caratteri), oppure quando una o più delle directory del suo percorso non vengono trovate.
Purtroppo pathchk non restituisce un codice d'errore riconoscibile e quindi è praticamente inutile in uno script. Si prendano in considerazione, al suo posto, gli operatori di verifica di file.
Questo è l'alquanto oscuro e molto temuto comando di duplicazione dati. Sebbene in origine fosse una utility per lo scambio di dati contenuti su nastri magnetici tra minicomputer UNIX e mainframe IBM, nondimeno viene tuttora utilizzata. Il comando dd copia semplicemente un file (o lo stdin/stdout), ma con delle conversioni. Le conversioni possibili sono ASCII/EBCDIC, [4] maiuscolo/minuscolo, scambio di copie di byte tra input e output, e saltare e/o troncare la parte iniziale o quella finale di un file di input. dd --help elenca le conversioni e tutte le altre opzioni disponibili per questa potente utility.
# Convertire in lettere maiuscole il contenuto di un file: dd if=$nomefile conv=ucase > $nomefile.maiuscolo # lcase # Per la conversione in minuscolo |
Esempio 15-52. Uno script che copia sè stesso
#!/bin/bash # self-copy.sh # Questo script copia se stesso. suffisso_file=copia dd if=$0 of=$0.$suffisso_file 2>/dev/null # Sopprime i messaggi di dd: ^^^^^^^^^^^ exit $? |
Esempio 15-53. Esercitarsi con dd
#!/bin/bash # exercising-dd.sh # Script di Stephane Chazelas. # Con qualche modifica eseguita dall'autore del libro. file_input=$0 # Questo script. file_output=log.txt n=3 p=5 dd if=$file_input of=$file_output bs=1 skip=$((n-1)) count=$((p-n+1)) 2> /dev/null # Toglie i caratteri da n a p dallo script. # ------------------------------------------------------- echo -n "ciao mondo" | dd cbs=1 conv=unblock 2> /dev/null # Visualizza "ciao mondo" verticalmente. exit 0 |
Per dimostrare quanto versatile sia dd, lo si può usare per catturare i tasti premuti.
Esempio 15-54. Intercettare i tasti premuti
#!/bin/bash # dd-keypress.sh: Intercetta i tasti premuti senza dover premere anche INVIO. tastidapremere=4 # Numero di tasti da catturare. precedenti_impostazioni_tty=$(stty -g) # Salva le precedenti #+ impostazioni del terminale. echo "Premi $tastidapremere tasti." stty -icanon -echo # Disabilita la modalità canonica. # Disabilita l'eco locale. tasti=$(dd bs=1 count=$tastidapremere 2> /dev/null) # 'dd' usa lo stdin, se non viene specificato "fi" (file input). stty "$precedenti_impostazioni_tty" # Ripristina le precedenti impostazioni. echo "Hai premuto i tasti \"$tasti\"." # Grazie a Stephane Chazelas per la dimostrazione. exit 0 |
Il comando dd può eseguire un accesso casuale in un flusso di dati.
echo -n . | dd bs=1 seek=4 of=file conv=notrunc # L'opzione "conv=notrunc" significa che il file di output non verrà troncato. # Grazie, S.C. |
Il comando dd riesce a copiare dati grezzi e immagini di dischi su e dai dispositivi, come floppy e dispositivi a nastro (Esempio A-5). Un uso comune è quello per creare dischetti di boot.
dd if=immagine-kernel of=/dev/fd0H1440
In modo simile, dd può copiare l'intero contenuto di un floppy, persino di uno formattato su un SO "straniero" , sul disco fisso come file immagine.
dd if=/dev/fd0 of=/home/bozo/projects/floppy.img
Altre applicazioni di dd comprendono l'inizializzazione di file di swap temporanei (Esempio 28-2) e di ramdisk (Esempio 28-3). Può anche eseguire una copia di basso livello di un'intera partizione di un disco fisso, sebbene ciò non sia particolarmente raccomandabile.
Ci sono persone (che presumibilmente non hanno niente di meglio da fare con il loro tempo) che pensano costantemente ad applicazioni interessanti di dd.
Esempio 15-55. Cancellare in modo sicuro un file
#!/bin/bash # blot-out.sh: Cancella "ogni" traccia del file. # Questo script sovrascrive il file di riferimento alternativamente con byte #+ casuali e con zeri, prima della cancellazione finale. # Dopo di che, anche un esame diretto dei settori del disco, usando i metodi #+ convenzionali, non riuscirà a rivelare i dati originari del file. PASSI=7 # Numero di sovrascritture. # Aumentando questo valore si rallenta l'esecuzione dello #+ script, specialmente con i file di grandi dimensioni. DIMBLOCCO=1 # L'I/O con /dev/urandom richiede di specificare la dimensione #+ del blocco, altrimenti si ottengono risultati strani. E_ERR_ARG=70 # Codice d'uscita per errori generici. E_FILE_NON_TROVATO=71 E_CAMBIO_IDEA=72 if [ -z "$1" ] # Nessun nome di file specificato. then echo "Utilizzo: `basename $0` nomefile" exit $E_ERR_ARG fi file=$1 if [ ! -e "$file" ] then echo "Il file \"$file\" non è stato trovato." exit $E_FILE_NON_TROVATO fi echo; echo -n "Sei assolutamente sicuro di voler cancellare \"$file\" (s/n)? " read risposta case "$risposta" in [nN]) echo "Hai cambiato idea, vero?" exit $E_CAMBIO_IDEA ;; *) echo "Cancellazione del file \"$file\".";; esac dim_file=$(ls -l "$file" | awk '{print $5}') # Il 5 campo è la dimensione #+ del file. conta_passi=1 chmod u+w "$file" # Consente di sovrascrivere/cancellare il file. echo while [ "$conta-passi" -le "$PASSI" ] do echo "Passaggio nr.$conta_passi" sync # Scarica i buffer. dd if=/dev/urandom of=$file bs=$DIMBLOCCO count=$dim_file # Sovrascrive con byte casuali. sync # Scarica ancora i buffer. dd if=/dev/zero of=$file bs=$DIMBLOCCO count=$dim_file # Sovrascrive con zeri. sync # Scarica ancora una volta i buffer. let "conta_passi += 1" echo done rm -f $file # Infine, cancella il file. sync # Scarica i buffer un'ultima volta. echo "Il file \"$file\" è stato cancellato."; echo exit 0 # È un metodo abbastanza sicuro, sebbene lento ed inefficiente, per rendere un #+ file completamente "irriconoscibile". # Il comando "shred", che fa parte del pacchetto GNU "fileutils", esegue lo #+ stesso lavoro, ma in maniera molto più efficiente. # La cancellazione non può essere "annullata" né il file recuperato con i #+ metodi consueti. # Tuttavia . . . #+ questo semplice metodo probabilmente *non* resisterebbe #+ ad una sofisticata analisi forense. # Questo script potrebbe non funzionare correttamente con un file system journaled. # Esercizio (difficile): risolvete questo problema. # Il pacchetto per la cancellazione sicura di file "wipe" di Tom Vier esegue #+ un lavoro molto più completo di quanto non faccia questo semplice script. # http://www.ibiblio.org/pub/Linux/utils/file/wipe-2.0.0.tar.bz2 # Per un'analisi approfondita sull'argomento della cancellazione sicura dei #+ file, vedi lo studio di Peter Gutmann, #+ "Secure Deletion of Data From Magnetic and Solid-State Memory". # http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html |
Il filtro od, ovvero octal dump, converte l'input (o i file) in formato ottale (base-8) o in altre basi. È utile per visualizzare o elaborare file dati binari o file di dispositivi di sistema altrimenti illeggibili, come /dev/urandom, e come filtro per i dati binari. Vedi Esempio 9-29 e Esempio 15-13.
Esegue la conversione in esadecimale, ottale, decimale o ASCII di un file binario. Questo comando è grosso modo equivalente ad od, visto prima, ma non altrettanto utile.
Visualizza informazioni su un file oggetto, o un binario eseguibile,
sia in formato esadecimale che come listato assembly
(con l'opzione -d
).
bash$ objdump -d /bin/ls /bin/ls: file format elf32-i386 Disassembly of section .init: 080490bc <.init>: 80490bc: 55 push %ebp 80490bd: 89 e5 mov %esp,%ebp . . . |
Questo comando genera un "magic cookie", un numero esadecimale pseudocasuale di 128-bit (32-caratteri), normalmente usato come "firma" di autenticazione dal server X. È disponibile anche per gli script come mezzo "sbrigativo" per ottenere un numero casuale.
random000=$(mcookie) |
Naturalmente, uno script potrebbe utilizzare per lo stesso scopo md5.
# Genera una checksum md5 dello script stesso. random001=`md5sum $0 | awk '{print $1}'` # Usa 'awk' per eliminare il nome del file. |
Il comando mcookie fornisce un altro metodo, ancora, per generare un nome di file "univoco" .
Esempio 15-56. Generatore di nomi di file
#!/bin/bash # tempfile-name.sh: generatore di nomi di file temporanei STR_BASE=`mcookie` # magic cookie di 32-caratteri. POS=11 # Posizione arbitraria nella stringa magic cookie. LUN=5 # Ottiene $LUN caratteri consecutivi. prefisso=temp # È, dopo tutto, un file "temporaneo". # Per una maggiore "unicità", generate il prefisso del #+ nome del file usando lo stesso metodo del #+ suffisso, di seguito. suffisso=${STR_BASE:POS:LUN} # Estrae una stringa di 5-caratteri, iniziando dall'11a #+ posizione. nomefile_temp=$prefisso.$suffisso # Crea il nome del file. echo "Nome del file temporaneo = "$nomefile_temp"" # sh tempfile-name.sh # Nome del file temporaneo = temp.e19ea # Confrontate questa tecnica per generare nomi di file "univoci" #+ con il metodo 'date' usato in ex51.sh. exit 0 |
Questa utility esegue la conversione tra differenti unità di misura. Sebbene normalmente venga invocata in modalità interattiva, units può essere utilizzata anche in uno script.
Esempio 15-57. Convertire i metri in miglia
#!/bin/bash # unit-conversion.sh converte_unità () # Vuole come argomenti le unità da convertire. { cf=$(units "$1" "$2" | sed --silent -e '1p' | awk '{print $2}') # Toglie tutto tranne il reale fattore di conversione. echo "$cf" } Unità1=miglia Unità2=metri fatt_conv =`converte_unità $Unità1 $Unità2` quantità=3.73 risultato=$(echo $quantità*$fatt_conv | bc) echo "Ci sono $risultato $Unità2 in $quantità $Unità1." # Cosa succede se vengono passate alla funzione unità di misura #+ incompatibili, come "acri" e "miglia"? exit 0 |
Un tesoro nascosto, m4 è un potente filtro per l'elaborazione di macro, [5] virtualmente un linguaggio completo. Quantunque scritto originariamente come pre-processore per RatFor, m4 è risultato essere utile come utility indipendente. Infatti, m4 combina alcune delle funzionalità di eval, tr e awk con le sue notevoli capacità di espansione di macro.
Nel numero dell'aprile 2002 di Linux Journal vi è un bellissimo articolo su m4 ed i suoi impieghi.
Esempio 15-58. Utilizzo di m4
#!/bin/bash # m4.sh: Uso del processore di macro m4 # Stringhe stringa=abcdA01 echo "len($stringa)" | m4 # 7 echo "substr($stringa,4)" | m4 # A01 echo "regexp($stringa,[0-1][0-1],\&Z)" | m4 # 01Z # Calcoli aritmetici echo "incr(22)" | m4 # 23 echo "eval(99 / 3)" | m4 # 33 exit 0 |
Il comando doexec abilita il passaggio
di un elenco di argomenti, di lunghezza arbitraria, ad un binario
eseguibile. In particolare, passando
argv[0]
(che corrisponde a $0 in uno script), permette che
l'eseguibile possa essere invocato con nomi differenti e svolgere una
serie di azioni diverse, in accordo col nome con cui l'eseguibile è stato
posto in esecuzione. Quello che si ottiene è un
metodo indiretto per passare delle opzioni ad un eseguibile.
Per esempio, la directory /usr/local/bin potrebbe contenere un binario di nome "aaa". Eseguendo doexec /usr/local/bin/aaa list verrebbero elencati tutti quei file della directory di lavoro corrente che iniziano con una "a", mentre (lo stesso eseguibile) con doexec /usr/local/bin/aaa delete quei file verrebbero cancellati.
I diversi comportamenti dell'eseguibile devono essere definiti nel codice dell'eseguibile stesso, qualcosa di analogo al seguente script di shell:
|
La famiglia di strumenti dialog fornisce un mezzo per richiamare, da uno script, finestre di "dialogo" interattive. Le varianti più elaborate di dialog -- gdialog, Xdialog e kdialog -- in realtà invocano i widget X-Windows. Vedi Esempio 33-19.
Il comando sox, ovvero "sound exchange", permette di ascoltare i file audio e anche di modificarne il formato. Infatti l'eseguibile /usr/bin/play (ora deprecato) non è nient'altro che uno shell wrapper per sox
Per esempio, sox fileaudio.wav fileaudio.au trasforma un file musicale dal formato WAV al formato AU (audio Sun).
Gli script di shell sono l'ideale per eseguire in modalità batch operazioni sox sui file audio. Per alcuni esempi, vedi il Linux Radio Timeshift HOWTO e l'MP3do Project.
[1] | In realtà si tratta dell'adattamento di uno script della distribuzione Debian GNU/Linux. | |
[2] | Per coda di stampa si intende l'insieme dei job "in attesa" di essere stampati. | |
[3] | Per un'eccellente disamina di quest'argomento vedi l'articolo di Andy Vaught, Introduction to Named Pipes, nel numero del Settembre 1997 di Linux Journal. | |
[4] | EBCDIC (pronunciato
"ebb-sid-ick") è l'acronimo di
Extended Binary Coded Decimal Interchange Code.
È un formato dati IBM non più molto
usato. Una bizzarra applicazione dell'opzione
| |
[5] | Una macro è una costante simbolica che si espande in un comando o in una serie di operazioni sui parametri. |