Crea sito

La shell e i comandi

13 / 100

L’interfaccia a linea di comando.

I sistemi Unix nascono negli anni ’70, ben prima della nascita delle interfacce gra che, quando l’unico modo di interagire con il computer era attraverso dei terminali, se non addirittura delle semplici telescriventi. Per cui, anche se oggi sono disponibili delle interfacce gra che del tutto analoghe a quelle presenti in altri sistemi operativi nati in tempi piu recenti, l’interfaccia a riga di comando resta di fondamentale importanza, dato che 30 anni di storia e migliaia di persone che ci hanno lavorato sopra per migliorarla, la hanno resa l’interfaccia utente piu potente e flessibile che ci sia.

La   filosofia progettuale

Molte persone utilizzano un coltellino svizzero, dato che questo permette di avere in solo oggetto un discreto insieme di attrezzi diversi: coltello, forbici, cacciavite, seghetto, cavatappi. Pero e molto di cile vedere un professionista usare il coltellino svizzero per fare il suo lavoro. Un professionista ha bisogno di attrezzi professionali, e un carpentiere non costruisce una casa con un coltellino svizzero, ma con tanti attrezzi, ciascuno dei quali e specializzato nello svolgere un compito specifico.

Le persone che hanno progettato l’interfaccia a riga di comando erano appunto dei profes-sionisti, che sapevano bene che anche se fare un programma unico per tutti i compiti poteva essere attraente per l’utente nale, che deve conoscere solo quello, in pratica questo sarebbe stato di cile da scrivere, mantenere e soprattutto estendere. Per cui da professionisti pensarono ai programmi come a degli attrezzi, e piuttosto che il coltellino svizzero realizzarono l’equivalente della cassetta degli attrezzi (quella che in gergo viene chiamata “Unix toolbox “), con in testa un criterio fondamentale: che ciascun programma facesse una sola cosa, nel miglior modo possibile.

Questa è la caratteristica fondamentale dei programmi base di un sistema unix-like come GNU/Linux.

Ogni comando è progettato per eseguire un compito preciso. Ne abbiamo incontrati gia diversi nel corso della trattazione delle caratteristiche del sistema nel precedente capitolo: ls mostra la lista dei le, ps la lista dei processi, cp copia un le, chmod cambia i permessi, man mostra le pagine di manuale, ecc. I comandi hanno uno scopo preciso e precise funzionalita; le opzioni sono limitate e comunque speci che allo scopo del comando, e sono descritte dettagliatamente nella relativa pagina di manuale.

Il passo successivo fu quello di costruire anche un meccanismo che permettesse di combinare insieme i vari programmi, cosicche divenisse possibile eseguire, con una opportuna combinazione, anche dei compiti che nessuno di essi era in grado di fare da solo. Questo aveva il grande vantaggio, rispetto all’approccio del programma universale, di non dover attendere che l’autore dello stesso si decidesse a programmare la funzione in piu che serviva e che non era stata prevista all’inizio.

Questo e il ruolo della shell, cioe del programma che implementa l’interfaccia a riga di comando; e attraverso di essa che, concatenando vari comandi, si puo costruire l’equivalente di una catena di montaggio, in cui il risultato di un comando viene inviato al successivo, riuscendo così a realizzare compiti complessi con grande velocita e flessibilita, e spesso a fare anche cose che gli autori dei singoli programmi non si sarebbero neanche immaginati.

La modalità tradizionale con cui si utilizza l’interfaccia a riga di comando e attraverso una sessione di lavoro interattiva eseguita su un terminale. Un tempo questo accesso avveniva con la classica procedura di collegamento al sistema sulla console (torneremo su questo in sez. 4.3.5) dove un apposito programma, login, una volta autenticato l’utente, mette in esecuzione la shell.

Oggi, con la presenza sempre piu diffusa delle interfacce grafiche e la diffusione delle reti, si hanno molte altre modalità di accesso ad un terminale. Ad esempio si puo avere un opportuno programma, il cosiddetto emulatore di terminale, che opera all’interno dell’interfaccia gra ca creando un terminale virtuale a cui si accede all’interno di una nestra, oppure si puo accedere via rete attraverso un programma di collegamento remoto ed anche in questo caso esistono diversi programmi e diversi protocolli di collegamento (vedremo il principale in sez. 8.3). In tutti questi casi comunque, una volta predisposta l’opportuna interfaccia di accesso, viene sempre lanciata una shell.

Si ricordi comunque che per il kernel, secondo la filosofia fondamentale di Unix la shell resta un programma come tutti gli altri; essa ha pero un compito fondamentale, che e quello di fornire l’interfaccia che permette di lanciare altri programmi. Inoltre e sempre la shell che permette di usufruire di tutta una serie di ulteriori funzionalit messe a disposizione dal kernel, come il controllo di sessione

Dato che la shell e un programma come gli altri, essa puo essere realizzata in diversi modi, ed in effetti nel tempo sono state realizzate diverse shell. Anche in questo caso ci sono stati due loni di sviluppo, il primo deriva dalla prima shell realizzata, la Bourne shell, chiamata cos dal nome del suo creatore. La Bourne shell e la shell piu antica e le sue funzionalit sono anche state standardizzate nello standard POSIX.2. Il secondo lone nasce da un’altra shell, che usa una sintassi leggermente diversa, con delle analogie con quella del linguaggio C, e che per questo venne chiamata C shell.

Ciascuno di questi due filoni ha dato vita a successive versioni di shell con funzionalit piu o meno avanzate; un breve elenco di quelle piu signi cative disponibili su GNU/Linux e il seguente:

Bourne shell e derivate.

{ La Bourne shell. La prima shell di Unix, in genere utilizzata semplicemente con il comando sh. Non viene praticamente piu usata. In GNU/Linux e sostituita da bash. che quando viene invocata come sh fornisce esclusivamente le funzionalit  previste dallo standard POSIX.2, disabilitando le varie estensioni di cui e dotata, o da dash. Sugli altri sistemi che rispettano lo standard POSIX.2, e di norma sostituita da ksh.

{ La Bourne-Again Shell.

La bash e la shell di riferimento del progetto GNU. Il suo nome e un gioco di parole sul nome della Bourne shell, in sostanza una shell \rinata”. Viene utilizzata con il comando bash. Incorpora molte funzionalit avanzate, come la storia dei comandi (detta history), l’auto-completamento dell’input sulla linea di comando (per comandi, nomi di le e qualunque altra cosa, date le opportune estensioni), editing di linea, costrutti di programmazione complessi e molto altro (praticamente di tutto, si vocifera sia anche in grado di fare il caffè).

{ La Korn Shell

La Korn shell (dal nome dell’autore) e stata la prima ad introdurre la history (l’accesso ai comandi precedenti) e l’editing della linea di comando. Ha il grosso difetto che gran parte delle funzionalit avanzate non vengono attivate di default, per cui occorre un ulteriore lavoro di con gurazione per utilizzarla al meglio. Viene utilizzata con il comando ksh. Non viene usata su GNU/Linux dato che bash ne ha tutte le caratteristiche; e pero utile conoscerne l’esistenza dato che e facile trovarla su altri Unix.

{ La ash. Una shell minimale, realizzata in poche decine di kilobyte di codice sorgente. Viene utilizzata con il comando ash. Ha molti comandi integrati, occupa poca RAM e poco spazio disco, ed ha poche funzioni (ma e conforme allo standard POSIX.2). Viene usata spesso nei dischetti di installazione o recupero e nei sistemi embedded, puo essere utile per sistemi dove si fa un grosso uso di script semplici perch e piu veloce di bash.

{ La Z shell. Un’altra shell avanzata. Viene utilizzata con il comando zsh. O re praticamente le stesse funzioni della Korn shell, ed altre funzionalit avanzate, come il completamento di comandi, le e argomenti, che pero trovate anche nella bash.

C shell e derivate.

{ La C shell.  Utilizza una sintassi con analogie a quella del linguaggio C. Viene

utilizzata con il comando csh. In GNU/Linux non e disponibile essendo sostituita da tcsh.

{ La tcsh. E una evoluzione della C shell, alla quale aggiunge history e editing di linea e varie funzionalit avanzate. Viene utilizzata con il comando tcsh. Si trova su vari Unix proprietari, ma e poco di usa su GNU/Linux, pur essendo disponibile. Dato che e il principale strumento di lavoro di un amministratore professionista, la scelta della shell e spesso una questione strettamente personale. Qui parleremo pero solo di bash, che e la shell utilizzata in praticamente tutte le distribuzioni di GNU/Linux, e probabilmente e anche la piu potente e essibile fra quelle disponibili. L’unico motivo per volerne usare un’altra infatti e solo perchè siete maggiormente pratici con quella, nel qual caso probabilmente non avete bisogno di leggere questo capitolo. Il riferimento piu immediato per il funzionamento della bash e la sua pagina di manuale, accessibile al solito con man bash. Probabilmente questa e una delle piu lunghe fra tutte le pagine di manuale: quella associata alla versione 4.2 consta di ben 5459 righe, ed in e etti piu che una pagina e un manuale. Per questo in seguito faremo riferimento, quando necessario, alle varie sezioni in cui essa e divisa.

Sessioni di lavoro e job control

L’uso dell’interfaccia a riga di comando è strettamente legato all’uso di un terminale sul quale opera una shell, il terminale di controllo, all’interno di quella che viene solitamente chiamata una “sessione di lavoro”. Questo ci aveva gia portato a tracciare una distinzione fra i processi che avevamo classi cato come interattivi, perchè associati ad un terminale di controllo, visibile nell’output di ps alla colonna TTY, e quelli non interattivi, o meglio quelli che non lo sono tramite l’interfaccia a riga di comando, che non hanno nessun terminale di controllo.

Per spiegare le modalità in cui viene realizzata l’interazione tramite il terminale, occorre anticipare una caratteristica fondamentale dell’interfaccia a riga di comando. Tutte le volte che si lancia un programma che deve interagire con la riga di comando, questo si aspetta di avere a disposizione e gia aperti tre file che convenzionalmente sono chiamati standard input, standard output e standard error. Dato che questi sono i primi tre le aperti e lo sono in questa sequenza, ad essi vengono sempre assegnati rispettivamente i file descriptor 0, 1 e 2.

Convenzionalmente un programma legge i suoi dati in ingresso dal primo le descriptor, scrive i dati in uscita sul secondo, e gli eventuali errori sul terzo. Si tenga presente che e solo una convenzione, anche se seguita in maniera universale, si puo tranquillamente scrivere un programma che si comporta in modo diverso, scrivendo e leggendo da un le descriptor qualunque, ma ovviamente si avranno problemi di interazione con gli altri programmi.

Quando un processo è interattivo nel senso appena illustrato, tutti e tre questi le corrispon-dono al terminale di controllo, e l’interfaccia dei terminali (cioe quella dei dispositivi di questo tipo) fa sì che in lettura il kernel faccia arrivare sullo standard input quanto scritto sulla tastiera e che quanto scritto sullo standard output venga stampato sullo schermo, o sulla nestra dentro l’interfaccia gra ca, qualora si usi un emulatore di terminale, o trasmesso via rete, se si usa un terminale associato ad una connessione remota. Per capire meglio questa infrastruttura vediamo un esempio della sua applicazione. Come accennato una delle modalità per utilizzare l’interfaccia a riga di comando e quella di collegarsi ad una console con la tradizionale procedura di login su terminale; nei risultati del comando ps mostrati a pag. 27 si puo notare come sia presente il processo getty associato ad alcuni terminali virtuali (da tty1 a tty6).

In questo caso e getty che si cura dell’apertura iniziale dei tre le di standard input, standard output e standard error facendo riferimento al terminale che gli e stato indicato, nel caso una delle console virtuali, corrispondenti ai terminali da /dev/tty1 a /dev/tty6, che diventera il terminale di controllo di tutta la seguente sessione di lavoro. Poi il programma stampa un messaggio di benvenuto e la linea \login: ” e si pone in attesa che un utente scriva sul terminale il suo \username” (il cosiddetto nome utente o nome di login), che come accennato e il nome con cui un utente si identi ca presso il sistema.

Una volta che questo sia stato scritto, getty esegue direttamente il programma login che si cura di chiedere la password ed e ettuare l’autenticazione. Se questa ha successo e login che si incarica di impostare il valore del session ID per il processo in corso, cambiare gli identi cativi ad esso associati cioe l’user ID ed il group ID per farli corrispondere a quelli dell’utente che si e collegato, ed in ne lanciare quella che si chiama una “shell di login”. In tutta questa procedura i tre file di standard input, standard output e standard error resteranno aperti ed associati al terminale di controllo da cui si e partiti, che e quello su cui verra utilizzata la shell.

Una volta completata la procedura di collegamento avremo a disposizione una shell con un certo valore del session ID, e dato che anche questa e una proprieta che viene conservata quando si creano processi gli e si lanciano altri comandi, si avr come conseguenza che anche tutti i processi lanciati da detta shell saranno identi cati dallo stesso session ID. Tutto cio vale anche per tutte le altre modalit con cui si puo iniziare una sessione di lavoro su un terminale, che sia attraverso un emulatore ad interfaccia gra ca o un programma di connessione remota. La modalità piu comune di uso della shell e quella in cui si lancia un comando, si aspetta che questo completi le operazioni, e poi se ne lancia un altro, e cos via. Quello che succede in questo caso e che per ogni comando la shell crea un nuovo processo e si ferma in attesa che questo completi le sue operazioni. Alla conclusione del programma la shell riprende il possesso del terminale e consente di lanciare un altro comando. In genere si lavora in questo modo perch tutte le volte che si esegue un programma interattivo questo deve avere a disposizione il terminale di controllo per ricevere i dati in ingresso ed inviare i dati in uscita.

Tutte le volte che, come nell’esempio, si manda un processo in background, la shell stampa sul terminale un valore numerico fra parentesi quadre, detto job ID (dato che in gergo si suole chiamare job un processo che lavora in background) seguito dal PID del processo. La shell infatti identi ca i processi attivi all’interno di una stessa sessione di lavoro assegnando loro un numero progressivo, il job ID, che poi puo essere utilizzato come riferimento nei vari comandi di gestione del job control. L’esempio ci mostra anche come la shell provveda a stampare una noti ca sul terminale tutte le volte che un processo viene bloccato, utilizzando di nuovo il job ID. Oltre a lanciare un processo in background ci sono casi in cui puo esser necessario fermare temporaneamente un comando in esecuzione, per eseguire un controllo o un qualche altro compito piu urgente.

Abbiamo visto che è possibile fermare l’esecuzione di un processo con l’invio dei segnali SIGSTOP o SIGTSTP, il sistema del controllo di sessione sempli ca questa operazione permettendo di inviare SIGTSTP ai processi che sono attivi su un terminale, tramite la combinazione di tasti C-z, cioe l’uso del tasto \control” insieme alla lettera “z” dove il carattere “+” indica il cosiddetto job corrente, cioe il processo su cui si e operato per ultimo, mentre il carattere “” indica il job precedente (per maggiori dettagli consultare il manuale della bash alla sezione \JOB CONTROL“).

Quando un programma e stato fermato con C-z la shell ci consente di farlo ripartire in background con il comando bg, ovviamente se questo dovesse cercare di eseguire un accesso a terminale si bloccherebbe immediatamente, per questo e inutile usare bg su un comando che si e fermato per questo motivo: si fermera di nuovo. Un processo puo essere riassociato al terminale, cioe essere rimesso, come suol dirsi, in foreground, con il comando fg, se il processo era stato fermato in attesa di poter disporre del terminale in questo modo viene anche fatto ripartire.

Se non si specifica nulla sia bg che fg che tutti gli altri comandi del controllo di sessione agiscono sul job corrente. Qualora si voglia invece agire su uno specifico job si puo speci care come parametro sia il testo della riga di comando, che il numero di job ID stampato da jobs e questo puo essere indicato sia direttamente che preceduto da un carattere \%“. Quest’ultima sintassi puo essere usata anche con il comando kill, se questo e realizzato come comando interno della shell come avviene nel caso di bash, per mandare i segnali ad un processo identi cato per job ID piuttosto che per PID. Se si vuole aspettare la fine di uno specifico comando che si è mandato in background si puo usare il comando wait, passandogli di nuovo il job ID come argomento. Una volta che si voglia terminare una sessione ed uscire dalla shell si puo usare il comando exit o la combinazione di tasti C-d che,corrisponde ad inviare un carattere di end-of- le sul terminale, cosa che segnala alla shell che lo si vuole chiudere e con questo terminare la sessione di lavoro.Se si cerca di uscire dalla shell e chiudere la sessione ma ci sono job in background o bloccati, la shell stamperà un avviso senza uscire, così da poter provvedere a risolvere la situazione. Se pero si ripete immediatamente la richiesta di chiusura, questa verra onorata e la shell verra terminata insieme ai processi pendenti. Il comando exit prende anche un argomento opzionale che costituisce lo stato di uscita e viene usato principalmente per uscire all’interno degli script .

Translate »