|
In un sistema Unix la prima cosa che normalmente ci si
presenta davanti quando accediamo al nostro account, dopo
aver inserito il nome di login e la password, è uno
schermo nero con a sinistra un prompt in attesa di nostri
comandi. Questo è il momento in cui il sistema è
pronto a "dialogare" con noi, a ricevere e inviare
informazioni, il tutto mediato da un programma apposito
che funge da interfaccia fra l'utente e il cuore del nostro
computer, il kernel. Questa è in poche parole la
shell, una presenza imprescindibile per chiunque utilizzi
Linux in qualunque modo fruisca di questo sistema operativo
sia che vi acceda in modalità interattiva, come ad
esempio in login o tramite xterm, che in maniera non interattiva,
come durante l'esecuzione di uno script, l'omologo del batch
di MS-DOS. Gli script, in particolare, richiamano dalla
shell (interattiva) in cui vengono lanciati un altra sessione
non interattiva nella quale vengono eseguiti i comandi che
vi sono stati immessi.
E' bene evitare di generalizzare pensando che vi sia un
unico modo attraverso il quale il sistema possa dialogare
con l'utente. Esistono diverse shell che, per esigenze di
chiarezza, possiamo suddividere in tre categorie: le Bourne
shell, le C shell e le Korn shell.
Linux riprende questa suddivisione dai sistemi Unix suoi
predecessori, e addirittura aumenta il numero, di interfacce
a disposizione presentandone delle evoluzioni che sia l'interazione
con l'utente che un miglioramento delle possibilità
di scripting. In tema di aggiornamenti, gli utenti di Linux
hanno a disposizione, come in tutti i sistemi GNU, la classica
bash (bash Bourne Again Shell, uno dei soliti giochi di
parole tipici dei programmatori) una versione modificata
della Bourne che incorpora alcune delle caratteristiche
più utili di C e Korn shell.
Ovviamente qui parliamo solo delle interfacce più
conosciute e menzioneremo solo di sfuggita altre shell quali
la tcsh, la Tenex C Shell e la zsh che sebbene meno conosciute
hanno a loro vantaggio una buona versatilità e la
possibilità di creare script evoluti grazie al loro
linguaggio di programmazione interno.
Continuando questa veloce carrellata, infine, ricordiamo
che esiste una serie di shell di ridotte dimensioni, come
ad esempio ash o kiss, che proprio per la loro minima occupazione
di risorse, e la loro semplicità, vengono utilizzate
in casi particolari come per i dischetti di installazione
delle varie distribuzioni di Linux. Una menzione a parte
meritano le interfacce "speciali", ovvero quelle
shell come lsh che forniscono comandi compatibili con quelli
del DOS, utile per i principianti, oppure tclsh che è
un vero e proprio interprete tcl.
La lettura della linea di comando
Quando viene attivata, una shell legge rimane in attesa
dei comandi inseriti dall'utente sotto forma di blocchi,
separati da spazi o tabulazioni, terminati da un carattere
di newline, corrispondente alla pressione del tasto "Invio".
Il primo blocco di caratteri viene interpretato come un
comando interno alla shell o a un eseguibile presente su
disco: nel primo caso il comando viene lanciato all'interno
shell stessa, altrimenti viene creato un nuovo processo
che esegue il programma, attende che porti a compimento
la propria azione, ne presenta il suo stato di uscita (se
necessario) e infine restituisce il prompt all'utente.
Una particolarità delle shell Unix, rispetto all'interprete
di comando stile DOS, consiste nell'essere case sensitive,
cioè le lettere maiuscole e quelle minuscole sono
trattate in maniera diversa: CAT, cat e Cat sono tre comandi
diversi; in genere non esistono istruzioni con nomi simili
e compiti diversi, ma va fatta attenzione a come vengono
scritte.
I processi che entrano a fare parte della gestione della
linea di comando sono i seguenti:
1) Sostituzione dello storico dei comandi (history) - se
applicabile
2) Scomposizione della linea di comando in blocchi
3) Aggiornamento dello storico - se applicabile
4) Gestione del quoting
5) Sostituzione degli alias e delle funzioni - se applicabile
6) Redirezione, pipe e posizionamento del programma in background
7) Sostituzione delle variabili ($name, $user eccetera)
8) Sostituzione dei comandi
9) Sostituzione dei metacaratteri per l'espansione dei nomi
dei file
10) Esecuzione del programma
Prendiamo ora in esame più da vicino ogni singolo
aspetto di questa sequenza per comprenderne meglio le singole
funzioni:
Lo storico (history)
Per storico si intende la possibilità di richiamare
i comandi che sono stati memorizzati dall'interfaccia, senza
dovere riscriverli tutte le volte. Questa comoda opzione
consente di introdurre anche all'interno di linee di comando
successive tutto ciò che è stato inviato precedentemente
alla shell e che questa ha provveduto a memorizzare in sequenza.
Il quoting
Tutte le shell hanno un set di caratteri che assumono dei
significati speciali e consentono di effettuare alcune operazioni
che influenzano il flusso di input e output. Abbiamo i metacaratteri
che consentono l'espansione dei nomi dei file (* ?), i caratteri
per la redirezione e le pipe (<, > e |), gli spazi,
le virgolette e gli apici, l'ampersand (&), il punto
e virgola, il dollaro ($); questi sono considerati speciali,
riservati, che non possono essere inseriti all'interno di
una linea di comando senza le opportune precauzioni.
Per evitare che questi caratteri vengano interpretati è
opportuno farli precedere da un backslash (\), che è
un carattere speciale e quindi va preceduto da se stesso
se si vuole che appaia nella riga di comando senza che venga
a sua volta interpretato (\\) .
Un altro modo per evitare l'interpretazione dei caratteri
consiste nel racchiuderli all'interno delle virgolette (")
o degli apici (' - attenzione, non gli apici inversi); il
modo in cui essi operano è leggermente differente
a seconda della shell utilizzata, ma indicativamente il
loro utilizzo è questo:
* le virgolette (") proteggono i caratteri speciali
in esse inclusi dall'interpretazione consentendo però
la sostituzione delle variabili
* gli apici (') hanno la stessa funzione delle virgolette
ma non consentono la sostituzione delle variabili.
Gli alias e le funzioni
Gli alias possono essere definiti come delle utili abbreviazioni
con le quali è possibile ridefinire comandi con molte
opzioni, difficili da ricordare o lunghi da scrivere. In
genere gli alias sono definiti nei file di inizializzazione
della shell e vanno utilizzate, all'incirca con la seguente
sintassi:
bash
alias nome=valore
tcsh
alias nome valore
zsh
alias nome=valore
Se il valore dell'alias è formato da più
parole, queste vanno messe tra apici; ad esempio, in bash
alias ls='ls --color -F'
consente di visualizzare i diversi tipi di file evidenziandoli
con differenti colori.
Se gli alias risultano utili per ridefinire comandi un
discretamente complessi, maggiore flessibilità viene
offerta dalle funzioni, ovvero insiemi di comandi raggruppati
sotto una singola "etichetta" che consentono di
aumentare la modularità degli script. Ovviamente,
come in generale per tutti i casi riguardanti la definizione
del comportamento di una shell, la sintassi di una funzione
varia leggermente dal tipo di interfaccia usato. Diamo un'occhiata
a come vengono definite in Bash e Zsh:
Bash
[ function ] nomefunzione () { listacomandi; }
Zsh
function nomefunzione [()] [term] {listacomandi}
word () [term] {listacomandi}
word () [term] comando
dove term è uno o più caratteri di newline
o un punto e virgola.
Redirezione, pipe e posizionamento in background dei processi
Gli utenti di sistemi operativi Unix devono avere ben presente
i concetti di standard imput e standard output: quando un
programma scrive qualcosa sullo schermo, sta usando lo standard
output, o stdout; mentre se voi scrivete qualcosa con la
tastiera, state usando lo standard input, o stdin.
Normalmente, un comando di sistema, come ad esempio "ls"
accetta delle informazioni provenienti dallo standard input
e restituisce i risultati del suo operato tramite stdout,
ovvero il monitor; in più, alcuni programmi possono
utilizzare un ulteriore dispositivo di output chiamato standard
error, o sderr, in genere collegato allo schermo, che consente
di comunicare gli eventuali errori avvenuti nel corso della
loro esecuzione.
Sia stdin che stdout possono essere facilmente manipolati,
in modo da concatenare più comandi insieme, da redirigere
l'output in un file, o da acquisire l'input da un file.
Scrivere su un file l'output di un comando, che normalmente
andrebbe sullo schermo, è abbastanza semplice e molto
comodo per consultare le informazioni che esso mette a disposizione,
o condividerle con altre persone. Il carattere che consente
di redirigere il flusso delle informazioni è >.
Ad esempio
ls /etc > file.txt
crea il file file.txt che visualizza l'elenco dei file
presenti nella directory /etc.
Come abbiamo già detto, non solo è possibile
salvare su disco le informazioni che andrebbero normalmente
a schermo, ma è anche fattibile l'operazione inversa,
leggendo da file gli argomenti che normalmente andrebbero
scritti sulla linea di comando. In questo caso il carattere
da utilizzare è <:
In questo caso cat ci consente di visualizzare sullo schermo
il contenuto del file /etc/passwd
Non tutti i comandi accettano però l'input dallo
stdin: per scoprire è possibile utilizzare questo
metodo è bene consultare la pagina di man relativa,
dato che in essa è sempre presente questo tipo di
informazione.
Complicando ancora un po' le cose, vediamo come è
possibile spedire l'output di un comando come input di un
altro, utilizzando le pipe. In questo caso il carattere
che ci consente questa operazione è |:
ls /usr/bin | more
Intercalando i due programmi (ls e more) con il carattere
di pipe indirizziamo il flusso di informazioni proveniente
dal primo (ls) verso il secondo, ottenendo come risultato
una migliore visualizzazione del contenuto della directory
/usr/bin dato che la lista ottenuta non scorrerà
senza controllo sullo schermo ma verrà fermata quando
lo avrà riempito e ripartirà con la pressione
di un tasto.
Lo svantaggio della pipe consiste nel fatto che se viene
specificato un file già esistente, questo viene sovrascritto,
perdendo le informazioni che vi erano state immesse in precedenza.
Per avere una redirezione "incrementale" è
meglio utilizzare la modalità append che consente
di scrivere l'output di un comando in un file, aggiungendolo
alla fine dello stesso:
ls /usr/bin >> lista
o, addirittura, è possibile redirigere l'input fino
ad una determinata parola, come nell'esempio seguente:
piccinino:~$cat << EOF | mail eugenia
>Questa è una mail sperimentale
>C'è anche una seconda linea
>EOF
In questo caso possiamo inviare via mail più di
una riga senza uscire dalla linea di comando. La shell manda
al comando cat tutte le linee inserite finché non
trova una riga in cui sia contenuta solo la parola EOF;
l'output del comando cat viene poi mandato in pipe a mail.
Il tutto con poche righe in bash!
La gestione dei processi (job control)
Una shell Unix è in grado di gestire più
processi contemporaneamente consentendo, fra l'altro, di
manipolare la loro esecuzione fornendo delle priorità
di interazione con l'utente. Un programma può, in
poche parole, essere posto in background, sullo sfondo,
o in foreground, ovvero in primo piano: basti pensare ai
processi come a degli strati che si sovrappongono, di cui
solo ciò che sta più in alto (in primo piano,
appunto) è visibile. Quando si dà un comando,
il programma corrispondente viene eseguito in primo piano,
e ci preclude l'uso della shell. Il comando in esecuzione
può essere però sospeso (con un segnale di
stop, di solito Crtl-Z) e mandato in background con bg.
Si può lavorare con la shell, e riportare in primo
piano il processo con fg. Il comando jobs permette di elencare
i vari processi attivi sullo sfondo. Per indicare il processo
a cui ci si riferisce, si antepone un % al numero del job
(che si vede utilizzando il comando jobs). Ad esempio, per
mandare in background il primo processo della shell si usa
il comando
bg %1
Per avviare un processo direttamente in background si può
fare seguire il comando dal segno "&": il
processo viene mandato direttamente in background, e la
shell ripresenta subito il prompt. Attenzione: nella zsh
il controllo dei job può essere disattivato. In questo
caso un comando seguito da & ha l'output rediretto direttamente
in /dev/null, e quindi viene perso.
Le variabili
Le shell possono definire due tipi di variabili: variabili
locali e variabili d'ambiente; contengono informazioni utili
per personalizzare la shell, ed informazioni richieste da
altri programmi per funzionare correttamente. Le variabili
locali sono proprie della shell in cui sono state definite,
e non vengono usate da nessun processo creato da quella
shell; le variabili d'ambiente, invece, vengono passate
dai processi ai loro figli. Alcune variabili d'ambiente
sono ereditate dal processo che crea la shell, in genere
login, ed altre vengono definite nei file di inizializzazione
della shell stessa, o dall'utente direttamente sulla linea
di comando. In genere si usa la convenzione che le variabili
d'ambiente sono indicate in lettere maiuscole.
Una variabile d'ambiente molto importante è la variabile
PATH, che indica le directory in cui vengono ricercati i
file da eseguire senza bisogno di indicarne il percorso
completo.
Per indicare il valore di una variabile si utilizza il
segno $: PATH indica la variabile, mentre $PATH è
il suo valore; per assegnare alla variabile PATH il suo
vecchio valore più la directory ~/bin si dovrà
dunque scrivere
PATH=$PATH:~/bin
Normalmente le variabili sono considerate locali; per renderle
d'ambiente vanno "esportate", cioè va fatto
in modo che siano valide anche per i processi figli del
processo della shell.
In bash ed in zsh la procedura da seguire è la seguente:
* si definisce la variabile
PATH=$PATH:~/bin
* la si esporta
export PATH
oppure, in una sola linea:
export PATH=$PATH:~bin
Nella tcsh si usa invece il comando set per le variabili
normali, e setenv per quelle di ambiente. Ad esempio:
setenv PAGER less
imposta il pager di default a less (al posto di more, il
pager predefinito: less permette anche di tornare indietro
utilizzando le frecce).
Sostituzione dei comandi
Si ha sostituzione dei comandi quando si assegna ad una
variabile l'output di un comando, o quando si sostituisce
l'output di un comando ad una stringa contenente il comando
stesso. Per permettere la sostituzione dei comandi le shell
usano gli apici inversi (`).
Ad esempio:
locate `which bash`
è equivalente a
locate /bin/bash
dato che
which bash
rende
/bin/bash
Un altro metodo per sostituire i comandi usato da alcune
shell è $(comando) - questo metodo permette di inserire
una sostituzione di comando dentro l'altra in modo più
semplice e con minori rischi di errore. Questo tipo di sostituzione
è valida in bash e zsh, ma non in tcsh.
I metacaratteri per l'espansione dei nomi dei file
Spesso capita di voler cancellare un'intera serie di file
i cui nomi contengono una sequenza tipica, di volerli copiare,
oppure di volerci compiere una qualsiasi operazione. Specificare
su linea di comando ogni nome può diventare lungo
e noioso, specialmente se sono coinvolti parecchi file;
meglio allora utilizzare dei caratteri speciali per l'espansione
dei nomi dei file, o "caratteri jolly", che consentono
di indicare contemporaneamente su una stessa linea i nomi
di più file.
I meta caratteri usati da tutte le shell esaminate sono:
* espande zero o più caratteri
? espande un solo carattere
[abc] espande un carattere dell'insieme (a, b o c)
[a-z] espande un carattere nell'intervallo a-z
^ nega la corrispondenza successiva (es. [^abc])
~ espande la home directory dell'utente
Anche in questo caso anteporre un \ al metacarattere ne
disabilita la funzione. Altre shell utilizzano anzhe dei
metacaratteri aggiuntivi: la zsh, ad esempio, consente di
indicare un intervallo di interi con o di selezionare solo
i file di un certo tipo (si usa (/) per indicare le directory,
(@) per i link e così via). Dato che differenti shell
possono avere diversi caratteri speciali, è bene
consultare i documenti relativiall'interfaccia utilizzata
per avere informazioni più dettagliate che qui non
possiamo approfondire per ragioni di spazio.
Maggiore interesse forse riveste la possibilità offerta
dalle shell esaminate di completare automaticamente i nomi
dei comandi ed i nomi dei file. Se si hanno dei nomi molto
lunghi, è possibile inserirne solo una parte significativa
e premere "tab" per vedere magicamente completarsi
sotto i nostri occhi tutta la scritta; se invece viene fornita
una parte non significativa, premendo due volte "tab",
in bash, verrà fornito un elenco di file o comandi
che iniziano con le lettere da noi inserite. Oltre alla
possibilità di completare i nomi dei file e dei comandi,
le shell permettono anche di modificare la linea di comando
stessa usando le combinazioni di tasti proprie degli editor.
Le associazioni dei tasti sono in genere quelle che un utente
di emacs o di vi si aspetterebbe, ad esempio in bash per
cancellare il carattere sul cursore si usa Ctrl-D, per andare
in fondo alla linea si usa Ctrl-E e così via, se
è impostata la modalità emacs, che è
quella di default.
Alcune shell permettono anche di correggere gli errori di
battitura, ovvero caratteri invertiti, mancanti o in eccesso,
automaticamente o dando un comando: digitando in maniera
non corretta il nome di una directory è possibile
vedere corretto il nostro errore e passare comunque alla
directory desiderata.
Per rendere meno pesane il lavoro su Linux è bene
imparare la lista dei comandi interni della shell da utilizzare,
comandi che possono essere usati sia interattivamente che
all'interno degli script. Ci sono, come è noto a
tutti, comandi abbastanza semplici come cd, ma accanto a
questi vi sono strumenti più complessi come quelli
relativi alla gestione dei loop, che possono semplificare
notevolmente la vita dell'utente medio. Pensiamo, ad esempio,
alla situazione tipica in cui bisogna decomprimere un certo
numero di archivi tar.gz e in seguito cancellarli. Usando
una semplice linea di comando di bash, ad esempio, è
possibile automatizzare il tutto:
for i in `ls *.tar.gz`; do tar xzf $i ; rm $i ; done
Questa riga può essere letta in questo modo: a ciascun
elemento dell'output del comando ls applicare il comando
tar, poi cancellare il file. Da notare che utilizzando i
punto e virgola: è possibile concatenare più
comandi sulla stessa linea.
I file di configurazione della shell
I file di configurazione sono uno degli aspetti più
importanti della shell: qui vanno definite le variabili
d'ambiente, gli alias e le funzioni che influenzeranno il
modo in cui l'utente si interfaccia con il sistema operativo.
Non solo, qui possono essere indicati inoltre i processi
da eseguire all'avvio della stessa, siano essi programmi
o comandi interni. Normalmente i file di configurazione
sono almeno tre: uno, valido globalmente, risiede nella
directory /etc, mentre gli altri due, uno per le shell di
login e uno per quelle interattive, si trovano nelle home
directory degli utenti e sono liberamente modificabili.
Qui di seguito riportiamo i nomi dei file di inizializzazione
delle varie shell, con il loro uso.
Bash
/etc/profile contiene i comandi da eseguire all'avvio di
una shell
di login, ed è comune a tutti gli utenti
~/.bash_profile contiene i comandi da eseguire all'avvio
di una shell
di login per ciascun utente.
~/.bash.login e
~/.profile usati se ~/.bash_profile non esiste, per compatibilità
~/.bashrc contiene i comandi da eseguire all'avvio di una
qualsiasi shell interattiva non di login
~/.bash_logout contiene i comandi da eseguire al momento
del logout.
All'avvio di una shell non interattiva bash legge la variabile
ENV, la espande e legge il file che indica come .$ENV. Se
bash è richiamata come sh, cerca di imitare il comportamento
di sh il più fedelmente possibile, e quindi legge
solo i file /etc/profile e ~/.profile per le shell di login,
e nessun'altro.
Tcsh
/etc/csh.cshrc file di sistema per shell interattive
/etc/csh.login file di sistema per shell di login
~/.tcshrc file per le shell interattive
~/.cshrc usato in sostituzione di ~/.tcshrc se questo non
esiste
~/.login file per le shell di login
/etc/csh.logout file di sistema usato al logout
~/.logout file personalizzabile dall'utente usato al logout
Zsh
/etc/zshenv file di sistema per tutte le invocazioni della
shell
$ZDOTDIR/.zshenv per tutte le invocazioni della shell
$ZDOTDIR/.zprofile simile a zlogin ma usato prima di .zshrc
$ZDOTDIR/.zshrc per le shell interattive
$ZDOTDIR/.zlogin per le shell di login
$ZDOTDIR/.zlogout al logout
Se la variabile $ZDOTDIR non viene indicata specificamente,
il suo valore è pari a $HOME, cioè è
la home directory dell'utente.
Crearsi l'ambiente di lavoro.
Se non siete soddisfatti della shell di sistema potete
sempre scegliere quello che fa per voi ed indicare a Linux
la vostra scelta modificando il file /etc/passwd. Dando
un'occhiata a questo file si può notare che alcuni
utenti di sistema hanno come shell un programma particolare:
ad esempio sync ha la shell /bin/sync e quindi al suo collegamento
viene eseguito il programma sync.
La stessa cosa si potrebbe fare per rendere disponibili
ad utenti non di root alcuni servizi senza che questi implichino
particolari privilegi. Ad esempio, creando un utente shutdown
con una shell
/sbin/shutdown si consente a chiunque di effettuare una
procedura di arresto di sistema semplicemente collegandosi
con lo username e la password adeguati. Gli esempi potrebbero
continuare citando, per esempio, la possibilità di
creare utenti senza diritto di login, sostituendo ad una
shell reale una /bin/false o una /bin/passwd, nel caso si
volesse lasciare la possibilità di cambiare la password.
Ciò che conta, e che spesso sfugge all'utente alle
prime armi, è che non sempre una shell è quell'ambiente
ostico che appare alla prima installazione. Conoscere la
shell che si sta utilizzando è il primo passo verso
la crazione di un'interfaccia realmente amichevole, user
friendly non perchè presenti icone e finestre ma
perchè realmente consente di lavorare meglio e più
efficacemente.
|