[precedente] Inferno - Copertina - Corso Linux [successivo]

Articolo


Ambientarsi in Linux, 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; essa può essere usata sia in modalità interattiva, come nei login su console testuale o negli xterm, sia in maniera non interattiva, eseguendo gli script, l'equivalente, anche se molto più evoluto, dei file 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 diversi tipi di shell, e storicamente si distinguono 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 sia per quanto riguarda l'interazione con l'utente che le possibilità di scripting. Gli utenti di Linux di default hanno a disposizione, come in tutti i sistemi GNU, la 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.

In questo articolo esamineremo, oltre la bash, altre due interfacce di tipo avanzato: la tcsh (Tenex C Shell), ovviamente della categoria delle C shell, e la zsh, di tipo Korn, 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.

Oltre ad esse, Linux presenta una serie molto ricca di altre shell: ne abbiamo di dimensioni ridotte, 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; ci sono poi interfacce speciali, come ad esempio lsh che fornisce comandi compatibili con quelli del DOS, utile per i principianti, oppure tclsh che è un vero e proprio interprete tcl. Insomma, vi è quasi una soluzione per ogni esigenza.

La lettura della linea di comando

Quando viene attivata interattivamente, la shell legge i comandi inseriti dall'utente sotto forma di blocchi, separati da spazi o tabulazioni, e termina quando incontra un carattere di newline, corrispondente alla pressione del tasto "Invio". Il primo blocco di caratteri viene paragonato ai comandi interni alla shell e agli eseguibili presenti 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 meta caratteri 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 l'interpretazione della linea di comando: i meta caratteri che consentono l'espansione dei nomi dei file (* ?), i caratteri per la reindirizzo e le pipe (<, > e |), gli spazi, le virgolette e gli apici, l'ampersand (&), il punto e virgola, il dollaro ($); questi ed altri sono considerati speciali, riservati, e non possono essere inseriti all'interno di una linea di comando senza le opportune precauzioni.

Per evitare che questi caratteri vengano interpretati è necessario farli precedere da un backslash (\), che è anch'esso un carattere speciale e quindi va raddoppiato, se lo si vuole inserire in una linea 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:

Gli alias e le funzioni

Gli alias sono 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 definiti 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 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 per la definizione di una funzione varia leggermente a seconda della shell usata: ad esempio ecco 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 input e standard output: quando un programma scrive qualcosa sullo schermo, sta usando lo standard output, o stdout; mentre se voi date al programma dei comandi da 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 in genere il monitor; in più, i programmi possono utilizzare un ulteriore dispositivo chiamato standard error, o stderr, 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 lo stdout su un file è >. 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 è <:
cat < /etc/passwd
In questo caso cat ci consente di visualizzare sullo schermo il contenuto del file /etc/passwd: in questo modo redirigiamo lo stdin del programma su file.

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 redirigere 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 inviamo il flusso di informazioni proveniente dal primo (lo stdout di ls) in input al secondo (more), ottenendo in questo caso 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.

Quando si utilizza la redirezione dell'output va fatta attenzione, perché se viene specificato un file già esistente, questo viene sovrascritto, perdendo le informazioni che vi erano state immesse in precedenza. Per avere una scrittura "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 prendere più linee di input da linea di comando, 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; in questo modo è possibile continuare a lavorare, riportando in seguito in primo piano il programma con fg.

Per avviare un processo direttamente in background si può fare seguire il comando dal segno "&": in questo modo l'elaborazione passa direttamente in background lasciando all'utente il controllo della shell. Attenzione, pero': nella zsh il controllo dei job può essere disattivato.

In questo caso un comando seguito da & ha l'output inviato direttamente in /dev/null, e quindi viene perso.

Si può avere un elenco dei processi attivi tramite il comando jobs; per indicare il processo cui ci riferisce bisogna anteporre un % al numero di job (che si vede utilizzando il comando jobs).

Quindi, per fare un esempio, volendo mandare in background il primo processo della shell dobbiamo usare il comando

bg %1

Le variabili

Spesso è possibile influenzare il comportamento di alcuni programmi e della shell stessa configurando opportunamente alcune variabili, definite locali o d'ambiente a seconda del loro utilizzo. Brevemente, le variabili locali sono proprie della shell in cui sono state definite, e non vengono usate da nessun processo da essa creato, mentre le variabili d'ambiente vengono passate dai processi stessi ai loro figli. Alcune variabili d'ambiente, a loro volta, sono ereditate dal processo che crea la shell, in genere login, mentre altre vengono definite nei suoi file di inizializzazione o dall'utente, direttamente dalla linea di comando. Una variabile d'ambiente molto importante è PATH che, analogamente a quanto succede in DOS, indica le directory in cui vengono ricercati i file da eseguire; in questo modo è possibile lanciare un programma senza doverne indicare il percorso, se questo è presente nel path.

Una istanza tipica di PATH potrebbe essere questa:

PATH=$PATH:~/bin
Notate che con PATH viene indicata la variabile, mentre con $PATH il suo valore, che nell'esempio è stato istanziato con ~/bin. In questo esempio, al valore già esistente di PATH, $PATH, viene aggiunta la directory ~/bin.

Riferendoci ancora alle variabili va detto che normalmente esse vengono 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

A volte è comodo poter sostituire i comandi, ovvero assegnare ad una variabile l'output di un comando, o sostituire l'output ad una stringa contenente il comando stesso. In questo caso il carattere utilizzato per operare la sostituzione, in tutte le shell esaminate, è l'apice inverso (`).

Facciamo un 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 la stessa operazione vista in precedenza in modo più semplice e con minori rischi di errore, ma può essere applicato solo in bash e zsh, non in tcsh.

Un ambiente user friendly

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 anteponendo un \ al meta carattere ne disabilita la funzione.

Altre shell utilizzano anche dei meta caratteri aggiuntivi: la zsh, ad esempio, consente di indicare un intervallo di interi con <x-y> 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 relativi all'interfaccia utilizzata per avere informazioni più dettagliate che qui non possiamo approfondire per motivi 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, sempre che sia 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 pesante 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 di 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. Semplice e molto efficace. Da notare che utilizzando i punto e virgola è possibile concatenare più comandi su una 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 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 usati se ~/.bash_profile non esiste, per compatibilità
~/.profile
~/.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 carica il file che indica come .$ENV. Se bash è richiamata come sh, cerca di imitarne il comportamento 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 creazione di un'interfaccia realmente amichevole, user friendly non perché presenti icone e finestre ma perché realmente consente di lavorare meglio e più efficacemente.

di Eugenia Franzoni

Ristampato con il permesso di Inter.net


[precedente] Inferno - Copertina - Corso Linux [successivo]