Questione di shell

di Eugenia Franzoni

Tante interfacce per colloquiare col pc: QUESTIONE DI SHELL


Per utilizzare al meglio Linux bisogna saper scegliere l'interfaccia giusta. Scoprite con noi le shell di sistema.


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.


In collaborazione con:

AUTORE DEL TESTO
Eugenia Franzoni

Pubblicato il: 07/08/2002