***** TUTTA LA POTENZA DELLA SHELL ***** Comiciamo a sfruttare le capacità di Linux di Ivan Fabris Nei numeri scorsi abbiamo visto tutta la bellezza e la praticità degli attauli Windows Manager, come KDE e GNOME. Essi semplificano notevolmente tutte le più consuete operazioni, ma questa semplicità spesso si paga con una perdita di "capacita' espressiva" e quindi di efficienza. Non dobbiamo infatti dimenticare che Linux è nato con una interfaccia a linea di comando La linea di comando offre una capacità elaborativa che i programmi grafici difficilmente raggiungono. Il File Manager di KDE è molto pratico per spostare, rinominare, o cercare dei file, o comunque per eseguire le operazioni più semplici, ma possono esserci molti casi in cui è richiesto un approccio tradizionale. Inoltre quasi tutti i programmi di configurazione e di avvio di sistema sono degli script e non dei file binari: basta aprirli con un editor di testi per rendersene conto. La shell è l'interfaccia tra il Sistema Operativo e l'utente: essa si incarica di di interpretare ed eseguire i comandi impartiti dall' utente. Non e' possibile spiegare in due pagine la programmazione di shell, non solo per la vastità dell'argomento ma anche per il numero e la varietà di shell che Linux mette a disposizione; bash, sh e csh sono le più comuni shell di sistema, e differiscono tra loro nella sintassi; ci sono poi le shell grafiche come tcl/tk, qulle orientate a particolari aspetti della programmazione come perl ed expect. Ci limiteremo quindi ad esplorare gli aspetti più ostici delle funzioni comuni alle shell di sistema in uso su Linux ( bash in particolare ) escludendo per ora i nuovi linguaggi, come tcl/tk e perl Comiciamo a vedere come i vari comandi comunicano tra di loro. ***** titoletto: La ridirezione dei flussi In Linux, come in UNIX, i comandi sono dei "filtri". Questo significa che hanno uno "standard input", uno "standard output" ed uno "standard error", cioè tre "strade" da cui prelevare i dati di ingresso o scrivere i dati in uscita ed eventuali messaggi di errore: solitamente l'input e' dato da tastiera e l' output e gli errori sono mandati allo schermo. Tuttavia ciascuna di queste tre strade può essere "ridiretta" altrove, e soprattutto l'output di un comando può diventare l'input di un comando successivo. Potemo così impartire al sistema ordini estremamente complessi ( all'apparenza ) partendo da poche semplici istruzioni La prima cosa che vedremo è la ridirezione dell'ouput. Partiamo da un problema pratico: vogliamo spedire ad un nostro amico la lista di tutti i nostri file mp3. Come ottenerla? Il listato scorre sullo schermo, sembrerebbe impossibile catturarlo. Basta mandarlo ad un file usando il metacarattere " > " , ossia, il "maggiore di" in questo modo ls > listafile.txt In questo caso la shell su cui stiamo lavorando, che molto probabilmente e' la Bash, se stiamo usando Mandrake, si incarica di creare un file chiamato listafile.txt e di riempirlo con l'output del comando ls Si può ridirigere anche l'input, con il metacarattere "<" . Non è di uso altrettanto frequente, ma è utile sapere che esiste: per vedere le se ci sono dei file con estensione .mp3 nella lista appena creata basta fare grep < listafile.txt ".mp3" Ancora una volta il risultato scorre sullo schermo, ma possiamo anche fare tutte e due le ridirezioni insieme grep < listafile.txt ".mp3" > listamp3.txt Se poi non volessimo perdere, durante una scrittura su file, il contenuto precedente, possiamo aggiungere i nuovi dati al file vecchio usando il metacarattere ">>" che significa "aggiungi al file" al posto di ">" , il quale cancellerebbe un eventuale file omonimo gia esistente Fin qui abbiamo proceduto un passo alla volta, ma e' anche possibile, anzi consigliabile, fare tutto in un colpo solo, in modo da non lasciare file temporanei sparsi per le directory. Sicuramente avrete gia visto un comando simile ls | more Il metacarattere | si chiama "pipe" ossia tubo, collegamento. Serve esplicitamente a fare si che l'output di un comando sia inviato come input ad un altro comando eseguito "contemporaneamente", senza passare attraverso file di appoggio. In questo caso la shell, eseguendo il comando ls, ne dirige l'output, anzichè a video, direttamente al comando more, il quale lo invia infine a video, in linee facilmente gestibili L'esempio precedente può quindi risolversi in questo modo ls | grep ".mp3" > listamp3.txt Questo è possibile anche grazie al fatto che i comandi di sistema producono un output formattato per righe ( record ) e colonne ( campi ), abbastanza ordinato e gestibile, e soprattutto coerente. La filosofia di questo sistema è di avere molti programmi specializzati, con tutte le opzioni possibili ed immaginabili. Difficilmente un comando esaudirà a puntino i nostri desideri, ma se riusciremo gestirlo e a combinarlo a dovere con altri comandi appropriati, ci sarà ben poco che non riusciremo a fare dalla shell. E qui entrano in gioco i parametri dei comandi. Il comportamento di ogni comando può essere modificato, anche in modo radicale, passandogli istruzioni supplementari: un esempio per tutti è il comando ls, uno dei più forniti di opzioni ( ce ne sono decine ). Questi parametri vengono forniti al comando facendoli solitamente precedere da un - o da un -- ls -la In questo modo richiediamo al comando ls ( lista i file ) di fornire una lista completa, mostrando anche i file nascosti ( -a ), e dettagliata ( -l ), anzichè una semplice lista di nomi di file Infine se intendiamo agire su più file alla volta, ci vengono in aiuto le "wildcards" ossia i caratteri "*" , "?" e "[]". Questi metacaratteri possono essere usati per rappresentare intere categorie di file o per selezionarne una parte: in particolare * rappresenta una qualsiasi stringa di zero o più caratteri in un nome di files ? rappresenta un singolo carattere [xxx] rappresenta un qualsiasi carattere di quelli specificati nell'insieme per esempio ls [a-m]* lista tutti i file che iniziano con una lettere compresa tra "a" e "m" incluse ls [v,k]*.?? lista tutti i file che iniziano con "v" oppure con "k" e che hanno una estensione di 2 caratteri ( kernel-2.2.12.tar.gz ) ****titoletto: Più comandi alla volta A volte abbiamo fretta, e non possiamo stare ad aspettare i comodi di un programma che impiega più del dovuto a terminare. A noi la shell serve subito! Come sempre, Linux ha una soluzione Possiamo lanciare comandi in parallelo usando il metacarattere & : makewhatis & in questo modo il processo makewhatis ( serve a costruire il database dei comandi ) viene eseguito in secondo piano, ossia in "background" e possiamo utilizzare la console per altri scopi, senza dover attendere la fine del comando appena lanciato. In caso di bisogno il processo può essere richiamato in primo piano , ossia in "foreground" tramite il comando "fg", può essere fermato momentaneamente premendo i tasti CTRL-z , rimandato in background col comando bg, o definitivamente ucciso con CTRL-c. Se dopo tutte queste manovre perdiamo la bussola, il comando "jobs" ci informerà sul numero e stato dei processi che stiamo gestendo. il numero ci serve per comunicare alla shell su quale processo vogliamo agire con uno qualunque dei comandi appena visti, mentre lo stato ci dice ciò che il processo sta facendo in quel momento ( Running, Stopped ( eventualmente il motivo ), Done. Vorrei far notare che il numero di processo assegnato dai comandi &, fg e bg e letto da jobs non ha nulla a che fare col PID "Process IDentitfier" riportato dal comando "ps" , ma è semplicemente un numero d'ordine interno alla shell. Infine, se invece pensiamo di lasciar lavorare in pace il computer e di andare a fare un giro, possiamo lanciare più comandi in successione, scrivendoli sulla stessa linea e separandoli con un ; e i comandi verranno eseguiti in successione, ciascuno al termine del precedente : make dep ; make clean ;make bzImage ; make modules ; make modules_install questa è una formulamagica che ormai tutti avrete provato. Ringraziate la shell che lavora al posto vostro e si ricorda di più di un ordine alla volta! ***** titoletto: Gli script Non crediate inoltre che sia finita qui! i comandi possono essere raggruppati in file eseguibili ( gli script di shell ) fino a raggiungere un notevole grado di complessità. Lo script consente l'uso di espressioni che dalla linea di comando sarebbero difficili o addirittura impossibili da eseguire, come i cicli, le istruzioni condizionali e le funzioni, ossia sono dei veri e propri linguaggi di profgrammazione. Alcune shell, come tcl/tk, offrono anche possibilità grafiche, tanto che alcuni "front end" grafici ai programmi a linea di comando sono scritte proprio il tcl/tk Uno script e' costituito da una serie di comandi, immessi in un file tramite un editor di testo; il file viene poi reso eseguibile con `chmod` ; la sintassi che lega questi comandi, ed i comandi stessi, variano a seconda della shell che li deve interpretare. La prima linea dello script in genere contiene il nome della shell per la quale è stato scritto #!/bin/sh Questa riga informa la shell corrente di caricare un interprete di comandi ( sh ) , il quale si occupera' di eseguire lo script. Nel caso che una linea simile non sia presente, verrà usato l'interprete corrente. Per mezzo di uno script è possibile automatizzare operazioni lunghe e complesse, personalizzare il proprio ambiente utente, e semplificare azioni che tramite l'interfaccia grafica potrebbero risultare noiose Le shell di Linux offrono un linguaggio di programmazione completo, e quindi supportano diversi tipi di variabili: - le variabili di Ambiente come PATH, che fanno parte del sistema e non necessitano di definizione, ma possono essere modificate all' interno dello script - le variabili predefinite come $0, che rappresenta il nome dello script, anch'esse fornite dal sistema, ma che non possono essere modificate - le variabili utente, che possono essere create, modificate e distrutte all' interno dello script La differenza principale fra la programmazione di shell e quella con altri linguaggi consiste nel nel fatto che nella shell le variabili non sono tipizzate, ossia non e' necessario specificare il tipo della variabile ( numerico intero, reale, stringa,ecc.. ) Sono inoltre possibili operazioni di confronto tra espressioni, tra stringhe e tra numeri, test sui file e operatori logici ( not, and e or ), istruzioni di iterazione ( cicli for, while, until, repeat, select...) istruzioni condizionali (if..then, case ) e la definizione di funzioni. *****titoletto: Variabili L'assegnamento di un valore ad una variabile è immediato : nomevariabile=valorenumerico nomevariabile=stringa nomevariabile='stringa con spazi' mentre per accedere al valore di una variabile si usa $ : echo $nomevariabile stampa a video il valore contenuto in "nomevariabile" Vengono gestiti come variabili anche i parametri passati allo script dalla shell e dall'invocazione dell'utente: $# numero di parametri posizionali passati allo script $? codice dell'ultimo comando eseguito all'interno dello script $0 nome dello script $* un'unica stringa di tutti i parametri passati allo script $1,$2,$3.... i parametri passati dall'utente allo script **** titoletto: Caratteri speciali Sia da linea di comando che da script si possono usare alcuni caratteri speciali : apice singolo ' Si usa per racchiudere una stringa, in modo da impedire alla shell di trattarla come se fosse una variabile var=10 echo 'stampa il contenuto di $var' otterremo : stampa il contenuto di $var apice doppio " Permette alla shell di risolvere le variabili var=10 echo "stampa il contenuto di $var" otterremo : stampa il contenuto di 10 backtick ` la shell esegue la stringa racchiusa da due backtick come se fosse un comando: var=`wc -l file.txt` otterremo : il numero di linee di file.txt viene assegnato a "var " backslash \ posto davanti ad un carattere speciale, fra quelli appena visti, lo priva del suo significato segnodollaro=\$ echo $segnodollaro otterremo : $ cancelletto # identifica una linea di commento, che verrà ignorata ****titoletto: Confronto tra espressioni I confronti possono essere eseguiti tramite l'operatore [] o la parola chiave "test". Possono essere confrontati stringhe, numeri e tipi di file, per mezzo di operatori logici ed operatori ad hoc per ciascun tipo di confronto = per verificare l'uguaglianza tra stringhe != per verificare la diversità -n per verificare se la lunghezza di una stringa è maggiore di zero -z per verificare se la lunghezza della stringa è zero stringa1="Linux" stringa2="UNIX" if [ stringa1 = stringa2 ] then echo "le due stringhe sono uguali" else echo "le due stringhe sono diverse" fi per quel che riguarda i numeri si possono usare i seguenti operatori -eq uguaglianza ( equal , = ) -ge maggiore o uguale ( greater or equal , >= ) -le minore o uguale ( less or equal , <= ) -ne disuguaglianza ( not equal , != ) -gt maggiore ( greater than , > ) -lt minore ( less than , < ) num1=3 num2=5 if [ num1 -eq num2 ] then echo "i due numeri sono uguali" else echo "i due numeri sono diversi" fi mentre per i file useremo questi -d per verificare se un file è una directory -f per verificare che sia un file normale -r -w -x per sapere se sul file è impostato rispettivamente un permesso di lettura ( -r ) di scrittura ( -w ) o di esecuzione ( -x ) -s per verificare se il file ha lunghezaza maggiore di zero if [ -r file ] then echo " il file può essere letto" fi Infine si usano i seguenti operatori logici per concatenare più espressioni in una sola ! negazione unaria -a and logico tra due espressioni -o or logico tra due espressioni **** titoletto: Istruzioni condizionali Le istruzioni condizionali servono per decidere cosa fare in caso di più alternative L'istruzione "if" valuta una espressione e prende una decisione, e si usa in questo modo if [ test da eseguire ] then istruzioni da eseguire se il test risulta vero else istruzioni da eseguire se il test risulta falso fi Le condizioni possono essere nidificate, ossia e' possibile che una delle istruzioni da eseguire nel ramo di then o in quello di else sia un'alto if, debitamente costruito e terminato con un fi Se invece abbiamo più di due possibilità conviene usare l'istruzione "case" case $variabile in valore1 | valore2 ) istruzioni-a ;; valore3 | valore4 ) istruzioni-b ;; *) istruzioni-default ;; esac Sembra complicata ma non lo è. $variabile e' il valore che pilota la decisione: se è compreso tra valore1 e valore2, che possono anche coincidere, allora verrà eseguita la lista di istruzioni istruzioni-a, se e' compresa tra vaolre3 e valore4 allora verrà eseguita la lista di istruzioni istruzioni-b, e via dicendo....se poi non si trovano riscontri viene eseguito il "default" ossia la sequenza di istruzioni istruzioni-default. Infine la parola "esac" termina il costrutto. Il doppio ; è necessario per forzare l'uscita da case appena si ottiene una corrispondenza, altrimenti verrebbero eseguite anche le istruzioni delle condizioni successive ****titoletto: Istruzioni di iterazione Le istruzioni di iterazione si usano per ripetere più volte una serie di comandi, e ne esistono alcune varianti: il ciclo "for", nella sua forma più semplice, appare così for var in elencovalori do istruzioni done dove elencovalori è una variabile che contiene un certo numero di voci, oppure una serie di valori ottenuta con un comando racchiuso da due backtick ( ricordate? ) tipo questo for nomefile in `ls` Esistono poi le istruzioni while e until while che continua il ciclo di istruzioni fino a che la condizione di controllo rimane vero. Perciò è possibile che il ciclo non venga mai eseguito, se la condizione è falsa fin dall'inizio, oppure che non termini mai, se la condizione resta sempre verificata Il formato dell' istruzione è while [ condizione ] do istruzioni done dove [condizione] è costruita con le regole viste in precedenza until invece agisce esattamente all'ooposto, il ciclo termina appena la condizione diventa vera; per il resto, il formato è identico a while ****titoletto: Funzioni All'interno dei programmi di shell è possibile specificare anche delle funzioni, cioè un insieme di comandi che può essere utilizzato in diverse parti del programma, senza doverlo scrivere più volte. Per dichiarare una funzione, in genere in fondo al programma, basta scrivere nomefunzione () { istruzioni } La funzione verra' poi chiamata con nomefunzione parametro1 parametro2.... dove i parametri, se presenti, vengono gestiti all'interno della funzione tramite le solite variabili $1, $2 .... ****titoletto: Istruzioni varie Tra le moltissime istruzioni rimanenti le più importanti sono : expr - permette di trasformare una stringa nel suo valore numerico e di eseguire operazioni aritmetiche count=0 count=`expr $count + 1` incrementa la variabile count di una unità eval - permette di considerare le stringhe come se fossero dei comandi da eseguire com="ls -la" eval com shift - Serve per far scorrere verso destra i parametri posizionali passati al programma: essi vengono identificati da $1, $2, $3....$n , e mediante il comando shift ciascun parametro viene traslato verso un numero più basso. Per esempio specificando "shift 3" il parametro $4 diventa $1, $5 diventa $2, e i primi 3 parametri vengono persi. Usando solo $1 e il comando shift si possono scandire tutti i parametri passati al programma, fino ad esaurirli break - Termina un ciclo for, until o while exit - Termina il programma di shell. Pù essere seguito da un numero ( exit 3 ) per informare un eventuale programma chiamante o l'utente su cosa ha causato la fine del programma: di solito la restituzione di uno zero indica una terminazione regolare **** titoletto: Siamo alla fine Ovviamente non è tutto qui! Non per nulla, il manuale della Bash è un documento di 60 pagine A4 denso di descrizioni di opzioni e di comandi, che per poter essere dettagliatamente spiegati ed esemplificati richiederebbero almeno 10 volte tanto. Tuttavia ormai ne sappiamo abbastanza per poter lasciare al sistema molte operazioni noiose e ripetitive, come svuotare la cache del Netscape, smistare la posta, e qualsiasi altra cosa vi venga in mente... soprattutto ora possiamo dare uno sguardo agli script di avvio di Linux o a quelli di configurazione dei programmi con la pretesa di capirci qualcosa! Per saperne di più, usate il comando man. Usatelo per tutti i comandi che avete visto fino ad ora. Guardate anche tutti i comandi che avete a disposizione sul sistema: sono contenuti nelle directory del path ( echo $PATH ) E, per i più curiosi : info bash > bash.txt per ottenere le famose 60 pagine di informazioni. Buono studio! ***** DIDA FOTO 1 sh-xman.jpg Una piccolissima parte dei comandi disponibili su un sistema Linux medio. Usate xman e dategli un'occhiata FOTO 2 sh-mancat.jpg La pagina man del comando cat. Le man pages sono il primo posto dove andare a guardare in caso di dubbi. FOTO 3 sh-ricercacmd.jpg Pare un comando complicato, in realtá mi ha semplicemente listato tutte le immagini jpeg nella cache del Netscape FOTO 4 sh-ricercaxfm.jp4 Provate invece a cercare le, con un filemanager, in ciascuna delle tante sottodirectory del Netscape FOTO 5 sh-mc.jpg Il midnight Commander integra molti comandi tipici della shell, puó esplorare le tarball e gli rpm, e tanto altro ancora in pochissimi colpi di tastiera FOTO 6 dos.jpg Beh le cose più semplici riusciva a farle anche il DOS...una volta. FOTO 7 sh-errori.jpg Dalla console potete anche controllare quali guai provocano i vostri ordini