La Shell di Unix è un interprete delle “linee di comando” attraverso le quali l’utente utilizza le risorse del sistema.
Permette la gestione di variabili e dispone di costrutti per il controllo del flusso delle operazioni.
Può essere utilizzata in modalità:
Viene generalmente eseguita in modalità interattiva, all’atto del login, restando attiva per tutta la durata della sessione di lavoro, effettuando le seguenti operazioni:
I sistemi Unix offrono diverse shell:
Shell diverse hanno sintassi leggermente diverse.
In molti sistemi Unix il file /etc/shells
contiene l’elenco delle shell installate dall’amministratore e disponibili a tutti gli utenti.
La shell interpreta la prima “parola” come:
/bin/ls
“#!/bin/bash
Quando invocata, ogni shell esegue una serie di script di personalizzazione definiti dall’amministratore e/o dall’utente.
Le shell si dividono in:
Ogni shell definisce i propri script di personalizzazione.
/etc/profile
: Profilo generico comune a tutti;~/.profile
: Proprio profilo utente./etc/bashrc
: Profilo generico comune a tutti gli utenti bash~/.bash_profile, ~/.bash_login
: Profilo definito dall’utente~/.bashrc
: Proprio profilo utente. Usato per “non-login shel”NON utilizzare costrutti bash in /etc/profile
o ~/.profile
.
Il comportamento di tutte le shell è definito attraverso alcune variabili d’ambiente.
Alcuni esempi:
/etc/passwd
;È possibile utilizzare il comado printenv per visualizzare le variabili d’ambiente.
L’esecuzione di un comando avviene creando un nuovo processo, detto processo figlio, che esegue il programma richiesto.
La sintassi del comandi Unix è:
comando [argomento ...]
Gli argomenti possono essere:
La prima “parola” identifica il comando:
La richiesta di esecuzione di un comando implica la creazione di un nuovo processo.
I processi eseguiti all’interno di una shell possono essere:
Per segnalare alla shell che si vuole lanciare il comando (o lo script) in background, è necessario farne seguire il nome dal carattere & :
comando [argomento..] &
comando [argomento..] > nomefile
L’output del comando viene scritto in nomefile. Se file non esiste viene creato, altrimenti viene sovrascritto.
comando [argomento..] >> nomefile
L’output del comando viene scritto in nomefile. Se file non esiste viene creato, altrimenti l’output viene accodato.
comando [argomento..] < nomefile
Il contenuto di nomefile viene passato in input al comando.
comando [argomento..] 2> nomefile
Lo standard error del comando viene scritto in nomefile. Se il file non esiste viene creato, altrimenti viene sovrascritto.
comando [argomento..] 2>> nomefile
Lo standard error del comando viene scritto in nomefile. Se file non esiste viene creato, altrimenti l’output viene accodato.
comando [argomento..] > /dev/null 2>&1
/dev/null
è un file speciale. Qualunque informazione scritta su questo file viene cestinata.
L’output che il comando invia sullo standard output viene cestinato. L’output inviata sullo standard errorviene rediretto sullo standard output (e quindi anch’esso su /dev/null
).
Legenda:
1=standard output
2=standard error
Per eseguire un comando, la shell:
| & ; ( ) < > spazio tab
;~/
” viene sostituita la stringa che identifica la home directory dell’utente$nomevar
” viene sostituito il valore della variabile nomevar;I caratteri speciali sono caratteri o gruppi di caratteri che vengono utilizzati per fornire indicazioni alla shell. Di seguito riportiamo le direttive codificate da alcuni caratteri speciali:
| (pipe)
: Indica alla shell di eseguire concorrentemente il comando a sinistra ed a destra della pipe. L’output del comando a sinistra della pipe diventa l’input del comando a destra della stessa. Ad es., ls -l | sort&& (AND logico):
Il comando a destra dell’AND viene eseguito se e solo se il comando a sinistra dell’AND termina con un valore di uscita zero.|| (OR logico)
: Il comando a destra dell’OR viene eseguito se e solo se il comando a sinistra dell’OR ritorna un valore NON-zero.<
(redirezione dello standard input): Indica alla shell di leggere l’input da un file invece che da tastiera>
(redirezione dello standard output): Indica alla shell di scrivere l’output su un file invece che su monitor*
corrisponde ad una stringa qualunque (anche vuota)?
corrisponde ad un carattere singolo qualsiasi[...]
corrisponde ad uno dei caratteri racchiusiUn elenco esteso dei caratteri speciali è disponibile al seguente indirizzo: http://www.pluto.it/files/ildp/guide/abs/special-chars.html
Tutte le shell consentono la definizione di variabili, i.e., di contenitori di informazioni. La shell stessa utilizza variabili, visibili all’utente, per mantenere lo stato dell’interazione con l’utente (e.g., la directory corrente) o le preferenza dell’utente (e.g., il prompt utente). Le variabili possono essere:
Per rendere una variabile locale visibile ai (soli) processi generati dalla shell corrente è possibile utilizzare il comando export.
La modifica ad una variabile globale od esportata:
Regole di naming. I nomi di variabili:
Esempi:
Per definire una variabile (locale) è possibile utilizzare la seguente sintassi:
Per referenziare il valore di una variabile è necessario utilizzare il carattere special $ seguito dal nome della variabile:
È possibile passare parametri ad uno script.
Gli argomenti di uno script sono posizionali e vengono indicati mediante un intero (0, 1, 2, 3…):
$0
rappresenta il nome del file contenente dello script;$1, $2...
rappresentano, nell’ordine, gli argomenti passati in input allo script;${10}, ${11}
,…$#
rappresenta il numero di argomenti passati in input allo script;$@
rappresenta l’insieme di tutti gli argomenti dati in input allo script. Se lo script riceve meno di nove argomenti, a quelli mancanti corrispondono stringhe vuote.Il comando shift esegue una rotazione a sinistra dei parametri.
Rende possibile processare in modo iterativo i parametri uno per volta.
Esempio di modifica della variabile $PATH: Mostra codice
La variabile PATH contiene la sequenza delle directory, divise da ":", in cui la shell ricerca i file eseguibili.
Nell'esempio, l'utente richiede l'esecuzione del comando ps. La shell ricerca un file eseguibile nella directory /usr/bin
. Se non esiste nessun file con quel nome in questa directory, ricerca un file "ps" nella directory /home/lso
. Se non lo trova cerca il file nella directory corrente (indicata dalla presenza del ".").
Chiaramente l'utente può modificare la variabile $PATH. Essendo questa, però, una variabile d'ambiente. La sua modifica implica una modifica del comportamento della shell.
Nell'esempio, l'aggiunta della directory "/bin
" alla variabile PATH consente alla shell di eseguire il comando ps.
Concatenazione di variabili shell: Mostra codice
Il secondo esempio concentra l'attenzione sulla concatenazione di valori di variabili.
La concatenazione avviene giustapponendo, senza spazi, i valori delle variabili e/o delle costanti.
Nel caso di ambiguità, i caratteri speciali { e } consentono di identificare l'inizio e la fine del nome di una variabile.
L'assegnazione ad una variabile di una stringa contenente spazi richiede la protezione del metacarattere spazio attraverso il quoting. L'utilizzo degli apici doppi o singoli dipende dall'utilizzo della stringa.
Esempio di utilizzo dei parametri: Mostra codice
Lo script esegue una visualizzazione dei parametri passati allo script.
La prima riga visualizza il nome del file contenente lo script, utilizzando il parametro $0.
Vengono successivamente visualizzati:
A questo punto, il comando shift esegue una rotazione a sinistra dei paramtri. L'effetto di shift è l'eliminazione del primo parametro, ed una ridefinizione del numero di parametri passati, della posizione dei parametri e della stringa contenete tutti i parametri. Da questo momento in poi, il primo parametro non è più recuperabile.
Difatti, nell'esempio di esecuzione: Mostra codice
- la variabile $# assume il valore tre prima dello shift e due dopo lo shift
- la variabile $2 assume il valore "due" prima dello shift e "tre" dopo lo shift
- la variabile $@ assume il valore "uno due tre" prima dello shift e "due tre" dopo lo shift.
Si noti che shift non ha effetto sulla variabile $0.
In bash è possibile definire array di variabili.
Non è necessario definire la dimensione dell’array ne la shell definisce una dimensione massima.
Il primo elemento dell’array ha indice 0.
Per definire un array è possibile utilizzare una delle seguenti sintassi:
Nomearray[indice]=valore, dove “indice” viene trattata come una espressione aritmetica il cui valore deve essere non negativo.
declare -a Nomearray
Nomerray=(valore1 valore2 … valoren)
Per aggiungere/modificare un elemento dell’array, è possibile utilizzare la scrittura Nomearray[indice]=valore.
Per referenziare un elemento in un array è necessario utilizzare la seguente sintassi:
${Nomearray[indice]}
accede all’elemento indicato nell’array${Nomearray[*]}
elenca tutti gli elementi dell’array$Nomearray
è un riferimento al primo elemento dell’arrayPer cancellare un elemento dall’array è possibile utilizzare la seguente:
La dichiarazione “esplicita” dell’array attraverso declare è, in realtà, inutile vista la dichiarazione “implicita” attraverso l’inizializzazione dell’array. È, comunque, buona norma, ove possibile, cercare di tipizzare esplicitamente le variabili utilizzate.
La scrittura $array[*] viene suddivisa dalla shell come:
$array
“: In bash il nome dell’array è implicitamente un riferimento alla prima locazione dell’array. Quindi la scrittura $array viene inteepretata come Sostituzione del primo elemento dell’array.[*]
“: Stringa costante.È possibile inserire un elemento in qualsiasi posizione dell’array. Difatti, nell’esempio che segue: Mostra codice, viene inserito un elemento nella posizione 10 dell'array, sebbene le precedenti siano vuote.
La cancellazione degli elementi in un array può essere puntuale, indicando l'indice dell'elemento da cancellare, o globale, indicando "*">
in quest'ultimo caso, tutti gli elementi dell'array saranno cancellati.
Output dello script: Mostra codice
La shell interpreta i caratteri speciali (e le keywords). In taluni casi, però, si rende necessario evitare l’interpretazione o, come vedremo, l’espansione.
Costa $3
“. La shell sostituirebbe “$3
” con il valore del terzo parametro passato allo script.Esistono diverse tecniche che consentono di proteggere (evitare l’interpretazione di) I caratteri speciali:
$, ` e \
Il seguente script: Mostra codice, mostra alcuni esempi di quoting ed escaping. Vengono definite due variabili, a e b, che differiscono semplicemente per il numero di spazi tra le parole "uno" e "due". Per comodità di lettura, l'output include un indice di riga.
Sebbene a e b siano diverse, gli output con indice 1 e 2 sono identici a causa della mancata protezione dei metacaratteri spazio. Il comando echo, difatti, sopprime le occorrenze multiple di questi metacaratteri in b.
Se proteggiamo gli spazi in a e b tramite quoting con apici doppi, gli output che otteniemo (3 e 4) riproducono fedelmente le variabili a e b. Si noti che le seguenti scritture sono equivalenti: echo "3->$a
" ed echo "
""3->
$a
"
La presenza di un backslash in 5 serve a proteggere il carattere speciale $. In questo caso, il \ evita che la shell interpreti $ come "sostituzione di variabile".
Si noti che, in 6, il primo backslash serve a proteggere il secondo backslash. La shell, quindi intepreta "\\
" come "visualiza il carattere \" ed i successivi "$b
" come "sostituisci il valore della variabile b".
Infine, l'utilizzo dell'apice singolo, in 7,8 e 9, protegge tutti I caratteri speciali tranne l'apice stesso.
La riga 10 viene suddivisa dalla shell come segue:
'10->\'
. Visualizza la stringa constante 10->\
\'
: Visualizza il carattere ' (protetto dal carattere di escape)
'\$a'
: Visualizza la stringa $a (protetta dal quoting)
Output dello script: Mostra codice
Il comando exit termina uno script ed, opzionalmente, ritorna alla shell un valore.
Per convenzione, uno script ritorna zero in caso di successo. Altrimenti, il valore di ritorno può essere utilizzato per dare indicazioni sull’errore che si è verificato.
Se lo script termina con un exit senza parametri o senza exit, il valore di ritorno dello script è pari al valore di ritorno dell’ultimo comando eseguito all’interno dello script.
Lo script può ritornare un intero nel range 0, 255 utilizzando la scrittura exit n.
Dopo la suddivisione dell’input in token, la shell “espande” se necessario i singoli token.
Ovviamente, l’espansione avviene se e solo se i caratteri speciali NON sono protetti.
Espansione delle parentesi: La stringa Prefisso{s1,s2,s3…}Suffisso viene espansa dalla shell generando tutte le stringhe del tipo PrefissosiSuffisso.
Il Suffisso è opzionale;
È possibile annidare le brace expansion.
Espansione della Tilde: Il carattere speciale ~ viene utilizzato dalla shell per indicare la home directory di un utente. In particolare:
~/ v
iene sostituita dal contenuto della variabile HOME dell’utente che esegue la shell~nomeutente/
viene sostituita dalla home directory dell’utente la cui username è “nomeutente”
Espansione delle variabili: Abbiamo già menzionato l’espansione effettuata attraverso il carattere speciale $, i.e., la shell interpreta $nomevar o ${nomevar} sostituendo il valore della variabile nomevar.
Sostituzione dei comandi: La shell consente la possibilità di ottenere l’output di un comando, in modo da poterlo processare successivamente. Esistono due possibili sintassi:
Il seguente script: Mostra codice, mostra un esempio di espansione annidata di parentesi. Sono presenti un prefisso "P" ed un suffisso "S".
Le variabili a e b corrispondono alle home directory dell'utente corrente (galdi) e dell'utente "corsi" rispettivamente. La shell sostituisce la scrittura "~/
" con la stringa "/home/galdi
" e la scrittura "~corsi
" con "/home/corsi
"/
L'esempio illustra la sostituzione di comando. Nello specifico, la shell.
/home/galdi | wc -l
. Questo comando consiste nell'esecuzione concorrente, definita dall'operatore |, di ls e wc. Inoltre, l'output di ls /home/galdi
diventa input di wc -l/home/galdi
", "e", "18"Si noti che l'output la sostituzione di comando può essere assegnata ad una variabile per essere processata successivamente: Mostra codice
Espansione aritmetica: Consente la valutazione di espressioni aritmetiche.
Suddivisione dell’output in parole: Se il risultato delle espansioni non viene assegnato ad una variabile o processato esplicitamente, la shell interpreta l’output dell’espansione come un comando od una sequenza di comandi da eseguire.
Espansione dei nomi dei file: Successivamente alla suddivisione in parole, la shell ricerca all’interno di ogni parola I caratteri speciali *, ?, [, ., ..
Il seguente script: Mostra codice, illustra alcuni esempi di espansione aritmetica.
Innanzitutto si ricorda che le operazioni vengono effettuate su interi ed hanno risultato intero. Quindi, il rapporto tra 5 e 3 e' pari ad 1.
La scrittura $((a/b)) implica il calcolo del rapporto tra il contenuto delle variabili a e b. La presenza del $ indica alla shell la necessità di effettuare la sostituzione aritmetica, I.e., la stringa "((a/b))" deve essere sostituita dal valore del rapporto a/b. Questo valore è assegnato alla variabile r.
Si noti che le due scritture $((a/b)) e $(($a/$b)) sono entrambe corrette. L'utilizzo della sostituzione di variabile all'interno delle espressioni è opzionale.
ATTENZIONE: È sbagliato utilizzare forme del tipo (($a++)). Questa scrittura equivarrebbe a $a=$a+1. Ma $a è il valore della variabile a, NON la variabile a, e quindi NON è possibile assegnare a $a alcunché.
Il comando built-in let può essere utilizzato per eseguire operazioni aritmetiche.
È infine necessario notare che la scrittura $((espressione)) viene interpretata dalla shell in due fasi. In una prima fase, la shell valuta l'espressione in ((espressione)). Nella seconda fase, la presenza del $ indica alla shell di "ritornare" il valore calcolato. In realtà la prima fase è indipendente dalla seconda. In particolare, è possibile utilizzare la scrittura ((espressione)), senza il "$". Questa scrittura indica alla shell semplicemente di valutare l'espressione, modificando, se richiesto, le variabili in essa contenute. Nell'esempio, ((a++)) modifica il valore della variabile a, il cui valore modificato può essere utilizzato successivamente.
Output dello script: Mostra codice
1. Introduzione ai sistemi Unix
2. Principi di programmazione Shell
3. Esercitazioni su shell scripting - parte prima
5. Esercitazioni su shell scripting - parte seconda
6. Espressioni Regolari ed Introduzione ad AWK
7. Esercitazioni su espressioni regolari ed awk scripting
9. Esercitazioni su awk scripting - parte seconda
10. Programmazione in linguaggio C: Input/Output di basso livello
11. Esercitazioni su I/O di basso livello
12. Interazione con file di sistema e variabili d'ambiente
13. Esercitazioni sulla gestione dei file di sistema e le variabili...
14. System call per la gestione di file e directory
15. Esercitazioni su gestione file e directory
16. La programmazione multi-processo
17. Esercitazioni su programmazione multi-processo
18. I Segnali
20. I Socket
Bash guide for beginners: Cap 1 (par. 1-3); Cap 2 (par. 1-2); Cap 3 (par. 1-4); Cap 10 (par. 2-3)
Advanced Bash Scripting: capitoli 2, 3, 4, 5, 6, 8, 11, 12