Guida avanzata di scripting Bash: Un'approfondita esplorazione dell'arte dello scripting di shell | ||
---|---|---|
Indietro | Avanti |
Here and now, boys. | |
Aldous Huxley, "Island" |
Un here document è un blocco di codice con una funzione specifica. Utilizza una particolare forma di redirezione I/O per fornire un elenco di comandi a un programma o a un comando interattivi, come ftp, cat o l'editor di testo ex.
COMANDO <<InputArrivaDaQui(HERE) ... InputArrivaDaQui(HERE) |
Una stringa limite delimita (incornicia) l'elenco dei comandi. Il simbolo speciale << indica la stringa limite. Questo ha come effetto la redirezione dell'output di un file nello stdin del programma o del comando. È simile a programma-interattivo < file-comandi, dove file-comandi contiene
comando nr.1 comando nr.2 ... |
L'alternativa rappresentata da un here document è la seguente:
#!/bin/bash programma-interattivo <<StringaLimite comando nr.1 comando nr.2 ... StringaLimite |
Si scelga, per la stringa limite, un nome abbastanza insolito in modo che non ci sia la possibilità che lo stesso nome compaia accidentalmente nell'elenco dei comandi presenti nel here document e causare confusione.
Si noti che gli here document possono, talvolta, essere usati efficacemente anche con utility e comandi non interattivi come, ad esempio, wall.
Esempio 18-1. broadcast: invia un messaggio a tutti gli utenti connessi
#!/bin/bash wall <<zzz23FineMessaggiozzz23 Inviate per e-mail gli ordini per la pizza di mezzogiorno all'amministratore di sistema. (Aggiungete un euro extra se la volete con acciughe o funghi.) # Il testo aggiuntivo del messaggio va inserito qui. # Nota: 'wall' visualizza le righe di commento. zzz23FIneMessaggiozzz23 # Si sarebbe potuto fare in modo più efficiente con # wall <file-messaggio # Comunque, inesrire un messaggio campione in uno script è una soluzione #+ rapida-e-grezza definitiva. exit 0 |
Persino candidati improbabili come vi si prestano ad essere impiegati negli here document.
Esempio 18-2. File di prova: crea un file di prova di due righe
#!/bin/bash # Uso non interattivo di 'vi' per scrivere un file. # Simula 'sed'. E_ERR_ARG=65 if [ -z "$1" ] then echo "Utilizzo: `basename $0` nomefile" exit $E_ERR_ARG fi FILE=$1 # Inserisce 2 righe nel file, quindi lo salva. #--------Inizio here document----------------# vi $FILE <<x23StringaLimitex23 i Questa è la riga 1 del file d'esempio. Questa è la riga 2 del file d'esempio. ^[ ZZ x23StringaLimitex23 #----------Fine here document----------------# # Notate che i caratteri ^[ corrispondono alla #+ digitazione di Control-V <Esc>. # Bram Moolenaar fa rilevare che questo potrebbe non funzionare con 'vim', #+ a causa di possibili problemi di interazione con il terminale. exit 0 |
Lo script precedente si potrebbe, semplicemente ed efficacemente, implementare con ex, invece che con vi. Gli here document che contengono una lista di comandi ex sono abbastanza comuni e formano una specifica categoria a parte, conosciuta come ex script.
#!/bin/bash # Sostituisce tutte le occorrenze d "Smith" con "Jones" #+ nei file i cui nomi abbiano il suffisso ".txt". ORIGINALE=Smith SOSTITUTO=Jones for parola in $(fgrep -l $ORIGINALE *.txt) do # --------------------------------------------- ex $parola <<EOF :%s/$ORIGINALE/$SOSTITUTO/g :wq EOF # :%s è la sostituzione di comando di "ex". # :wq significa write-and-quit (scrivi ed esci). # ---------------------------------------------- done |
Analoghi agli "script ex" sono gli script cat.
Esempio 18-3. Messaggio di più righe usando cat
#!/bin/bash # 'echo' è ottimo per visualizzare messaggi di una sola riga, #+ ma diventa problematico per messaggi più lunghi. # Un here document 'cat' supera questa limitazione. cat <<Fine-messaggio ------------------------------------- Questa è la riga 1 del messaggio. Questa è la riga 2 del messaggio. Questa è la riga 3 del messaggio. Questa è la riga 4 del messaggio. Questa è l'ultima riga del messaggio. ------------------------------------- Fine-messaggio # Sostituendo la precedente riga 7 con #+ cat > $Nuovofile <<Fine-messaggio #+ ^^^^^^^^^^^^ #+ l'output viene scritto nel file $Nuovofile invece che allo stdout. exit 0 #-------------------------------------------- # Il codice che segue non viene eseguito per l'"exit 0" precedente. # S.C. sottolinea che anche la forma seguente funziona. echo "------------------------------------- Questa è la riga 1 del messaggio. Questa è la riga 2 del messaggio. Questa è la riga 3 del messaggio. Questa è la riga 4 del messaggio. Questa è l'ultima riga del messaggio. --------------------------------------------" # Tuttavia, il testo non dovrebbe contenere doppi apici privi #+ del carattere di escape. |
L'opzione -
alla stringa limite del here
document (<<-StringaLimite)
elimina, nell'output, i caratteri di tabulazione iniziali (ma non gli spazi).
Può essere utile per rendere lo script più leggibile.
Esempio 18-4. Messaggio di più righe con cancellazione dei caratteri di tabulazione
#!/bin/bash # Uguale all'esempio precedente, ma... # L'opzione - al here document <<- #+ sopprime le tabulazioni iniziali nel corpo del documento, #+ ma *non* gli spazi. cat <<-FINEMESSAGGIO Questa è la riga 1 del messaggio. Questa è la riga 2 del messaggio. Questa è la riga 3 del messaggio. Questa è la riga 4 del messaggio. Questa è l'ultima riga del messaggio. FINEMESSAGGIO # L'output dello script viene spostato a sinistra. # Le tabulazioni iniziali di ogni riga non vengono mostrate. # Le precedenti 5 righe del "messaggio" sono precedute da #+ tabulazioni, non da spazi. # Gli spazi non sono interessati da <<-. # Notate che quest'opzione non ha alcun effetto sulle tabulazioni *incorporate* exit 0 |
Un here document supporta la sostituzione di comando e di parametro. È quindi possibile passare diversi parametri al corpo del here document e modificare, conseguentemente, il suo output.
Esempio 18-5. Here document con sostituzione di parametro
#!/bin/bash # Un altro here document 'cat' che usa la sostituzione di parametro. # Provatelo senza nessun parametro da riga di comando, ./nomescript # Provatelo con un parametro da riga di comando, ./nomescript Mortimer # Provatelo con un parametro di due parole racchiuse tra doppi apici, # ./nomescript "Mortimer Jones" RIGACMDPARAM=1 # Si aspetta almeno un parametro da riga di comando. if [ $# -ge $RIGACMDPARAM ] then NOME=$1 # Se vi è più di un parametro, #+ tiene conto solo del primo. else NOME="John Doe" # È il nome predefinito, se non si passa alcun parametro. fi RISPONDENTE="l'autore di questo bello script" cat <<Finemessaggio Ciao, sono $NOME. Salute a te $NOME, $RISPONDENTE. # Questo commento viene visualizzato nell'output (perché?). Finemessaggio # Notate che vengono visualizzate nell'output anche le righe vuote. # Così si fa un "commento". exit 0 |
Quello che segue è un utile script contenente un here document con sostituzione di parametro.
Esempio 18-6. Caricare due file nella directory incoming di Sunsite
#!/bin/bash # upload.sh # Carica due file (Nomefile.lsm, Nomefile.tar.gz) #+ nella directory incoming di Sunsite/UNC (ibiblio.org). # Nomefile.tar.gz è l'archivio vero e proprio. # Nomefile.lsm è il file di descrizione. # Sunsite richiede il file "lsm", altrimenti l'upload viene rifiutato. E_ERR_ARG=65 if [ -z "$1" ] then echo "Utilizzo: `basename $0` nomefile-da-caricare" exit $E_ERR_ARG fi Nomefile=`basename $1` # Toglie il percorso dal nome del file. Server="ibiblio.org" Directory="/incoming/Linux" # Questi dati non dovrebbero essere codificati nello script, #+ ma si dovrebbe avere la possibilità di cambiarli fornendoli come #+ argomenti da riga di comando. Password="vostro.indirizzo.e-mail" # Sostituitelo con quello appropriato. ftp -n $Server <<Fine-Sessione # l'opzione -n disabilita l'auto-logon user anonymous "$Password" binary bell # Emette un 'segnale acustico' dopo ogni #+ trasferimento di file cd $Directory put "$Nomefile.lsm" put "$Nomefile.tar.gz" bye Fine-Sessione exit 0 |
L'uso del quoting o dell'escaping sulla "stringa limite" del here document disabilita la sostituzione di parametro al suo interno.
Esempio 18-7. Sostituzione di parametro disabilitata
#!/bin/bash # Un here document 'cat' con la sostituzione di parametro disabilitata. NOME="John Doe" RISPONDENTE="L'autore dello script" cat <<'Finemessaggio' Ciao, sono $NOME. Salute a te $NOME, $RISPONDENTE. Finemessaggio # Non c'è sostituzione di parametro quando si usa il quoting o l'escaping #+ sulla "stringa limite". # Le seguenti notazioni avrebbero avuto, entrambe, lo stesso effetto. # cat <"Finemessaggio" # cat <\Finemessaggio exit 0 |
Disabilitare la sostituzione di parametro permette la produzione di un testo letterale. Questo può essere sfruttato per generare degli script o perfino il codice di un programma.
Esempio 18-8. Uno script che genera un altro script
#!/bin/bash # generate-script.sh # Basato su un'idea di Albert Reiner. OUTFILE=generato.sh # Nome del file da generare. # ----------------------------------------------------------- # 'Here document contenente il corpo dello script generato. ( cat <<'EOF' #!/bin/bash echo "Questo è uno script di shell creato da un altro script." # È da notare che, dal momento che ci troviamo all'interno di una subshell, #+ non possiamo accedere alle variabili dello script "esterno". # echo "Il file prodotto si chiamerà: $OUTFILE" # La riga precedente non funziona come ci si potrebbe normalmente attendere #+ perché è stata disabilitata l'espansione di parametro. # Come risultato avremo, invece, una visualizzazione letterale. a=7 b=3 let "c = $a * $b" echo "c = $c" exit 0 EOF ) > $OUTFILE # ----------------------------------------------------------- # Il quoting della 'stringa limite' evita l'espansione di variabile #+ all'interno del corpo del precedente 'here document.' # Questo consente di conservare le stringhe letterali nel file prodotto. if [ -f "$OUTFILE" ] then chmod 755 $OUTFILE # Rende eseguibile il file generato. else echo "Problema nella creazione del file: \"$OUTFILE\"" fi # Questo metodo può anche essere usato per generare #+ programmi C, Perl, Python, Makefiles e simili. exit 0 |
È possibile impostare una variabile all'output di un here document.
variabile=$(cat <<IMPVAR Questa variabile si estende su più righe. IMPVAR) echo "$variabile" |
Un here document può fornire l'input ad una funzione del medesimo script.
Esempio 18-9. Here document e funzioni
#!/bin/bash # here-function.sh AcquisisceDatiPersonali () { read nome read cognome read indirizzo read città read cap read nazione } # Può certamente apparire come una funzione interattiva, ma... # Forniamo l'input alla precedente funzione. AcquisisceDatiPersonali <<RECORD001 Ferdinando Rossi Via XX Settembre, 69 Milano 20100 ITALIA RECORD001 echo echo "$nome $cognome" echo "$indirizzo" echo "$città, $cap, $nazione" echo exit 0 |
È possibile usare i : come comando fittizio per ottenere l'output di un here document. Si crea, così, un here document "anonimo".
Esempio 18-10. Here document "anonimo"
#!/bin/bash : <<VERIFICAVARIABILI ${HOSTNAME?}${USER?}${MAIL?} # Visualizza un messaggio d'errore se una #+ delle variabili non è impostata. VERIFICAVARIABILI exit 0 |
Una variazione della precedente tecnica consente di "commentare" blocchi di codice. |
Esempio 18-11. Commentare un blocco di codice
#!/bin/bash # commentblock.sh : <<BLOCCOCOMMENTO echo "Questa riga non viene visualizzata." Questa è una riga di commento senza il carattere "#" Questa è un'altra riga di commento senza il carattere "#" &*@!!++= La riga precedente non causa alcun messaggio d'errore, perché l'interprete Bash la ignora. BLOCCOCOMMENTO echo "Il valore di uscita del precedente \"BLOCCOCOMMENTO\" è $?." # 0 # Non viene visualizzato alcun errore. # La tecnica appena mostrata diventa utile anche per commentare #+ un blocco di codice a scopo di debugging. # Questo evita di dover mettere il "#" all'inizio di ogni riga, #+ e quindi di dovere, più tardi, ricominciare da capo e cancellare tutti i "#" : <<DEBUGXXX for file in * do cat "$file" done DEBUGXXX exit 0 |
Un'altra variazione di questo efficace espediente rende possibile l'"auto-documentazione" degli script. |
Esempio 18-12. Uno script che si auto-documenta
#!/bin/bash # self-document.sh: script autoesplicativo # È una modifica di "colm.sh". RICHIESTA_DOCUMENTAZIONE=70 if [ "$1" = "-h" -o "$1" = "--help" ] # Richiesta d'aiuto. then echo; echo "Utilizzo: $0 [nome-directory]"; echo sed --silent -e '/DOCUMENTAZIONEXX$/,/^DOCUMENTAZIONEXX$/p' "$0" | sed -e '/DOCUMENTAZIONEXX$/d'; exit $RICHIESTA_DOCUMENTAZIONE; fi : <<DOCUMENTAZIONEXX Elenca le statistiche di una directory specificata in formato tabellare. ------------------------------------------------------------------------------ Il parametro da riga di comando specifica la directory di cui si desiderano le statistiche. Se non è specificata alcuna directory o quella indicata non può essere letta, allora vengono visualizzate le statistiche della directory di lavoro corrente. DOCUMENTAZIONEXX if [ -z "$1" -o ! -r "$1" ] then directory=. else directory="$1" fi echo "Statistiche di "$directory":"; echo (printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \ ; ls -l "$directory" | sed 1d) | column -t exit 0 |
Un modo alternativo per ottenere lo stesso risultato è quello di usare uno script cat.
RICHIESTA_DOCUMENTAZIONE=70 if [ "$1" = "-h" -o "$1" = "--help" ] # Richiesta d'aiuto. then # Usa uno "script cat" . . . cat <<DOCUMENTAZIONEXX Elenca le statistiche della directory specificata in formato tabellare. --------------------------------------------------------------------------- Il parametro da riga di comando specifica la directory di cui si desiderano le statistiche. Se non è specificata alcuna directory o quella indicata non può essere letta, allora vengono visualizzate le statistiche della directory di lavoro corrente. DOCUMENTAZIONEXX exit $RICHIESTA_DOCUMENTAZIONE fi |
Vedi anche Esempio A-28 come eccellente dimostrazione di script autoesplicativo.
Gli here document creano file temporanei che vengono cancellati subito dopo la loro apertura e non sono accessibili da nessun altro processo.
|
Alcune utility non funzionano se inserite in un here document. |
La stringa limite di chiusura, dopo l'ultima riga di un here document, deve iniziare esattamente dalla prima posizione della riga. Non deve esserci nessuno spazio iniziale. Allo stesso modo uno spazio posto dopo la stringa limite provoca comportamenti imprevisti. Lo spazio impedisce il riconoscimento della stringa limite.
|
Per quei compiti che risultassero troppo complessi per un "here document", si prenda in considerazione l'impiego del linguaggio di scripting expect che è particolarmente adatto a fornire gli input ai programmi interattivi.
Una here string può essere considerata
un here document ridotto ai minimi termini.
È formata semplicemente da COMANDO
<<<$PAROLA, dove $PAROLA
viene espansa per diventare lo stdin di
COMANDO
.
Come semplice esempio, la si consideri una alternativa al costrutto echo-grep.
# Invece di: if echo "$VAR" | grep -q txt # if [[ $VAR = *txt* ]] # ecc. # Provate: if grep -q "txt" <<< "$VAR" then echo "$VAR contiene la sottostringa \"txt\"" fi # Grazie a Sebastian Kaminski per il suggerimento. |
O in combinazione con read:
Stringa="Questa è una stringa di parole." read -r -a Parole <<< "$Stringa" # L'opzione -a di "read" #+ assegna consecutivamente i valori risultanti agli elementi di un array. echo "La prima parola in Stringa è: ${Parole[0]}" # Questa echo "La second parola in Stringa è: ${Parole[1]}" # è echo "La terza parola in Stringa è: ${Parole[2]}" # una echo "La quarta parola in Stringa è: ${Parole[3]}" # stringa echo "La quinta parola in Stringa è: ${Parole[4]}" # di echo "La sesta parola in Stringa è: ${Parole[5]}" # parole. echo "La settima parola in Stringa è: ${Parole[6]}" # (null) # Oltre la #+ dimensione di $Stringa. # Grazie a Francisco Lobo per il suggerimento. |
Esempio 18-13. Anteporre una riga in un file
#!/bin/bash # prepend.sh: Aggiunge del testo all'inizio di un file. # # Esempio fornito da Kenny Stauffer, #+ leggermente modificato dall'autore del libro. E_FILE_ERRATO=65 read -p "File: " file # L'opzione -p di 'read' visualizza il prompt. if [ ! -e "$file" ] then # Termina l'esecuzione se il file non esiste. echo "File $file non trovato." exit $E_FILE_ERRATO fi read -p "Titolo: " titolo cat - $file <<<$titolo > $file.nuovo echo "Il file modificato è $file.nuovo" exit 0 # da 'man bash': # Here Strings # A variant of here documents, the format is: # # <<<word # # The word is expanded and supplied to the command on its standard input. |
Esempio 18-14. Analizzare un file mailbox
#!/bin/bash # Script di Francisco Lobo, #+ commentato e leggermente modificato dall'autore de Guida ASB. # Usato in Guida ASB con il permesso dell'autore dello script . (Grazie!) # Lo script non funziona con versioni Bash < 3.0. E_ARG_MANCANTE=67 if [ -z "$1" ] then echo "Utilizzo: $0 file-mailbox" exit $E_ARG_MANCANTE fi mbox_grep() # Analizza il file mailbox. { declare -i corpo=0 corrispondenza=0 declare -a data mittente declare mail intestazione valore while IFS= read -r mail # ^^^^ Reimposta $IFS. # Altrimenti "read" elimina lo spazio iniziale e finale dal proprio input. do if [[ $mail =~ "^From " ]] # Verifica il campo "From" del messaggio. then (( corpo = 0 )) # "Azzera" le variabili. (( corrispondenza = 0 )) unset data elif (( corpo )) then (( corrispondenza )) # echo "$mail" # Decommentate la riga precedente se volete visualizzare l'intero #+ corpo del messaggio. elif [[ $mail ]]; then IFS=: read -r intestazione valore <<< "$mail" # ^^^ "here string" case "$intestazione" in [Ff][Rr][Oo][Mm] ) [[ $valore =~ "$2" ]] && (( corrispondernza++ )) ;; # Verifica della riga "From". [Dd][Aa][Tt][Ee] ) read -r -a data <<< "$valore" ;; # ^^^ # Verifica della riga "Date". [Rr][Ee][Cc][Ee][Ii][Vv][Ee][Dd] ) read -r -a mittente <<< "$valore" ;; # ^^^ # Verifica dell'indirizzo IP (c'è stato dello spoofing?). esac else (( corpo++ )) (( corrispondenza )) && echo "MESSAGGIO ${data:+di: ${data[*]} }" # L'intero array $data ^ echo "Indirizzo IP del mittente: ${mittente[1]}" # Secondo campo della riga "Received" ^ fi done < "$1" # Redirezione dello stdout del file nel ciclo. } mbox_grep "$1" # Invio del file mailbox alla funzione. exit $? # Esercizi: # --------- # 1) Suddividete la precedente funzione in più funzioni, #+ per rendere lo script maggiormente leggibile. # 2) Aggiungete altre analisi che verifichino diverse parole chiave. $ mailbox_grep.sh scam_mail --> MESSAGGIO di Thu, 5 Jan 2006 08:00:56 -0500 (EST) --> Indirizzo IP del mittente: 196.3.62.4 |
Esercizio: scoprite altri impieghi per le here string.