Sei sulla pagina 1di 27

La shell BASH

http://wowarea.com/italiano/linux/shell.htm

11.1 I comandi

La shell e' un interprete di comandi. I comandi digitati dall'utente vengono letti dalla shell,
interpretati e inviati al kernel per essere eseguiti. La shell e' percio' una interfaccia per gli
umani, cioe' un programma che accetta i comandi digitati dall'utente, li trasforma in istruzioni
eseguibili dal computer e li invia al kernel per l'esecuzione. Supponiamo di voler intrattenere
una conversazione con una persona straniera, ad esempio un norvegese: conoscendo la lingua,
non avremmo problemi, in caso contrario avremmo bisogno di un interprete, cioe' di una
persona che, conoscendo entrambe le lingue possa tradurre da una lingua all'altra e viceversa.
La shell e' qualcosa di simile: converte i nostri comandi in istruzioni eseguibili dalla macchina. I
comandi vengono digitati dall'utente sulla riga comandi della shell. La riga comandi e' una riga
digitabile che comincia dal prompt. Il prompt e' un carattere o un insieme di caratteri che
possono essere personalizzati dall'utente. Il prompt dell'utente root (il superuser) inizia con il
carattere '#' (cancelletto), mentre il prompt di un utente qualsiasi inizia con il carattere '$'
(dollaro). Ecco un esempio di prompt:

mau@localhost mau]$_

il carattere di sottolineatura '_' (underscore) rappresenta il cursore dal quale e' possibile
iniziare a scrivere. Ma un prompt come questo appena visto puo' essere personalizzato
modificando la variabile PS1 presente nel file di configurazione .bash_profile. Ad esempio si
potrebbe definire un prompt di questo tipo:

sto aspettando un tuo comando:_

I comandi sono dei programmi che sono contenuti all'interno di alcune directory speciali come
le directory /bin e /sbin. Quando l'utente digita il nome del comando/programma e preme il
tasto invio, la shell cerca tale file in alcune directory predefinite (come /bin e /sbin appunto) e
se lo trova lo esegue. In caso contrario produce un errore del tipo

bash: nome-comando : command not found

cioe' comando non trovato. Un comando e' composto dal nome del comando stesso, da una o
piu' opzioni facoltative e da uno o piu' argomenti facoltativi. Un argomento e' solitamente il
nome di un file sul quale il comando deve agire. Alcuni comandi prevedono degli argomenti
obbligatori, altri no:

nome-comando -opzione argomento

il nome del comando deve essere separato dalle opzioni da uno spazio, cosi' come le opzioni e
gli eventuali argomenti. Le opzioni sono specificate usando un trattino '-' e la lettera o le
lettere che specificano le opzioni. L'ordine e': 1) nome-comando, spazio, 2) -opzioni, spazio, 3)
argomenti. Tra il trattino ed i caratteri di opzione non devono essere presenti spazi. Ad
esempio il comando ls consente l'uso di alcune opzioni come l (lista estesa) a (tutti i file) i
(visualizza inode) e via dicendo. Percio' per visualizzare l'elenco dei file di una directory si puo'
usare il comando 'ls -lai'. Se si vogliono visualizzare le informazioni di un file ben preciso,
occorre specificare il nome del file come argomento. Ad esempio con il comando 'ls -l pippo'
vengono visualizzate le informazioni sul file pippo. Ecco alcuni esempi dello stesso comando,
alcuni errati ed alcuni corretti:
ls -lai (corretto)
ls - lai (errato: tra il trattino '-' e le opzioni 'l', 'a' ed 'i' non devono esserci spazi)
ls -l ai (errato: tra le opzioni 'l', 'a' ed 'i' non ci devono essere spazi)
ls pippo -lai (errato: l'argomento pippo deve essere scritto dopo le opzioni -lai)
ls -l pippo (corretto: l'argomento pippo e' specificato dopo l'opzione 'l')
ls -ail (corretto: le opzioni non devono essere digitate rispettando una sequenza prestabilita)
ls -lia (corretto: le opzioni non devono essere digitate rispettando una sequenza prestabilita)
ls -ial (corretto: le opzioni non devono essere digitate rispettando una sequenza prestabilita)

un esempio di comando che puo' accettare piu' argomenti e' il comando cp (CoPy, cioe' copia)
che consente di effettuare la copia di un file in un altro file con un nome diverso. Questo
comando puo' essere utile se si possiede un documento importante e si desidera salvarlo in
una directory di salvataggio. Qualora il file originario venisse cancellato accidentalmente, si
avrebbe sempre a disposizione la sua copia nella directory di salvataggio. Percio' si puo' usare
il comando cp specificando il file da copiare ed il nome della nuova copia dello stesso file. Ad
esempio:

cp documento /home/mau/salvataggi/documento.bak

come si puo' vedere il primo argomento del comando cp e' 'documento' mentre il secondo
argomento e' '/home/mau/salvataggi/documento.bak'. In questo comando non sono presenti
opzioni poiche' non e' presente un carattere '-' che contraddistingue le opzioni. Le opzioni
infatti, per definizione sono facoltative. Nel primo argomento non e' stato specificato il path
completo, cioe' il percorso per raggiungere il file documento. In questo caso la shell sa di dover
reperire tale file dalla directory corrente. Per default infatti se non si specifica un path, la shell
cerca il file nella directory corrente (chiamata anche directory di lavoro). Nel secondo
argomento invece e' stato specificato il path completo, perche' altrimenti la shell avrebbe
copiato il file documento nella directory corrente con il nome documento.bak. Cio' sarebbe
stato possibile, ma il nostro obiettivo e' quello di salvarlo in una directory diversa e preposta al
salvataggio dei nostri documenti. Ad ogni modo il comando 'cp documento documento.bak' e'
un comando perfettamente legale in quanto i due argomenti hanno nomi diversi. Non si puo'
dire altrettanto del comando: 'cp documento documento', in quanto il nome del file iniziale e
quello della copia sono identici. In questo caso infatti riceveremmo un messaggio di errore
della shell che ci informa che il file documento esiste gia'. Non e' possibile infatti avere due file
diversi con lo stesso nome all'interno della stessa directory. Se per assurdo cio' fosse possibile,
nel caso si volesse visualizzare il file documento, quale dei due file dovrebbe aprire la shell? Al
contrario due file con lo stesso nome possono esistere nel sistema purche' residenti in due
directory distinte. Nell'esempio sopra avremmo potuto percio' scrivere:

cp documento /home/mau/salvataggi/documento

ora i due argomenti hanno lo stesso nome (documento) ma questo comando e' perfettamente
legale e la shell lo interpretera' senza problemi. Questo perche' i due file pur avendo lo stesso
nome risiedono in directory diverse. Non esiste percio' ambiguita' in questo caso, purche' per
referenziare uno dei due file venga specificato il path completo. Nell'esempio la directory che
contiene il file originale e' quella corrente cioe' /home/mau/ e per referenziare il file originale
occorre scrivere /home/mau/documento; la copia di tale file invece risiede nella directory di
salvataggio /home/mau/salvataggi pertanto per referenziarla occorre scrivere
/home/mau/salvataggi/documento. Per lavorare con i file e' necessario conoscere alcuni
comandi basilari come cp, mv, rm ed altri: cio' sara' oggetto di un successivo capitolo. E'
possibile digitare piu' comandi sulla stessa riga comando separandoli con il carattere ';'. E'
anche possibile scrivere dei comandi molto lunghi su piu' righe usando il carattere '\' a fine
riga. In questo caso, premendo invio si potra' scrivere su una nuova riga per continuare la
digitazione del comando, ma niente verra' inviato alla shell. Ogni riga che termina con '\' ed il
tasto invio, crea una nuova riga ma non permette di eseguire il comando. Per eseguire il
comando occorrera' premere invio avendo l'accortezza di non terminare il comando stesso con
il carattere '\'. Ad esempio:
questo e' un comando molto lungo \ (premo invio)
pertanto ho deciso di scriverlo \ (premo invio)
su piu' righe (premo invio)

In pratica il carattere \ dice alla shell: 'in questa riga il tasto invio non serve per inviarti il
comando ma per andare a capo'. Il tasto \ e' in questo caso un metacarattere, cioe' un
carattere che maschera un altro carattere. Il tasto invio infatti produce un carattere di 'new
line' che la shell interpreta come fine del comando. Mascherando tale carattere con il
metacarattere \ potremo andare a capo ma la shell non lo considerera' come carattere di fine
comando. All'ultima riga, alla fine del comando, premendo invio senza \ faremo eseguire il
comando alla shell. Infine un ultimo quesito: come interrompere un comando che sta
impiegando troppo tempo? Premendo la combinazione di tasti CTRL-C. Tale combinazione di
tasti infatti interrompe l'esecuzione di qualsiasi comando.

11.2 Un po' di storia


I sistemi operativi che esistevano prima di Linux e di Unix (ricordiamo che Linux e' un clone di
Unix) possedevano gia' degli interpreti di comandi al loro interno, ma tali interpreti avevano un
difetto: non erano modificabili. Quando Ken Thompson e Dennis Ritchie cominciarono a
scrivere Unix, decisero di creare una interfaccia utente modificabile a seconda delle esigenze
che si potevano presentare di volta in volta. Cio' che venne fuori fu un tipo di shell
programmabile, contenente istruzioni simili a quelle del linguaggio C. Questo non e' un caso
visto che la prima versione di Unix venne scritta in linguaggio assembler, ma fu
successivamente riscritta in linguaggio C (ricordiamo che Dennis Ritchie e' insieme a Brian
Kerninghan l'inventore del linguaggio C). L'interprete dei comandi creato sotto Unix venne
chiamato shell. Una delle prime shell venne creata agli inizi degli anni 70 presso i Bell
Laboratories AT&T ad opera di Steven R. Bourne e venne chiamata appunto Bourne shell (sh).
Verso la fine degli anni 70 presso l'universita' di Berkley venne creata la C shell (csh) allo
scopo di estendere la shell Bourne e renderla piu' simile al linguaggio di programmazione C.
Successivamente vennero sviluppate altre shell, come la Korn shell (ksh) e la TC shell (tcsh). A
causa dei problemi di copyright, l'organizzazione GNU decise di sviluppare una shell
completamente libera da restrizioni e nacque cosi' la shell BASH, cioe' Bourne Again SHell.
Esistono molteplici versioni di shell, la shell ash, la shell csh, la shell ksh, la shell pdksh, la
shell tcsh, la shell zsh ed altre ancora, ma la shell BASH e' senza dubbio la piu' famosa ed
usata in tutti i sistemi Linux. La shell BASH e' conforme allo standard POSIX. Posix sta per
Portable Operating System Interface for uniX, cioe' interfaccia standard per il sistema
operativo Unix e si tratta di uno standard creato verso la fine degli anni 80 allo scopo di
uniformare le varie versioni di Unix. Successivamente, ad opera del gruppo IEEE (Institute of
Electrical and Electronics Engineers, cioe' istituto degli ingegneri elettrotecnici ed elettronici) lo
standard POSIX venne adottato anche da altri sistemi operativi non Unix come VMS, MVS, NT
etc.

11.3 La shell BASH


Per iniziare ad esplorare la shell BASH e' sufficiente disporre di un emulatore di terminale. Se
stiamo lavorando in ambiente grafico (X Window) occorre aprire una finestra di emulazione
terminale come ad esempio Xterm, altrimenti non occorre fare nulla, in quanto ci troviamo gia'
in modalita' terminale. Nel primo caso, supponendo di usare un desktop grafico come KDE,
bastera' cliccare sul bottone di avvio di KDE, selezionare 'terminali' e scegliere il tipo di
terminale che si preferisce. A questo punto ci si trova all'interno di una shell, che potrebbe
essere BASH o CSH o TCSH o altro. Normalmente pero' la shell di default, cioe' quella
preimpostata inizialmente e' la shell BASH. Ad ogni modo e' sempre possibile invocare la shell
BASH digitando il comando 'sh' oppure il comando 'bash'. Questo comando non fa altro che
invocare la shell BASH. Piu' esattamente invoca una subshell, ossia una shell BASH all'interno
della shell corrente. Per uscire dalla subshell e' possibile digitare il comando 'exit' o premere i
tasti CTRL-D (Ctrl e D contemporaneamente). Premendo CTRL-D da una subshell si ritorna alla
shell corrente e premendo nuovamente CTRL-D si esce dalla shell iniziale. Per scoprire quali
shell siano installate nel nostro sistema, e' possibile visualizzare il file /etc/shells utilizzando il
comando:

cat /etc/shells

verra' visualizzato un elenco delle shell disponibili. Per invocare una qualsiasi delle shell
elencate e' sufficiente digitarne il nome e premere invio. Cosi' facendo verra' creata una
ISTANZA della shell digitata. E' possibile creare piu' istanze, l'una all'interno dell'altra (a mo' di
bambola matrioska), digitando il nome della shell. Per capire meglio cio', apriamo un terminale
tipo Xterm e digitiamo il comando exit (o i tasti CTRL-D): cosa accade? Si chiude la finestra
Xterm e si ritorna nel desktop, poiche' il comando exit permette di uscire dalla shell. Ora
facciamo una seconda prova: apriamo la finestra Xterm e digitiamo il comando: bash.
Apparentemente non accade nulla in quanto il sistema non fa altro che riproporre il prompt
visualizzando una seconda riga comandi vuota. In realta' pero' abbiamo appena creato una
subshell bash, abbiamo cioe' invocato una shell bash all'interno della shell bash iniziale. Per
dimostrarlo digitiamo il comando exit. Cosa accade? La finestra Xterm non si chiude, ed il
sistema ripropone ancora il prompt. Questo avviene perche' il comando exit distrugge la
subshell che abbiamo creato e ritorna a quella iniziale. A questo punto, digitando nuovamente
il comando exit si chiudera' la finestra, in quanto usciremo anche dalla shell iniziale. Digitando
il comando bash piu' volte possiamo creare innumerevoli subshell una all'interno dell'altra. Per
uscire dalla finestra Xterm percio', dovremo digitare il comando exit tante volte quante
subshell abbiamo creato. La primissima shell che Linux ci propone e' la shell di login, ossia la
shell con la quale l'utente effettua il login. Cosa e' possibile fare con una shell? Prima di tutto
inviare comandi al sistema, ma e' anche possibile creare dei programmi per effettuare
operazioni particolari o per eseguire una serie di comandi in sequenza. Vediamo cosa accade
digitando il comando:

ls -l *

apparentemente il sistema mostra un elenco di file e cio' appare ai nostri occhi come una
operazione semplicissima. In realta', ogni volta che viene eseguito un semplice comando, il
sistema effettua tutta una serie di operazioni invisibili ai nostri occhi delle quali non
sospetteremmo minimamente l'esistenza. Nell'esempio precedente, una volta digitato il
comando e premuto il tasto invio, il sistema o meglio la shell:

1. Esamina la riga di comando appena digitata


2. Espande il carattere '*' individuando tutti i file presenti nella directory corrente
3. Cerca il comando ls all'interno delle directory specificate nella variabile PATH
4. Crea una subshell dove verra' eseguito il comando ls
5. Collega l'input standard e l'output standard al comando ls
6. Esegue il comando ls all'interno della subshell appena creata
7. Chiude la subshell, torna alla shell iniziale e mostra i risultati del comando

non male come lavoro eh? ;o) Andando avanti nella conoscenza di Linux, cominceremo ad
usare la shell sempre piu' spesso, in quanto molte volte troveremo piu' semplice digitare alcuni
comandi piuttosto che cliccare a destra e a sinistra con il mouse. Prima di esaminare i comandi
principali di Linux e di imparare a programmare la shell, e' utile apprendere a districarsi
all'interno della riga comando, applicando molte scorciatoie che il sistema offre per velocizzare
la digitazione dei comandi. Ad esempio e' possibile rieseguire l'ultimo comando digitando !! e
premendo invio, e' possibile visualizzare i comandi appena digitati o rieseguire un comando
ben preciso premendo un solo tasto,e' possibile concatenare i comandi tra loro, e via dicendo.
Tutto cio' e' oggetto del prossimo paragrafo.

11.4 Le scorciatoie, la storia ed il comando history


Vediamo come 'navigare' all'interno della riga comando. Prima di tutto occorre conoscere lo
scopo del tasto invio. Il tasto invio e' un tasto utilizzato per comunicare al sistema: 'ok, ho
appena finito di digitare il comando, puoi eseguirlo'. Una volta premuto invio il sistema viene
allertato per eseguire il comando digitato. Ogni volta che si preme un tasto,
indipendentemente dal sistema operativo usato, viene attivata una cosidetta richiesta di
interrupt di tipo hardware che, molto piu' semplicemente significa che un dispositivo
hardware (la tastiera in questo caso) 'sveglia' il sistema dal torpore e gli comunica che deve
intraprendere un'azione. In realta' il sistema non dorme, ma esegue altre operazioni in
sottofondo. Il sistema e' sempre in movimento ed effettua molte operazioni continuamente.
Pero', ogni volta che viene premuto un tasto invio, il sistema viene bruscamente interrotto da
qualsiasi azione stava eseguendo per rispondere alla richiesta di attenzione inviata dalla
tastiera. Se ci si trova all'interno di un programma di scrittura e stiamo scrivendo una lettera,
ad ogni tasto premuto il sistema risponde visualizzando il carattere corrispondente, a meno
che non si tratti di un tasto speciale come ad esempio il tasto invio, il tasto canc, il tasto
freccia in alto e via dicendo. All'interno di un programma di scrittura di testi il tasto invio
permette di andare a capo, mentre all'interno della riga comando serve per avvertire il sistema
che ci occorre la sua attenzione per eseguire il comando appena digitato. Ma prima di inviare il
comando al sistema con il tasto invio, e' possibile editarlo (il termine editare e' uno italianismo
del termine inglese edit che sigifica modificare). Per modificare quanto si sta scrivendo sulla
riga comando esistono molte possibilita'. Ad esempio e possibile andare avanti ed indietro nella
riga comandi premendo i tasti freccia a sinistra e freccia a destra. Oppure e' possibile
cancellare l'ultimo carattere appena digitato premendo il tasto canc. Ma e' anche possibile
cancellare l'ultima parola appena digitata premendo i tasti CTRL-W. Oppure e' possibile
cancellare l'intera riga appena scritta premendo i tasti CTRL-U. I tasti freccia in alto e freccia in
basso invece, sono utili per scorrere la storia dei comandi. La shell infatti memorizza i
comandi che vengono digitati all'interno di un file di sistema, che e' possibile scorrere a video
tramite i tasti freccia in alto e freccia in basso. Cio' permette di eseguire gli stessi comandi piu'
volte senza doverli riscrivere, oppure di visualizzare un comando precedentemente digitato per
modificarlo e rieseguirlo. I comandi eseguiti vengono memorizzati all'interno di un file di
sistema che si trova all'interno della directory home dell'utente e che si chiama
.bash_history. Ad esempio l'utente mau avra' un suo file di storia dei comandi all'interno
della directory /home/mau. Per visualizzare i comandi presenti in questo file e' possibile usare
il comando history. Oppure e' possibile visualizzarlo con il comando cat. Il comando cat
visualizza il contenuto di un file (in realta' lo scopo del comando cat e' ben altro, ma per ora e'
irrilevante saperlo). Ad esempio 'cat pippo' permette di visualizzare a video il contenuto del file
pippo. Quindi l'utente mau puo' visualizzare la sua storia dei comandi con il comando cat
/home/mau/.bash_history. Un sistema Unix o Linux e' un sistema multiutente, percio' nello
stesso istante possono coesistere piu' utenti che lavorano con lo stesso sistema. Bene, ogni
utente ha a disposizione una directory home personale, una shell personale e ovviamente, un
file di storia personale. Esistono pertanto tante copie del file .bash_history quanti sono gli
utenti che posseggono un account su un sistema. Nel caso di un sistema Linux su un PC
utilizzato da un unico utente sara' presente ovviamente una sola copia del file .bash_history.
Digitando il comando

history

verra' visualizzato a video un elenco di tutti i comandi digitati. A sinistra di ciascun comando e'
presente un numero progressivo che rappresenta un identificativo utilizzabile per richiamare
quel dato comando. Infatti digitando il carattere ! e premendo invio viene eseguito l'ultimo
comando appena eseguito, ma digitando il carattere ! seguito da un numero, e' possibile
eseguire il comando referenziato da quel numero. Ad esempio, se digitando history si ottiene:

1 ls
2 cd miadir
3 cat pippo
4 ls -lai *

digitando il comando:

!3
viene eseguito il comando 'cat pippo' (questo comando visualizza il file pippo). Un'altra
scorciatoia molto utile e' il tasto TAB. E' possibile espandere nomi di file o di comandi digitando
una serie di caratteri (pattern) e premendo TAB. Ad esempio, supponiamo di voler eseguire il
comando mount, ma non ricordiamo esattamente il nome del comando. Sappiamo solo che
inizia per 'm' o per 'mo'. Bene, digitando m e premendo TAB, il sistema visualizzera' tutti i
comandi che iniziano per m. Digitando mo e premendo TAB, il sistema visualizzera' tutti i
comandi che iniziano per mo. Premendo il tasto TAB senza digitare alcun carattere il sistema
chiedera' la conferma per visualizzare tutti i comandi che conosce. La ricerca dei comandi
viene effettuata scorrendo la variabile di sistema PATH, che contiene tutte le directory dove il
sistema deve ricercare i comandi da eseguire. Se si digita un comando e successivamente
alcuni caratteri e poi si premte TAB, il sistema non cerca il nome di un comando ma il nome di
un file. Ad esempio, digitando:

ls pr

e premendo TAB senza premere invio, la shell visualizzera' tutti i file che iniziano per pr come
prova, provetta, prosa, prassi e via dicendo, ed inoltre consentira' di proseguire il comando ls
continuando a digitare i caratteri rimanenti. Infatti a questo punto e' possibile digitare ls prov e
premere TAB. La shell visualizzera' ora solo i file prova e provetta. E' un sistema utile quando
sono presenti centinaia di voci e non rammentiamo bene il nome esatto del comando o del file
che ci interessa.

11.5 Sommario delle scorciatoie della shell


 !! riesegue l'ultimo comando appena eseguito
 !n riesegue l'ennesimo comando presente nella storia, dove 'n' e' il numero del
comando da rieseguire
 !stringa riesegue l'ultimo comando che inizia con i caratteri specificati in 'stringa'
 !stringa:p visualizza l'ultimo comando che inizia con i caratteri specificati in 'stringa'
 !?comando? ricerca il comando specificato tra punti interrogativi
 fc 4 permette di modificare in comando numero 4 con l'editor predefinito (solitamente
vi)
 fc -e 'nome' 4 permette di modificare in comando numero 4 con l'editor 'nome'
specificato
 ^comando1^comando2 riesegue l'ultimo comando eseguito che contiene la parola
'comando1' sostituendola con 'comando2'.
 history visualizza l'elenco di tutti i comandi eseguiti
 CTRL-U cancella tutta la riga dalla posizione del cursore all'inizio della riga
 CTRL-K cancella tutta la riga dalla posizione del cursore alla fine della riga
 CTRL-W cancella una parola dalla posizione del cursore all'inizio della riga
 ALT-D cancella una parola dalla posizione del cursore alla fine della riga
 freccia sinistra sposta il cursore di un carattere a sinistra
 freccia destra sposta il cursore di un carattere a destra
 freccia su scorre la storia dei comandi a ritroso
 freccia giu' scorre la storia dei comandi in avanti
 tasto home sposta il cursore all'inizio della riga
 CTRL-A sposta il cursore all'inizio della riga
 tasto fine sposta il cursore alla fine della riga
 CTRL-E sposta il cursore alla fine della riga
 ALT-F sposta il cursore alla fine della parola successiva (F sta per forward, successivo)
 CTRL-B sposta il cursore all'inizio della parola precedente (B sta per backward,
precedente)
 CTRL-T inverte gli ultimi due caratteri a sinistra del cursore, cioe' ab diventa ba (T sta
per transpose)
 ALT-T inverte le ultime due parole a sinistra del cursore, cioe' cp pippo pluto diventa cp
pluto pippo
 ALT-U trasforma in maiuscolo la parola su cui si trova il cursore (U sta per uppercase,
cioe' maiuscolo)
 ALT-L trasforma in minuscolo la parola su cui si trova il cursore (L sta per lowercase,
cioe' minuscolo)
 tasto TAB espande il nome di un file o di un comando

11.6 Gli alias


Digitare frequentemente dei comandi complessi contenenti numerose opzioni puo' essere alla
lunga fastidioso, percio' puo' esser conveniente utilizzare dei sinonimi piu' facili da ricordare e
da digitare. Prendiamo ad esempio il comando:

mount -t vfat /dev/hdc1 /mnt/mydir

questo comando 'monta' la prima partizione presente sul terzo disco alla directory /mnt/mydir,
specificando la tipologia del filesystem (vfat). Supponiamo che tale partizione contenga i file di
Windows: sarebbe interessante poter sostituire tale comando con il comando 'montaWindows'.
Bene, e' effettivamente possibile fare cio' creando l'alias montaWindows per tale comando. Per
creare l'alias 'montaWindows' occorre usare il comando alias:

alias montaWindows='mount -t vfat /dev/hdc1 /mnt/mydir'

questo comando non fa altro che creare il sinomimo montaWindows. Una volta eseguito questo
comando, sara' possibile usare il sinonimo creato: lanciando montaWindows verra' eseguito il
comando 'mount -t vfat /dev/hdc1 /mnt/mydir'. Gli alias possono essere utili anche per creare
dei comandi in lingua italiana. Ad esempio, il comando cp si potrebbe sostituire con 'copia', il
comando rm potrebbe chiamarsi 'cancella' oppure potremmo sostituire il comando pwd con
'dovemitrovo'. Un alias puo' essere utile anche per limitare operazioni pericolose. Ad esempio,
il comando rm cancella i file irreversibilmente senza chiedere conferma, a meno che non venga
usata l'opzione -i. Pertanto, poiche' il comando rm cancella senza conferma, mentre il
comando 'rm -i' cancella chiedendo conferma, e' possibile creare un sinonimo di rm in questo
modo:

alias rm='rm -i'

da questo momento in poi, digitando il comando rm il sistema chiedera' conferma prima di


cancellare qualsiasi file. ;o) Per eliminare un alias e' possibile usare il comando <b<unalias<
b="">: </b<unalias<>

unalias rm

dove rm e' il nome dell'alias.

11.7 Opzioni per modificare il comportamento della shell


Per evitare di uscire dalla shell digitando inavvertitamente i tasti CTRL-D, e' possibile usare
l'opzione ignoreeof. Quest'opzione viene attivata con il comando set. Il comando set accetta
due opzioni: -o e o. -o imposta ignoreeof mentre o lo elimina. Il altre parole, per impostare
l'opzione ignoreeof occorre digitare il comando:

set -o ignoreeof

mentre per ripristinare il comportamento normale della shell occorre digitare:

set o ignoreeof
Esistono altre opzioni per modificare il comportamento della shell, tra le quali noclobber e
noglob. La prima serve per evitare che un file venga cancellato inavvertitamente da una
operazione di redirezione. Ad esempio il comando 'cat pippo > pluto' potrebbe sovrascrivere il
contenuto di pluto con pippo. Impostando l'opzione noclobber con il comando set, non sara'
piu' possibile sovrascrivere il file pluto in quanto la shell presentera' un messaggio di errore.
Vediamo:

ls > pluto (copio l'elenco dei file presenti nella directory all'interno del file pluto)
set -o noclobber (ora imposto noclobber)
ls > pluto (provo ad eseguire nuovamente questo comando ma non riesco perche' la shell
invia un messaggio di errore)
pluto: file exist (la shell infatti mi informa che il file esiste gia' e l'operazione non e'
consentita in quanto lo cancellerebbe).

L'opzione noglob infine, disattiva i caratteri speciali della shell. Una volta impostata tale
opzione i caratteri *, ?, [, ] e ~ non verranno piu' espansi se presenti all'interno di un file. Cio'
puo' essere utile per fare riferimento a file che contengono tali caratteri. Ad esempio, scrivendo
'pippo*' normalmente la shell espanderebbe pippo con tutti i nomi dei file che iniziano con
'pippo' e terminano con una qualsiasi sequenza di caratteri: impostando l'opzione noglob, cio'
non accadrebbe piu' e sarebbe possibile far riferimento al file pippo* senza problemi. Inutile
dire che e' comunque meglio evitare di creare nomi di file contenenti caratteri speciali! ;o)

11.8 Configurazione della shell


La shell BASH ha delle impostazioni standard generali per tutti gli utenti, ma ogni utente puo'
avere una versione personalizzata della propria shell modificando opportunamente alcuni file di
configurazione. All'interno di questi file sono presenti delle variabili che contengono dei valori
predefiniti. Una variabile e' un'area di memoria alla quale viene assegnato uno specifico valore.
Tale area viene creata al momento del login e viene distrutta al momento del logout. Una
variabile puo' essere paragonata ad una scatola nera che puo' contenere dei valori. Ad esempio
la variabile USER contiene il nome dell'utente che ha effettuato il login. Quando un utente
effettua il login nel sistema, Linux crea una shell di login ed inizializza alcune variabili con dei
valori predefiniti. L'utente puo' creare delle variabili personali e puo' modificare alcune di quelle
predefinite dal sistema. Per convenzione le variabili della shell sono definite con nomi costituiti
da lettere maiuscole. Ad esempio, la variabile HOME definisce il percorso della directory home
dell'utente. Le variabili vengono inizializzate dal sistema al momento del login da parte di un
utente e vengono distrutte al momento del logout, cioe' quando l'utente si scollega dal
sistema. Per visualizzare il contenuto di una variabile si puo' eseguire il comando echo,
seguito dal carattere $ e dal nome della variabile. Ad esempio, un ipotetico utente mau,
visualizzando la variabile HOME utilizzando il comando:

echo $HOME

produrra' a video il percorso /home/mau. I file di configurazione della shell BASH sono 5:

 /etc/profile
 /etc/bashrc
 /home/mau/.bash_profile
 /home/mau/.bashrc
 /home/mau/.bash_logout

le prime 2 sono generali per tutti gli utenti e le altre 3 sono specifiche per ogni utente e se
vengono modificate ridefiniscono quelle generali. Percio' se un utente 'mau' modifica ad
esempio il file .bash_profile presente nella sua directory home (/home/mau), tali modifiche
saranno valide per lui ma non per tutti gli altri utenti che continueranno ad usare il file profile
generale presente nella directory /etc. Stesso discorso vale per il file bashrc. Il file profile
presente nella directory /etc puo' essere paragonato al file autoexec.bat del dos. Questo file
infatti contiene l'inizializzazione di alcune variabili di sistema e l'esecuzione di alcuni
programmi di avvio. I file bashrc invece contiene gli alias ed alcune funzioni definibili
dall'utente. Un alias e' un nome mnemonico che si puo' dare ad un comando. Ad esempio il
comando che visualizza tutti i file di una directory:

ls -lai

puo' avere come alias il nome 'elencafile' oppure 'lsa' oppure 'dir' (per gli amanti del DOS) o un
qualsiasi altro nome scelto dall'utente. Un'altra differenza che contraddistingue il file profile dal
file bashrc sta nel fatto che mentre il primo viene eseguito al momento del login da parte
dell'utente, il secondo viene eseguito ogni volta che viene creata una shell o una subshell.
Quando l'utente effettua il login, viene creata una shell chiamata shell di login, percio' viene
eseguito prima il file profile e poi il file bashrc. Quando un utente crea una subshell od una
shell che non e' di login (ad esempio apre una finestra Xterm) viene eseguito il file bashrc. In
un sistema Linux, oltre ai file di inizializzazione della shell attivati al momento del login, e'
presente anche un file attivato al momento del logout. Una subshell viene creata
automaticamente dal sistema ogni volta che si esegue un comando oppure intenzionalmente
dall'utente lanciando il comando che crea la shell (che nel caso di shell BASH e' 'bash'). Ogni
volta che un utente effettua il logout e si scollega dal sistema, viene eseguito il file
.bash_logout. In questo file sono presenti tutte le operazioni di chiusura che l'utente vuole
eseguire. Questo file non viene creato automaticamente come gli altri gia' visti, percio' occorre
crearlo manualmente con un editor di testi qualsiasi (ad esempio vi). In questo file puo' essere
contenuto ad esempio un messaggio di arrivederci con un comando tipo echo "Arrivederci".
Riassumendo, non appena un utente effettua il login nel sistema:

 viene automaticamente creata una shell di login


 viene eseguito il file profile presente nella directory /etc che definisce le variabili
generali
 viene eseguito il file .bash_profile presente nella directory home dell'utente
 viene eseguito il file bashrc presente nella directory /etc dove sono presenti gli alias
dei comandi
 viene eseguito il file .bashrc presente nella directory home dell'utente

non appena l'utente effettua il login dal sistema, viene eseguito il file .bash_logout.

11.9 I caratteri speciali di completamento dei nomi


Come gia' visto precedentemente per nominare un file e' possibile usare fino a 256 caratteri,
pero' esiste una limitazione: non e' possibile usare determinati caratteri. Tali caratteri infatti
sono speciali, in quanto se presenti, vengono interpretati dalla shell seguendo delle regole ben
precise. Ecco un elenco di alcuni di tali caratteri:

. / " $ & ' ? ~ ! < > * = .. ; [ ]

ognuno di questi caratteri ha un significato ben preciso per la shell, ad esempio il carattere '.'
significa la directory corrente, il carattere '..' e' la directory immediatamente superiore, il
carattere '~' rappresenta la directory home, il carattere ';' e' usato per separare piu' comandi
sulla stessa riga e via dicendo. I caratteri speciali che verranno analizzati qui sono i caratteri:
'*', '?' , '[' e ']'. Tali caratteri vengono definiti anche caratteri jolly o anche wildcards. Si tratta
di caratteri che vengono usati dalla shell per espandere il nome di un file. Il carattere '*'
significa qualsiasi carattere o qualsiasi sequenza di caratteri. Digitando una serie di
caratteri e terminando la serie con il carattere *, la shell individuera' tutti i file all'interno della
directory corrente i cui nomi iniziano con la serie di caratteri digitati e terminano con una
sequenza qualsiasi di caratteri. Digitando solo il carattere *, la shell individuera'
semplicemente tutti i file all'interno della directory corrente. Ad esempio, supponiamo che
l'utente mau possegga all'interno della directory documenti i file lettera1, lettera2, doc,
letteratura.txt, lettere e doc2. Il comando:
ls lett*

visualizzera' solo i file lettera1, lettera2, lettere e letteratura.txt. i file doc1 e doc2 non
verranno visualizzati. Cio' perche' la shell considerera' tutti i file presenti all'interno della
directory che iniziano con i caratteri 'lett' e terminano con una sequenza qualsiasi di caratteri. I
file doc1 e doc2 non soddisfano tali requisiti. Invece, digitando il comando:

ls lettera*

la shell visualizzera' i file lettera1, lettera2 e letteratura.txt. Questa volta, oltre ai file doc1 e
doc2 verra' escluso dall'elenco anche il file lettere che percio' non verra' visualizzato. Ma
supponiamo ora che di tutti i file presenti nella directory volessimo visualizzare solamente i file
lettera1 e lettera2: come dovremmo usare il carattere '*'? Bene, in questo caso scrivendo
lettera* visualizzeremmo i file lettera1, lettera2 ma anche letteratura.txt. Come fare allora?
Semplice: potremmo usare il carattere '?'. Infatti il carattere ? significa qualsiasi
carattere. Nel nostro esempio pertanto potremmo scrivere il comando: 'ls lettera?'. La shell
interpretera' la stringa 'lettera?' cercando all'interno della directory corrente tutti i file il cui
nome inizia per 'lettera' e termina con 1 carattere qualsiasi. La differenza tra il carattere * ed il
carattere ? sta nel fatto che mentre con il primo si intende qualsiasi sequenza di caratteri, con
il secondo si intende qualsiasi carattere. Per specificare ulteriormente i caratteri da espandere,
si puo' specificare uno o piu' caratteri all'interno di una coppia di parentesi quadre. I caratteri
definiti tra parentesi quadre stabiliscono un insieme di caratteri validi che devono essere
presenti all'interno del nome dei file da considerare. Ad esempio, supponendo di avere
all'interno della directory corrente i file doc1, doc2, docA1, docB1 e doca2, specificando la
stringa doc[1A] prenderemmo in considerazione solamente i file doc1 e docA1 in quanto
entrambi hanno un nome che inizia con i caratteri doc e termina con il carattere 1 o con il
carattere A. Come possiamo osservare non viene preso in considerazione neanche il file doca2
perche' Linux e' un sistema case sensitive, cioe' fa differenza tra caratteri maiuscoli e caratteri
minuscoli. Per Linux i caratteri 'A' e 'a' sono due caratteri completamente diversi. Se avessimo
voluto prendere in considerazione anche il file doca2, avremmo dovuto scrivere doc[1Aa].
L'ordine con il quale vengono digitati i caratteri jolly all'interno delle parentesi quadre e'
ininfluente, percio' avremmo potuto scrivere anche doc[Aa1] o doc[A1a]. Ma i caratteri jolly
possono servire non solamente a specificare dei nomi di file che cominciano con una sequenza
di caratteri prestabilita e terminano con una sequenza di caratteri indefinita: infatti e' possibile
anche effettuare l'operazione inversa, cioe' specificare dei nomi di file che iniziano con una
sequenza di caratteri indefinita e che pero' terminano con dei caratteri ben precisi. Ad
esempio, supponendo di avere all'interno della directory corrente i file doc.txt, doc.doc,
lettera.doc e lettera.txt, scrivendo *.doc specificheremmo solamente i file doc.doc e
lettera.doc. All'interno delle parentesi quadre e' possibile anche specificare una sequenza di
valori usando il carattere '-'. Ad esempio la stringa doc[1-9] significa qualsiasi file il cui nome
inizia per doc e termina con una cifra da 1 a 9. Supponendo di avere all'interno della nostra
directory i file doc1, doc2, doc3, doc4 e doc5, scrivendo la stringa doc[1-3] la shell prendera'
in considerazione unicamente i file doc1, doc2 e doc3. E' possibile combinare tutti questi
caratteri speciali in qualsiasi modo, percio' una stringa del tipo d?c[1-3].* e' perfettamente
valida. Supponendo infatti di avere all'interno della nostra directory i file doc1.txt, dic2.txt,
dec3.doc, documenti, lettere, fax e moduli, con la stringa d?c[1-2].* prenderemmo in
considerazione unicamente i file doc1.txt, dic2.txt e dec3.doc.

11.10 Standard input, standard output e standard error


Quando Unix, e di conseguenza anche Linux, quando vennero sviluppati, si volle mantenere
indipendenti la struttura logica dei file dalla conformazione del supporto fisico, di conseguenza
venne sviluppato il concetto secondo il quale qualsiasi dispositivo fisico puo' essere visto
logicamente come un file, ossia come una sequenza continua di byte. Questo concetto logico si
estende anche alle operazioni di input ed output, infatti in queste operazioni i dati sono
organizzati come sequenze continue di byte. I dati introdotti in input con la tastiera vengono
inseriti all'interno di un canale costituito da una sequenza continua di byte. Allo stesso modo i
dati prodotti a video vengoni inseriti all'interno di un altro canale. Tali canali vengono definiti
standard input e standard output. Pertanto la tastiera e' il canale standard input ed il video
e' il canale standard output. Qualsiasi comando quindi, accetta in input dei valori che legge
dallo standard input (la tastiera) e produce dei risultati scrivendoli nello standard output (il
video). Ma un comando puo' produrre anche dei messaggi di errore se per esempio vengono
introdotti dei valori in input non validi. I messaggi di errore dei comandi vengono scritti in un
terzo canale, lo standard error. Lo standard error generalmente coincide con lo standard
output, nel senso che sia il risultato di un comando che eventuali errori vengono visualizzati a
video. Digitando il comando cat pippo, otterremo a video il contenuto del file pippo, in quanto
cat accetta dallo standard input (la tastiera) l'argomento pippo e lo invia allo standard output
(il video). Cosa accade pero' se si digita cat senza argomenti e si preme il tasto invio? Verra'
eseguito il comando cat che leggera' lo standard input. In pratica ìl comando cat accetta da
tastiera degli argomenti. A questo modo e' possibile scrivere un documento come una lettera
ad esempio. Per terminare la scrittura della lettera occorre premere la combinazione di tasti
CTRL-D. Tale combinazione di tasti corrisponde infatti al carattere di fine file (EOF, cioe' End Of
File). Infatti lo standard input e' un file come un altro. Usando CTRL-D scriviamo il carattere di
fine file ed il comando cat smettera' di leggere cio' che digitiamo. Questi 3 canali sono dei file
ben distinti e separati, e questo e' un concetto importante da tenere a mente, poiche' Linux
(cosi' come Unix) ci consente di redirigere un canale standard da o verso un file. E' possibile ad
esempio inviare l'output prodotto da un comando non a video (il canale standard output) ma
all'interno di un file. Oppure e' possibile far si che un comando accetti in input dei valori
prendendoli da un file piuttosto che dalla tastiera. Queste operazioni di redirezione vengono
effettuate utilizzando gli operatori di redirezione '<', '>' e '>>'. Il primo e' l'operatore di
redirezione dell'input standard, mentre gli ultimi due sono gli operatori di redirezione
dell'output standard. Supponiamo che digitando il comando ls il sistema produca a video la
seguente lista di file:

pippo
pluto
topolino
minnie

supponiamo ora di voler conservare questo elenco di file all'interno di un documento chiamato
disney.txt. Bene, per eseguire tale operazione dovremmo redirigere l'output standard (il video)
all'interno del file disney.txt tramite l'operatore '>' in questo modo:

ls > disney.txt

eseguendo tale comando la shell interpretera' l'operatore '>' redirigendo il risultato del
comando ls all'interno del file disney.txt. In altre parole tale comando fornisce le seguenti
istruzioni per la shell: 'esegui il comando ls ma non inviare il risultato a video perche' lo devi
inviare all'interno del file disney.txt; se tale file non esiste crealo'. Cosi' la shell creera' il file
disney.txt ed inviera' l'output del comando ls all'interno di tale file. Qualora il file disney.txt
fosse gia' presente, la shell lo creera' nuovamente, distruggendo eventuali dati gia' presenti.
Nel nostro esempio il comando ls > disney.txt creera' il file disney.txt scrivendo all' interno i
seguenti valori:

pippo
pluto
topolino
minnie
disney.txt

per visualizzare il contenuto del file disney.txt potremmo utilizzare il comando cat:

cat disney.txt
pippo
pluto
topolino
minnie
disney.txt

Come possiamo notare all'interno del file disney.txt e' presente anche il nome disney.txt.
Infatti con l'operatore > la shell prima crea il file disney.txt e successivamente esegue il
comando ls (che elenchera' anche il file appena creato). Supponiamo ora di cancellare i file
pluto topolino e minnie dalla nostra directory e di creare un file chiamato paperino.

ls (elenca i file della directory)


pippo
pluto
topolino
minnie
disney.txt
ls > disney.txt (reindirizza il risultato del comando ls nel file disney.txt)
cat disney.txt (visualizza il contenuto del file disney.txt)
pippo
pluto
topolino
minnie
disney.txt
rm pluto topolino minnie (cancella i file pluto topolino e minnie)
ls (visualizza il contenuto della directory dove ora sono presenti solo i file pippo e disney.txt)
pippo
disney.txt
cp pippo paperino (copia pippo in paperino, cioe' crea il file paperino che e' una copia di
pippo)
ls (visualizza il contenuto della directory che ora contiene i file pippo, paperino e disney.txt)
pippo
paperino
disney.txt
ls > disney.txt (reindirizza il risultato del comando ls nel file disney.txt distruggendo cio' che
conteneva precedentemente)
cat disney.txt (infatti visualizzando il file disney.txt vedremo che pluto, topolino e minnie non
sono piu' presenti)
pippo
paperino
disney.txt

Come abbiamo visto, eseguendo nuovamente il comando ls > disney.txt il contenuto


precedente del file disney.txt viene azzerato e vengono scritti all'interno solo i nomi pippo e
paperino. Come fare per aggiornare il file disney.txt senza distruggere il contenuto
precedente? Potremmo usare l'operatore di redirezione dello standard output '>>'. Infatti tale
operatore non distrugge il file, in quanto permette di accodare alla fine del file il risultato
prodotto dal comando. Tornando al nostro esempio eseguendo il comando ls per la seconda
volta ma utilizzando l'operatore >> al posto dell'operatore > il contenuto del file non verrebbe
cancellato in quanto il risultato del comando ls verrebbe accodato ad esso. Percio' eseguendo il
comando ls >> disney.txt si otterrebbe un file disney.txt contenente i nomi: pippo, pluto,
topolino, minnie, pippo e paperino. Come si puo' osservare pippo e' ripetuto due volte in
quanto il contenuto del file non e' stato cancellato. Ovviamente per utilizzare il comando ls >>
disney.txt occorre che il file disney.txt sia esistente, in caso contrario otterremmo un errorre
dalla shell. Infatti mentre l'operatore '>' crea un nuovo file, l'operatore '>>' accoda all'interno
di un file gia' esistente. L'operazione di redirezione puo' avvenire anche all'inverso, in quanto e'
possibile redirigere il canale di input standard. Generalmente i comandi accettano dei valori
leggendoli dal canale di input standard cioe' dalla tastiera. Per esempio il comando:
ls /home/mau/documenti

visualizza il contenuto della directory /home/mau/documenti. Tale directory e' un argomento


che abbiamo fornito noi in input digitandolo da tastiera. E' possibile pero' fare in modo che il
comando ls prenda in input l'argomento '/home/mau/documenti' non da tastiera ma ad
esempio da un file. Cio' puo' essere fatto redirigendo l'input standard, cioe' copiando il
contenuto di un file all'interno del canale di input standard (ricordiamo che in Linux la struttura
fisica dei dispositivi e' separata dalla struttura logica dei file, pertanto un comando sa che deve
accettare eventuali argomenti dal canale di input standard ignorando completamente se tale
canale sia la tastiera, il disco o qualsiasi altro supporto). Ad esempio il comando:

ls < pippo

redirige il contenuto del file pippo nell'input standard. Come gia' accennato, ogni volta che
viene eseguito un comando la shell si occupa di collegare input standard ed output standard al
comando stesso: interpretando il comando, in questo caso la shell collega l'output standard e
l'input standard ma prima di far cio' redirige il contenuto del file pippo all'interno dell'input
standard. Percio' se all'interno del file e' presente la stringa '/home/mau/documenti', tale
stringa verra' copiata dalla shell (cioe' rediretta) nel canale di input standard. Quando il
comando ls leggera' l'input standard, trovera' quindi all'interno la stringa
'/home/mau/documenti' appena copiata dalla shell. E' possibile redirigere l'input e l'output
standard contemporaneamente, infatti il comando:

cat < pippo > pluto

legge dal file pippo e scrive il risultato nel file pluto.

11.11 Le pipe
Spesso puo' essere utile inviare l'output di un comando non a video ma ad un altro comando
prima di essere visualizzato. Supponiamo ad esempio di voler visualizzare l'elenco dei file
contenuti all'interno di una directory. Supponiamo anche che il risultato prodotto sia un elenco
interminabile di file. E possibile in questo caso utilizzare il comando more che accetta in input
dei dati e li visualizza a video sotto forma di pagine formattate, consentendo la visualizzazione
di una pagina per volta. Premendo un tasto qualsiasi il comando more visualizza la pagina
successiva. Come e' possibile collegare l'output prodotto dal comando ls al comando more? E'
possibile utilizzando l'operatore di pipe '|'. E' il carattere che si trova nel tasto posto sotto al
tasto esc. Si ottiene premendolo contemporaneamente al tasto shift (il tasto che permette di
scrivere un carattere maiuscolo). Usando l'operatore di pipe '|' comunichiamo alla shell che e'
necessario collegare l'output standard del primo comando all'input standard del secondo
comando. Ad esempio il comando:

ls | more

invia il risultato del comando ls al comando more. Il comando more a sua volta formatta tale
risultato e lo invia a video. E' possibile usare il comando less al posto del comando more, in
quanto tale comando permette di scorrere le pagine avanti ed indietro (less e' in sostanza una
versione migliorata del comando more). Un'altra cosa che Linux permette di fare e' consentire
ad un comando di accettare degli argomenti sia dall'input standard e sia da un file. Redirigere
l'input standard con l'operatore < significa in pratica copiare dei dati da un file all'interno
dell'input standard. Puo' essere utile pero' far si che un comando accetti degli argomenti
dall'input standard e contemporaneamente da un file. E' possibile farlo utilizzando l'operatore
'-'. L'operatore '-' rappresenta infatti l'input standard. Vediamo un esempio concreto.
Supponiamo di voler visualizzare il contenuto di un file a video usando il comando cat.
Supponiamo anche di voler visualizzare prima del contenuto del file anche il nome della
directory corrente che lo contiene. Il nome della directory corrente si ottiene con il comando
pwd (Print Working Directory, cioe' stampa la directory di lavoro). Potremmo scrivere percio':
pwd | cat - miofile

cosa significano questi comandi? Il comando pwd crea in output il nome della directory,
supponiamo /home/mau ma invece di visualizzarlo a video lo invia tramite l'operatore di pipe
'|' al comando cat. Cio' accade perche' la shell interpretando la riga comandi, nota l'operatore
di pipe '|' e quindi collega l'output standard del comando pwd all'input standard del comando
cat. Il comando cat quindi leggera' dall'input standard la riga prodotta da pwd (/home/mau)
ma anche il nome del file miofile (miofile e' stato digitato con la tastiera pertanto si trova
all'interno dell'input standard). In altre parole il comando cat visualizzera' prima il primo
argomento letto dall'input standard, cioe' il nome della directory prodotto dal comando pwd e
successivamente il secondo argomento letto dall'input standard, cioe' il file miofile. Il risultato
finale sara' il nome della directory ed il contenuto del file prodotti a video. Cosi' come e'
possibile forzare un comando ad accettare un argomento dall'input standard ed un altro da un
file e' anche possibile l'operazione inversa. E' possibile cioe' inviare il risultato di un comando
sia a video (output standard) che in un file. Cio' e' possibile utilizzando il comando tee. Il
comando cat pippo visualizza a video il contenuto del file pippo. Per visualizzarne il contenuto e
contemporaneamente scriverlo all'interno di un altro file, si puo usare il comando tee in questo
modo:

cat pippo | tee pluto

il comando cat legge il contenuto del file pippo e lo invia allo standard output. La shell pero'
notando l'operatore di pipe '|' collega l'output standard del comando cat all'input standard del
comando tee. Il comando tee a sua volta legge il contenuto del file pippo dal suo input
standard e lo invia sia a video che all'interno del file pluto. Vediamo a questo punto come
redirigere l'ultimo canale standard visto: il canale error standard. Quando un comando viene
eseguito, normalmente viene prodotto un risultato a video. Se pero' sbagliamo qualcosa nel
digitare il comando, viene prodotto a video un messaggio di errore. Supponiamo ad esempio di
voler visualizzare il contenuto del file pippo ma digitando erroneamente scriviamo:

cat peppo

poiche' il file peppo non esiste, il comando cat visualizzera' a video il messaggio di errore cat:
peppo not found. Infatti normalmente il canale di standard error e' il video. Come fare per
scrivere il messaggio di errore non a video ma all'interno di un file? Ogni canale standard e'
identificabile da un numero: 0 per il canale standard input, 1 per il canale standard output e 2
per il canale standard error. Digitare il comando cat peppo > errori non produrrebbe il risultato
voluto in quanto l'operatore > redirige lo standard output all'interno del file errori ma non lo
standard error. Poiche' il file peppo non esiste il comando cat non scrive nulla nello standard
output ma scrive un messaggio di errore nello standard error. Per specificare quale canale
redirigere nel file errori si puo' usare il numero che lo identifica. Nel nostro esempio
desideriamo redirigere lo standard error (2) e non lo standard output (1) all'interno del file
errori. Pertanto possiamo scrivere:

cat peppo 2> errori

Il numero 2 a sinistra dell'operatore > specifica che il canale che intendiamo redirigere non e'
lo standard output ma bensi' lo standard error. Il comando cat pippo > pluto scrive il
contenuto di pippo all'interno di pluto. Questo perche' per default l'operatore > redirige lo
standard output. In pratica e' la stessa cosa che scrivere cat pippo 1> pluto, in quanto in tal
caso 1 se non specificato e' sottinteso. Diverso e' il comando 'cat pippo 1> dati 2> errori'. Qui
si vuole infatti specificare che occorre inviare il contenuto di pippo all'interno del file dati ed
eventuali errori all'interno del file errori. Tanto per complicare un po' di piu' le idee, illustriamo
come sia possibile redirigere un canale standard all'interno di un altro canale standard. Fino ad
ora abbiamo infatti rediretto un canale standard verso un file o viceversa, un file verso un
canale standard. Per far riferimento ad un canale standard occorre usare l'operatore &. Infatto
&0 fa riferimento al canale standard input, &1 fa riferimento al canale standard output e &2 fa
riferimento al canale standard error. Quindi la stringa '2>&1' significa: redirezione del canale
standard error (2) nel canale standard output (&1). Vediamo ad esempio il comando:

cat non_esiste 1> dati 2>&1

tale comando visualizza il contenuto del file non_esiste inviandolo non a video ma all'interno
del file dati ed inoltre invia eventuali errori all'interno dello stesso file. In questo caso poiche' il
file non_esiste come dice il nome non esiste ;o) verra' prodotto un messaggio di errore.
Analizziamo il comando: cat legge dallo standard input il nome del file da visualizzare ma
poiche' non lo trova produce un errore; inoltre il comando cat sa che il contenuto di tale file
dovrebbe essere inviato all'interno del file dati e non a video ('1>' infatti redirige lo standard
output nel file dati); infine cat sa che lo standard error deve essere rediretto nello standard
output ('2>&1' infatti redirige lo standard error nello standard output). Il risultato e' che il
messaggio di errore andrebbe scritto nello standard error ma, poiche' tale canale e' stato
rediretto, viene scritto nello standard output. Poiche a sua volta lo standard output e' stato
rediretto nel file dati, il messaggio di errore verra' scritto nel file dati. Seguendo lo stesso
percorso logico, la stringa '1>&2' redirige lo standard output verso lo standard error.
Riassumendo, '2>&1' invia lo standard error verso lo standard output e 1>&2 produce il
risultato contrario.

11.12 Variabili d'ambiente

Una variabile e' una parte della memoria RAM identificata da un nome che puo' contenere dei
valori. E' una scatola nera, un contenitore all'interno del quale possiamo collocare una data, un
nome, una cifra e cosi' via. Quando viene definita una nuova variabile, il sistema associa al
nome di tale variabile un indirizzo di memoria RAM a partire dal quale viene memorizzato un
valore stabilito dall'utente. Per definire e valorizzare una variabile e' sufficiente assegnare un
valore ad un nome, ad esempio, il comando:

PIPPO=253

definisce una variabile di nome PIPPO ed assegna a questa variabile il valore '253'. In altre
parole il sistema alloca uno spazio di memoria RAM a partire da un indirizzo scelto da lui (ad
esempio 12234) una zona all'interno della quale memorizza il valore 253. Cio' significa che la
cella di memoria 12234 contiene 2, la cella contigua 12235 contiene 5 e la cella contigua
12236 contiene 3. Queste 3 celle contigue rappresentano una entita' di nome PIPPO. Oltre a
contenere valori numerici, una variabile puo' contenere anche stringhe, cioe' insiemi di
caratteri. Ad esempio il comando PIPPO=scatola, crea una variabile di nome PIPPO all'interno
della quale viene memorizzato la stringa 'scatola'. Molte variabili contengono dei valori utili al
sistema. Ad esempio la variabile USERNAME contiene il nome dell'utente che si e' collegato al
sistema mentre la variabile HOME contiene il path della directory home dell'utente (ad esempio
/home/mau). Una stringa puo' contenere anche il carattere spazio, ma in questo caso occorre
racchiuderla tra doppi apici. Ad esempio il comando MIAVAR="ciao a tutti" crea una variabile di
nome MIOVAR e ne memorizza all'interno la stringa "ciao a tutti". Il carattere '=' rappresenta
l'operatore di assegnamento, cioe' l'operatore che assegna il valore alla variabile. Tra il nome
della variabile, l'operatore '=' ed il valore vero e proprio non devono essere presenti spazi.
Quindi il comando MIAVAR = 234 non e' corretto, perche' sono presenti degli spazi. Invece il
comando MIAVAR=234 e' un comando corretto. Una variabile ha vita solo all'interno della shell
nella quale viene creata, pertanto cambiando shell, il sistema non riconosce piu' tale variabile.
Come sappiamo e' possibile creare delle subshell, cioe' delle shell all'interno di altre shell.
Bene, se definiamo la variabile PIPPO all'interno di una data shell e successivamente apriamo
una seconda shell, la variabile PIPPO rimarra' sconosciuta alla seconda shell. Possiamo
verificarlo con questa sequenza di comandi:

PIPPO=maurizio (definisco una variabile di nome PIPPO che contiene la stringa 'maurizio')
set (il comando set visualizza tutte le variabili definite: mostrera' anche la variabile PIPPO)
bash (creo una subshell bash)
set (visualizzo le variabili definite e, con sorpresa, non trovo piu' la variabile PIPPO)
exit (o CTRL-D, con questo comando esco dalla subshell e torno alla shell principale)
set (ripeto il comando set ed ora la variabile PIPPO e' nuovamente visibile)

Affinche' una variabile sia visibile all'interno di qualsiasi shell, occorre 'esportarla' mediante il
comando export. Ad esempio:

PIPPO="ciao a tutti"
export PIPPO (ora la variabile PIPPO e' visibile da qualsiasi shell)

Per visualizzare le variabili definite si puo' usare il comando set, mentre per visualizzare le
variabili di ambiente occorre usare il comando env. Le variabili di ambiente sono tutte quelle
variabili predefinite all'interno della shell che troviamo normalmente all'interno di un sistema
Linux. Per visualizzare l'elenco di tali variabili usiamo il comando env:

env

Per visualizzare il contenuto di una variabile invece, occorre usare il comando echo insieme
all'operatore '$'. L'operatore $ serve per far riferimento al contenuto di una variabile.
Ad esempio il comando:

echo $PIPPO

visualizza a video il contenuto della variabile PIPPO.

11.13 Comandi eseguiti in background


I programmi che sono in esecuzione sotto Linux vengono definiti processi. Quando si esegue
un comando come ad esempio ls, viene messo in esecuzione un processo. Ogni processo sotto
Linux viene identificato da un numero univoco: il PID. PID sta per Process IDentifier cioe'
identificatore del processo. A dire il vero i comandi eseguiti dagli utenti vengono definiti job. Si
tratta sempre di processi, ma i processi utente vengono definiti job per distinguerli dai processi
di sistema. Un comando, come qualsiasi processo puo' essere eseguito in background (lett. in
sottofondo) nel senso che mentre il comando e' in esecuzione l'utente puo' eseguire altre
attivita'. E' conveniente eseguire un comando in background quando la sua esecuzione richiede
del tempo. Ad esempio la ricerca di un file all'interno di un grosso disco scorrendo tutte le
directory richiede un po' di tempo: e' possibile eseguire tale ricerca in background e nel
frattempo eseguire altre attivita'. Per eseguire un comando in background e' sufficiente
apporre il carattere di e commerciale '&' alla fine del comando. Ad esempio per stampare un
file molto grande si puo' lanciare il comando lpr in questo modo:

lpr dati &


[1] 534

il carattere & permette di eseguire il comando lpr in background, permettendoci di eseguire


altre attivita' evitando di dover aspettare che il comando termini la sua esecuzione. La riga
presente sotto a quella che contiene il comando rappresenta il numero del job (1) ed il PID del
processo (4). Cio' significa che il comando lpr appena lanciato e' identificato dal sistema come
processo numero 534 e job numero 1. E' possibile eseguire in background piu' di un comando
contemporaneamente. Per visualizzare i comandi in esecuzione in background (cioe' i job) si
utilizza il comando jobs.

jobs
[1] running lpr dati
[2] - running cat *.txt > testo
Il comando jobs visualizza i comandi in esecuzione in background e per ogni comando viene
indicato il numero del job, lo stato del comando (running o stopped) ed il comando stesso. Un
comando in stato running e' un comando in esecuzione mentre un comando in stato stopped e'
un comando fermo. Il carattere indica il job corrente, cioe' quello attualmente in esecuzione,
mentre il carattere - indica il prossimo job che verra' eseguito. Non bisogna confondere un job
stopped da uno non ancora in esecuzione: il primo e' un job fermo in attesa di essere riattivato
o ucciso, il secondo e' un job attivo ma non ancora in esecuzione. Questa distinzione e'
importante perche' l'utente puo' spospendere un job e riattivarlo in qualsiasi momento. E'
possibile eseguire piu' comandi in background digitandoli sulla stessa riga e terminandoli con il
carattere &:

lpr dati & cat *.txt > testo &


[1] 534
[2] 567

Linux avverte della conclusione di job solo quando si termina il comando corrente (ad esempio
una sessione di editing in vi). Per essere avvertiti della conclusione di un job si puo' utilizzare il
comando notify. Usando il comando notify, non appena un job termina Linux interrompe il
comando corrente (indipendentemente dall'operazione che si sta svolgendo) avvertendo
l'utente della conclusione del job. Il comando notify accetta come parametro il numero del job
che desideriamo tenere sotto controllo. Nell'esempio precedente, digitando il comando notify
%1 Linux avvisera' l'utente - interrompendo qualsiasi azione stia facendo - non appena il
comando 'lpr dati' (il job numero 1) sara' terminato. E' possibile portare un comando da
modalita' background a modalita' foreground (la normale modalita' di esecuzione dei comandi)
utilizzando il comando fg. E' anche possibile l'operazione opposta, cioe' da foreground a
background mediante il comando bg. E' possibile annullare l'esecuzione di un comando
mediante il comando kill (lett. uccidi, uccidere). Nell'esempio precedente il comando kill %2
annullera' l'esecuzione del job numero 2 ossia del comando 'cat *.txt > testo'. E' possibile
annullare un comando indicando il numero del job od il numero del processo cioe' il PID. E'
possibile anche sospendere temporaneamente un comando senza ucciderlo definitivamente
premendo la combinazione di tasti CTRL-Z. L'utente puo' riattivare il comando un secondo
tempo in modalita' background o foreground mediante i comandi bg e fg. Tutto cio' puo' essere
utile nei casi in cui l'esecuzione di un comando appena digitato richieda piu' tempo del
previsto. In questo caso e' possibile mettere in background il comando, ma occorre prima di
tutto sospenderne l'esecuzione premendo i tasti CTR-Z. Non e' possibile infatti mettere il
comando in background direttamente senza averne prima sospeso l'esecuzione. Una volta
sospesa l'esecuzione del comando, lo si puo' mettere in backgroun usando il comando bg:

lpr divinacommedia.txt (stampo tutta la divina commedia ;o)


CTRL-Z (sospendo l'esecuzione del comando non appena mi accorgo che il comando richiede
molto tempo)
jobs
[1] 345 stopped lpr divinacommedia.txt
bg %1 (metto in background il comando di stampa)

11.14 Programmazione della shell


Nei sistemi Linux e' possibile programmare la shell, cioe' creare dei programmi per eseguire
automaticamente delle operazioni che altrimenti si dovrebbero eseguire manualmente. Ad
esempio e' possibile creare un programma per effettuare periodicamente il salvataggio dei dati
su un supporto esterno (nastro, floppy, CD-Rom etc), oppure si potrebbe scrivere un
programma che controlla lo spazio occupato sul disco e produce delle statistiche. Non ci sono
limiti alle possibili applicazioni che possono essere gestite automaticamente da un programma:
l'unico limite e' la fantasia del programmatore. Bene, tali programmi vengono definiti script
della shell (per chi conosce il SO MSDOS: possono essere paragonati ai file .bat). Per scrivere
uno script della shell si puo' utilizzare un qualsiasi editor di testo come VI, EMACS, JED e via
dicendo. Una volta scritto lo script, per eseguirlo occorre renderlo eseguibile impostando i
permessi di esecuzione (utilizzando il comando chmod) oppure usare il comando sh od il
comando '.'. Entrambi questi comandi eseguono lo script che viene loro passato come
argomento. Ad esempio, supponendo di aver scritto uno script chiamato pippo, impostandone i
permessi di esecuzione con il comando chmod, tale file diventera' simile ad un comune
comando Linux: digitandolo e premendo INVIO verra' eseguito:

chmod u x pippo

pippo (e premere INVIO)

oppure, senza impostare i permessi di esecuzione:

sh pippo

o anche:

. pippo

Uno script della shell puo' contenere dei comuni comandi Linux, delle righe di commento ed
una prima riga di commento un po' speciale. Quando un file di script viene eseguito la shell
legge tale file riga per riga ed esegue tutte le istruzioni che incontra: si tratta percio' di un
programma intrpretato. Tutte le righe che iniziano con il carattere cancelletto '#' vengono
trattate come commento e sono percio' ignorate dalla shell. Le righe di commento sono
particolarmente utili nella scrittura di script complessi per descrivere lo scopo del codice che si
sta scrivendo. La primissima riga di commento pero' ha una funzione ben precisa: indicare il
tipo di shell che potra' eseguire lo script ed eventualmente la locazione del disco o piu'
esattamente il path per richiamare tale shell. Nel caso questa prima riga di commento venga
omessa verra' chiamata la shell di default, cioe' la shell BASH. Infatti se il primo carattere e'
uno spazio si suppone che tale script e' eseguibile dalla shell bash, se tale carattere e' un
cancelletto si suppone che la shell da usare sia la TCSH. Inoltre e' possibile scrivere il carattere
! dopo il carattere # per specificare una shell diversa, ma in questo caso occorre anche
indicare il path per reperire tale shell: #!/bin/sh. Quest'ultima notazione e' indispensabile se si
utilizza una shell e si crea uno script per un'altra shell. Ad esempio se si utilizza la shell TCSH e
si scrive uno script per la shell BASH e' indispensabile iniziare tale script con la stringa:
#!/bin/sh, dove /bin/sh e' il path di default della shell BASH (ovviamente se in un particolare
sistema la shell BASH si trova in una locazione diversa il relativo path da specificare dovra'
essere diverso). Il comando echo visualizza in output una stringa. Vediamo un classico
esempio di programma che utilizza il comando echo:

mioscript e' il nome dello script e contiene al suo interno le seguenti istruzioni:

#!/bin/sh
# questo e' un semplice script
echo "salve gente!"

mioscript (digitando mioscript e premendo INVIO lo eseguo)

salve gente!

Uno script generalmente accetta dei dati in input e genera dei dati in output: il comando read
permette di leggere l'input ed il comando echo permette di scrivere in output. Ad esempio:

lo script mioscript contiene:

#!/bin/sh
echo "digita qualcosa"
read qualcosa
echo "ok hai digitato: $qualcosa"
mioscript (digitando mioscript e premendo INVIO lo eseguo)

digita qualcosa
adlkmasdl
ok hai digitato: adlkmasdl

nello script appena visto l'istruzione read legge lo standard input e lo mette nella variabile
$qualcosa, successivamente il comando echo visualizza il contenuto della variabile. Poiche' la
stringa passata al comando echo e' inserita tra una coppia di doppi apici, eventuali caratteri
speciali all'interno della variabile qualcosa non vengono valutati. Nel caso occorresse valutare
eventuali caratteri speciali occorrera' eliminare i doppi apici. Ad esempio si potrebbe creare un
comando che prendesse in considerazione anche eventuali caratteri speciali digitati dall'utente
per effettuare alcune elaborazioni. Osserviamo il seguente script:

lo script elenco contiene:

#!/bin/sh
echo "quali file vuoi visualizzare?"
read nomi
ls $nomi

elenco *.txt

pippo.txt, pluto.txt , paperino.txt

l'utente digita il nome dello script (elenco) seguito dall'argomento *.txt. L'asterisco e' un
carattere speciale che in questo caso viene preso in input dallo script e valutato in quanto la
variabile $nomi non e' posta tra doppi apici. Percio' il comando ls accetta in input il contenuto
della variabile passata allo script elenco e cioe' '*.txt'. Ad ogni modo uno script puo' leggere
dei dati anche da se stesso:

lo script inviamail contiene:

#!/bin/sh
mail marco <<fine
caro marco
ricordati dell'appuntamento!
ciao
fine

la stringa <<fine significa: prendi in input tutto cio' che trovi da questo punto fino alla parola
'fine'. Se non si specifica un punto di terminazione occorrera' inserire il carattere di fine file
CTRL-D. Ovviamente dopo i caratteri << e' possibile scrivere qualsiasi cosa come ad esempio
<<stop, <<end e via dicendo.

Come qualsiasi comando Linux, anche gli script accettano degli argomenti in input. Per far
riferimento ad un argomento e' possibile usare il simbolo $ seguito dal numero dell'argomento:
$0 e' il nome dello script, $1 e' il primo argomento, $2 e' il secondo, $3 il terzo e cosi' via. $#
rappresenta il numero degli argomenti passati in input e $* rappresenta tutti gli argomenti. Un
esempio chiarira' il concetto:

mio script arg1 arg2 arg3


br> #!/bin/sh
il numero degli argomenti passati e': $#
il primo e': $1
il secondo e': $2
il terzo e': $3
gli argomenti passati sono: $*

Veniamo ora alla prima istruzione utilizzabile all'interno di uno script: l'istruzione let. Let
confronta due valori o permette di eseguire delle operazioni aritmetiche. Attenzione:
let confronta due valori e non due espressioni. Esempi:

let 3*5
let 2 4

oppure

mioscript contiene:

ancora=1
while let "ancora <= 3"
do echo $ancora ciao
let "ancora = ancora 1"
done

eseguo mioscript (premendo INVIO):

1ciao 2ciao 3ciao

come si puo' vedere se gli argomenti del comando let contengono spazi, occorre quotarli
usando i doppi apici. Il comando while appena visto e' una struttura di controllo che vedremo
successivamente insieme alle altre strutture (if, for, for-in e case) in quanto occorre prima
introdurre la struttura di controllo test. Il comando test permette di confrontare due valori. E'
possibile confrontare due valori usando il comando test o inserendo l'espressione di confronto
all'interno di una coppia di parentesi quadre. Per verificare l'uguaglianza tra due valori occorre
usare il simbolo '=' oppure l'opzione -eq del comando test. Viceversa, per verificare la
disuguaglianza tra due valori occore usare il simbolo "!=":

num=2
test $num -eq 3
echo $? ($? contiene il codice di uscita dell'ultimo comando eseguito)

un altro esempio per verificare due valori che non contiene la parola test:

[$num = 3]

la verifica dei valori usando il comando test o le parentesi quadre e' usata all'interno di
strutture di controllo come while, if, for, for-in e case- La sintassi della struttura di controllo if
e' la seguente:

if comando-linux
then
comandi...
else
comandi...
fi

Ogni comando linux quando termina restituisce un codice di errore che puo' essere 0 se il
comando non ha successo o 1 se il comando e' andato a buon fine. Tale codice di errore viene
verificato dalla struttura di confronto if: se e' 1 vengono eseguiti i comandi successivi alla
parola then, in caso contrario vengono eseguiti i comandi successivi alla parola else. Vediamo il
funzionamento della struttura di controllo if con lo script 'argomenti' che accetta degli
argomenti in input e verifica che il numero di questi sia corretto: se viene passato piu' di un
argomento visualizza un messaggio di errore:

if [$# -ne 1]
then
echo "numero di argomenti errato"
exit 1
else
echo "numero di argomenti corretto
fi

eseguendo lo script si avra':

argomenti (premendo INVIO):


numero di argomenti errato
argomenti a (INVIO)
numero di argomenti corretto
argomenti a b c d (INVIO)
numero di argomenti errato

e' possibile 'nidificare' le if, ossia effettuare piu' controlli usando la sintassi 'elif' (elif sta per
'else if'). In altre parole se non si verifica la condizione testata vengono eseguiti i comandi
successivi alla parola else ma a questo punto e' possibile eseguire un altro controllo utilizzando
un altro comando if dopo la parola elif:

if [conddizione1]
then
comandi
elif
if [condizione2]
then
comandi
elif
if [condizione3]
then
comandi
else
comandi
fi

come abbiamo visto il costrutto if permette di verificare l'esito di un comando Linux: e'
possibile pero' verificare l'esito di due o piu' comandi Linux usando gli operandori logici && e
||. L'operatore logico && corrisponde all' and logico mentre l'operatore || corrisponde all' or
logico. Cio' significa che se si vuole verificare che due o piu' comandi siano andati a buon fine
contemporaneamente occorre usare l'operatore &&, mentre se si vuole verificare che almeno
uno dei comandi sia andato a buon fine occorre usare l'operatore ||: comando1 && comando2
oppure comando1 || comando2. Nel primo caso viene eseguito tutto cio' che si trova dopo la
parola then solo se entrambi i comandi hanno esito positivo mentre nel secondo caso e'
sufficiente che almeno uno dei due comandi abbia esito positivo. Quando occorre verificare piu'
condizioni, e' conveniente usare la struttura di controllo case. Vediamo il costrutto case con un
esempio di script che visualizza un menu dove e' possibile scegliere tra le varie opzioni per
visualizzare i file:

echo "scegli una opzione tra le seguenti:


echo "d: dimensioni"
echo "t: tutte le informazioni"
echo "i: i-node"
echo "c: solo i file con estensione .c"c echo "digita la tua scelta:"
read scelta
case $scelta in
d)
ls -s
;;
t)
ls -l
;;
i)
ls -i
;;
c)
ls *.c
;;
*)
echo "input non valido"
esac

cosi' come per il costrutto if anche il costrutto case deve terminare con una parola particolare:
esac. Come si puo' osservare fi e' if letto in senso contrario ed esac e' case letto in senso
contrario. ;o) Vediamo infine i costrutti while, for e for-in. Il costrutto while permette di
eseguire un ciclo di comandi mentre e' verificata una condizione. Quando la condizione non e'
piu' verificata il ciclo si interrompe e lo script prosegue dall' istruzione successiva al blocco del
while. La sintassi dell'istruzione while e' la seguente:

while comando-linux
do
comando1
comando2
comando3
comando4
...
done

Il costrutto for permette di costruire cicli di comandi. Verranno eseguiti tanti cicli quanti sono
gli argomenti passati allo script. Ogni argomento viene spostato all'interno di una variabile che
verra' verificata dal ciclo di for. Vediamo un esempio con lo script 'copiafile' che permette di
salvare i file specificati all'interno di una data directory:

for miofile
do
cp $elencofile /home/mau/backup/$miofile
echo "$miofile salvato!"
done

eseguendo lo script copiafile con i file pippo, pluto e topolino:

copiafile pippo pluto topolino

verranno salvati i tre file nella directory /home/mau/backup:

pippo salvato!
pluto salvato!
topolino salvato!

il costrutto for-in e' molto simile al costrutto for con la sola differenza che l'elenco dei valori
non viene preso dall'elenco degli argomenti passati allo script ma dall'elenco fornito
direttamente all'interno del costrutto for-in:
for variabile in pippo pluto topolino
do
echo $variabile
done

Infine il comando exit permette di uscire immediatamente dallo script fornendo un codice di
errore che puo' essere 0 o qualsiasi altro numero. Non appena viene incontrato il comando
exit, lo script termina ed il controllo torna alla shell.

11.15 Le espressioni regolari


Una espressione regolare e' una stringa che contiene caratteri speciali. I caratteri
speciali delle espressioni regolari pero' sono diversi dai caratteri speciali della shell: i primi
consentono di effettuare ricerche all'interno di un testo mentre gli altri operano sui nomi di file.
Le espressioni regolari possono essere utilizzate da molti filtri come ad esempio grep, sed e
awk. I caratteri speciali sono l'accento circonflesso, il dollaro, l'asterisco, il punto e le parentesi
quadre. Il segno ^ (accento circonflesso) ed il segno $ (dollaro) indicano rispettivamente
l'inizio della riga e la fine della riga. Per ricercare tutte le righe all'interno di un testo che
iniziano con la parola 'pippo' occorre definire una espressione regolare in questo modo:
^pippo. Al contrario per ricercare all'interno di un testo tutte le righe che terminano con al
stringa 'pluto' occorre definire una espressione regolare in quest'altro modo: pluto$. Vediamo
un esempio:

cat testo
questo e' un testo banale
che e' utile solo per mostrare
l'uso delle espressioni regolari
all'interno di un testo

grep '^che' testo


testo: che e' utile solo per mostrare

grep 'lari$' testo


testo: l'uso delle espressioni regolari

il primo comando grep ricerca le righe che iniziano per 'che' mentre il secondo ricerca le righe
che terminano con 'lari. Per ricercare le righe che contengono unicamente una determinata
stringa si possono combinare i caratteri ^ e $ insieme in questo modo: ^stringa$, dove stringa
e' la parola da ricercare. Se infine si vogliono ricercare tutte le righe vuote si puo' costruire una
espressione regolare che contenga unicamente i caratteri ^ e $. Esempio:

cat testo
questo e' un testo banale
che e' utile solo per mostrare
l'uso delle espressioni regolari
all'interno di un testo

grep '^testo$'
testo: questo e' un testo banale

grep '^$' testo


testo:

Le espressioni regolari possono essere usate anche combinando piu' comandi attraverso le
pipe:

ls -l | grep '^d'
i comandi precedenti elencano i file contenuti all'interno della directory corrente e di questi file
estraggono solo quelli che iniziano per 'd', cioe' tutti i file che sono directory. Infatti il comando
ls -l elenca tutti i file in modalita' estesa (-l sta per long). Ogni riga prodotta in output dal
comando ls -l contiene un trattino '-' per i file comuni e una 'd' per le directory: tale output
viene passato tramite pipe al comando grep che ricerca solo le righe che iniziano per 'd'. Il
risultato finale e' l'elenco delle directory contenute all'interno della directory corrente. Un altro
carattere speciale e' il punto: tale simbolo significa qualsiasi carattere. Ad esempio la stringa
'a.a' individua tutte le stringhe che iniziano per a, terminano per a e contengono un qualsiasi
altro carattere. Le stringhe che soddisfano questa espressione regolare possono essere: ala,
ara, a a, ava, aka e via dicendo. E' possibile usare piu' punti, ad esempio l'espressione
regolare: bar.. trova tutte le stringhe che iniziano per bar e contengono 2 altri caratteri
qualsiasi. Le stringhe che soddisfano tale espressione possono essere: barca, barza, barone,
baronetto e via dicendo. Il carattere '*' posto all'interno di una espressione regolare trova
sequenze di 0 o piu' caratteri uguali al carattere che lo precede. Ad esempio bo* permette di
trovare stringhe come boomerang, bossolo, boa e cosi' via. L'ultima serie di caratteri speciali
usabili nelle espressioni regolari sono le parentesi quadre. Le parentesi quadre permettono di
specificare un particolare carattere o una particolare sequenza di caratteri. Vediamo un
esempio:

gruppo[145]

l'espressione regolare appena vista individua tutte le stringhe che iniziano per gruppo e
terminano con 1, 4 o 5. Le stringhe gruppo1, gruppo2 e gruppo4 soddisfano i criteri ma la
stringa gruppo9 o la stringa gruppo6 non soddisfano i criteri e pertanto non verranno
individuate. La stringa gruppo2 soddisfa i criteri ma la stringa Gruppo2 non li soddisfa. Infatti
'gruppo2' e 'Gruppo2' sono due stringhe diverse. E' possibile pero' individuare entrambe le
stringhe in questo modo:

[gG]gruppo[2]

E' possibile effettuare delle ricerche procedendo in senso inverso, cioe' individuando non tanto
le stringhe da trovare ma bensi' le stringhe che non devono essere trovate. Per effettuare cio'
e' possibile usare il simbolo '^' per escludere alcuni caratteri dalla ricerca. L'accento
circonflesso si deve trovare tra la prima parentesi quadra ed il primo carattere all'interno delle
parentesi quadre:

gruppo[^3456789]

l'espressione regolare esposta individua le stringhe gruppo1 e gruppo2 ma non le stringhe


gruppo3, gruppo4 o gruppo9, in quanto terminano con un carattere che non deve essere
presente: tutte le stringhe che terminano con 3,4,5,6,7,8 o 9 non verranno individuate. Se
viene posto un trattino all'interno delle parentesi quadre viene definita una sequenza di
caratteri. Ad esempio l'espressione regolare [A-Z] individua tutti i caratteri dell'alfabeto
maiuscoli mentre [a-z] individua quelli minuscoli. Allo stesso modo l'espressione regolare [0-9]
individua tutti i numeri da 0 a 9. Per includere anche il trattino nella ricerca, occorre usare il
carattere '\' come carattere di quotazione: [0-9\-]. Tale espressione individua le stringhe che
terminano con un numero o con un trattino. E' possibile combinare tutti questi caratteri
speciali insieme per costruire delle stringhe di ricerca particolarmente efficaci. Ad esempio la
stringa [0-9][0-9]* significa qualsiasi stringa che inizia con un numero e che termina con uno
o piu' numeri: in sostanza vengono individuate solo stringhe numeriche. Allo stesso modo la
stringa [A-Z][A-Z]* individua qualsiasi stringa composta da caratteri maiuscoli. Esempio:

I PC IBM sono stati i primi personal computer della storia e sono nati nel 1981

all'interno della riga esposta, l'espressione regolare [A-Z][A-Z]* individua le stringhe PC ed


IBM mentre l'espressione regolare [0-9][0-9]* individua la stringa 1981. Le espressioni
regolari possono essere particolarmente efficaci:
ls -l | grep '^......r.x'

i comandi esposti permettono di individuare tutti i file per i quali tutti gli altri utenti
(proprietario e gruppo esclusi) posseggano i permessi di lettura ed esecuzione. Ecco un
riepilogo dei caratteri speciali utilizzabili all'interno di espressioni regolari:

^ inizio riga
$ fine riga
. qualsiasi carattere
* qualsiasi sequenza dei caratteri
[] classi di caratteri

Infine esistono alcuni comandi che accettano anche altri caratteri come ad esempio il comando
egrep. Il comando egrep infatti accetta anche i simboli: |, (), e ?. Il simbolo | rappresenta l'
OR logico, cioe' permette di indicare delle stringhe alternative. Ad esempio 'frutta|verdura'
individua sia la parola frutta che la parola verdura. Le parentesi permettono di concatenare piu'
stringhe: (latte(intero|scremato). Il carattere ricerca una o piu' ripetizioni del carattere
precedente, ad esempio o individua una o piu' o come ad esempio zoo, boomerang e via
dicendo. Il carattere ? infine, individua 0 o 1 istanze del carattere precedente.

11.16 I filtri
I filtri sono comandi che leggono dei dati, li elaborano ed inviano in output il risultato. I dati
possono provenire da file, dispositivi o possono essere l'output di altri comandi. L'output di un
filtro e' generalmente l'output standard (il video) ma puo' essere rediretto verso un file, un
dispositivo od un altro comando che puo' essere a sua volta un filtro. Un esempio di filtro e' il
comando cat (da conCATenate). Il comando cat accetta in input dei dati e li visualizza a video.
Se non viene specificato l'input, il comando cat accetta i dati dall'input standard (la tastiera).
Percio' il comando cat > lettera si mette in attesa di dati dalla tastiera e al termine del flusso li
scrive all'interno del file lettera:

cat > lettera


caro amico ti scrivo
cosi' mi distraggo un po'
e siccome sei molto lontano
piu' forte ti scrivero'
CTRL-D

Per terminare l'input da tastiera occorre premere la combinazione dei tasti CTRL e D che
invia il carattere di fine file. I filtri head (testa) e tail (coda) servono per visualizzare le
prime righe di un testo (head) o le ultime righe (tail). Ad esempio il comando head -2
visualizza le prime due righe di un testo:

cat lettera
caro amico ti scrivo
cosi' mi distraggo un po'
e siccome sei molto lontano
piu' forte ti scrivero'
head -2 lettera
caro amico ti scrivo
cosi' mi distraggo un po'

mentre il comando tail -2 lettera visualizza le ultime due:

cat lettera
caro amico ti scrivo
cosi' mi distraggo un po'
e siccome sei molto lontano
piu' forte ti scrivero'
tail -2 lettera
e siccome sei molto lontano
piu' forte ti scrivero'

Altri esempi di filtri sono i comandi wc (Word Count) spell e sort. Il comando wc conta il
numero di righe, parole e caratteri (inclusi i caratteri di fine riga) presenti all'interno di un file.
Il comando spell legge un file e produce in output tutte le parole errate che trova (utilizzando il
dizionario inglese). Il comando sort legge un file e lo riproduce in output in modo ordinato. Un
altro esempio di filtri sono i comandi grep ed fgrep. Tali comandi ricercano una stringa
all'interno di un file. Ad esempio, se all'interno di una directory con molti file esiste un file al
cui interno e' presente la stringa 'pippo' ma non ricordiamo piu' il nome di tale file, e' possibile
individurarlo utilizzando il comando grep. Il comando grep accetta due argomenti, il primo e' la
stringa da ricercare, il secondo e' un elenco di file. Per specificare l'elenco di file e' anche
possibile usare i caratteri speciali come '*'. Esempio:

cat lettera
caro amico ti scrivo
cosi' mi distraggo un po'
e siccome sei molto lontano
piu' forte ti scrivero'
cat documento
egregio dottore
a seguito della sua richiesta di informazioni
pervenutaci in data 12-12-2001
relative al nostro prodotto
grep forte lettera documento
lettera:piu' forte ti scrivero'

se la stringa da ricercare contiene degli spazi occorre scriverla tra apici altrimenti il comando
grep interpretera' lo spazio come un delimitatore considerando gli altri argomenti successivi al
primo come nomi di file all'interno dei quali effettuare la ricerca. Esempio:

cat lettera
caro amico ti scrivo
cosi' mi distraggo un po'
e siccome sei molto lontano
piu' forte ti scrivero'
grep 'caro amico' lettera documento
lettera: caro amico ti scrivo

qualora non fossero stati digitati gli apici il comando grep avrebbe ricercato la stringa 'caro'
all'interno dei file: amico, lettera e documento. Occorre ricordarsi che Linux e' case sensitive,
cioe' fa differenza tra caratteri maiuscoli e caratteri minuscoli:

cat lettera
Caro amico ti scrivo
cosi' mi distraggo un po'
e siccome sei molto lontano
piu' forte ti scrivero'
grep 'caro amico' lettera documento

il comando grep in questo caso ricerca la stringa 'caro amico' all'interno dei file lettera e
documento ma non produce alcun risultato in quanto 'Caro amico' e 'caro amico' sono due
stringhe diverse. Per effettuare una ricerca senza badare ai caratteri maiuscoli o minuscoli
occorre usare l'opzione -i: grep -i 'caro amico' lettera documento. Il comando grep puo' anche
funzionare al contrario, e' possibile cioe' ricercare tutte le righe che non contengono una data
stringa. A tale scopo occorre usare l'opzione -v. Il filtro fgrep e' piu' veloce di grep e puo'
cercare contemporaneamente piu' stringhe nei file. Tale comando a differenza di grep non
accetta pero' le espressioni regolari. Altri filtri utili sono i comandi diff e sed. Il comando diff
permette di confrontare due file per visualizzare le righe differenti. Il comando diff produce in
output le righe che risultano differenti, evidenziando quelle del primo file con il carattere '<' e
quelle del secondo file con il carattere '>'. Il comando diff inoltre indica le operazioni che
occorre effettuare per rendere i due file uguali. Sono possibili 3 operazioni: a, d o c.
L'operazione a significa aggiungere, l'operazione d significa cancellare e l'operazione c significa
sostituire. Un esempio chiarira' il funzionamento di diff:

cat frutta
pere
mele
banane
cat fruttaverdura
pere
mele
carote
pomodori
cavoli
diff frutta fruttaverdura
3c35
< banane
> carote
> pomodori
> cavoli

cioe': sostituire la terza riga del primo file con le righe dalla terza alla quinta del secondo file.
La riga del primo file da sostiture e' < banane le righe del secondo file sono > carote, >
pomodori e > cavoli. Per ignorare le differenze tra caratteri maiuscoli e caratteri minuscoli e'
possibile usare l'opzione -i. Ovviamente e' possibile reindirizzare l'output del comando diff
all'interno di un file: diff frutta fruttaeverdura > differenze. Esistono molti altri filtri come join,
cut, paste, egrep, comm, cmp, tee, sort, pr, sed...tra questi un filtro molto usato e' il comando
sed. Sed sta per Stream EDitor, cioe' editor di flusso: si tratta in sostanza di un editor di riga.
Il comando sed accetta in input un comando di editing ed un elenco di file e genera in output
una copia modificata di tali file. Esempio:

cat frutta
pere
mele
banane
sed '3 d' frutta
pere
mele

nell'esempio appena visto il comando sed cancella la terza riga dal file frutta. Il comando sed
puo' effettuare varie operazioni di editing (inserimento, cancellazione, sostituzione etc.): per
conoscere l'elenco completo delle operazioni di editing effettuabili consultare la relativa pagina
di man (man sed).

Inizio della guida Il filesystem Indice tipologie di comandi

Copyright (c) 2002-2003 Maurizio Silvestri