Le shell dei comandi del sistema operativo UNIX sono dei programmi che, al pari di altri, possono essere eseguiti da ciascun utente del sistema o possono essere richiamati da altri programmi. A differenza di altri software applicativi (es.: un editor di testo, un'applicazione di calcolo scientifico, un software per la posta elettronica, ecc.) la shell dei comandi è progettata per essere uno degli strumenti principali per consentire una interazione tra l'utente ed il sistema operativo. Mediante la shell l'utente è in grado di gestire i file e le directory presenti sul filesystem della macchina, di elaborarne il contenuto e di eseguire altri programmi presenti sul sistema utilizzando la tastiera del proprio terminale come unità di input e lo schermo alfanumerico del terminale come unità di output.
L'utilizzo del sistema operativo in modalità “alfanumerica” è tipico dei sistemi operativi destinati ai server, macchine che eseguono programmi in grado di offrire servizi di vario genere ad altri sistemi informatici o agli utenti connessi in rete. Su queste macchine tipicamente gli utenti non operano in modalità interattiva, a meno che il sistema non consenta una connessione attraverso terminali direttamente connessi al server o mediante software di emulazione di terminale eseguiti sulle postazioni client degli utenti (spesso dei normali personal computer). L'alternativa ad una shell di comandi che opera in modalità alfanumerica è un'interfaccia utente grafica (GUI -- Graphical User Interface), che consente all'utente di interagire con il sistema operativo attraverso delle componenti grafico-funzionali che spesso contribuiscono a semplificare l'uso del sistema (es.: invece di digitare un comando sulla tastiera del terminale, con una GUI è possibile eseguire un comando o un programma selezionando un'icona visualizzata all'interno di una finestra grafica). Salvo alcune eccezioni, le interfacce utente grafiche sono presenti sulle postazioni di lavoro degli utenti finali (workstation) e non sui server, dove tutte le risorse del sistema sono impiegate per l'esecuzione dei programmi che offrono servizi, senza lasciare spazio all'implementazione di interfacce grafiche sofisticate che semplificherebbero in parte l'interazione tra l'utente e la macchina, penalizzando però l'efficienza dell'esecuzione dei programmi applicativi.
Con un minimo di semplificazione possiamo dire che la shell è un programma che esegue iterativamente sempre la stessa operazione: attende che gli venga fornito in input un comando da eseguire, lo valuta per verificare che il comando sia sintatticamente corretto e lo esegue; quindi torna ad attendere che sia fornito in input il comando successivo. Questo processo iterativo termina quando la shell riceve un segnale che indica che l'input è terminato e che non le saranno inviati altri comandi; a quel punto il programma shell termina, liberando la memoria allocata ed altre risorse della macchina messe a disposizione dal sistema operativo.
La shell può essere lanciata in esecuzione in modo automatico dal sistema operativo quando l'utente esegue il login sul sistema stesso, ovvero può essere eseguita dall'utente mediante un comando impartito su una shell già aperta, oppure mediante l'utilizzo di apposite utility grafiche, nel caso in cui si stia operando su un sistema con interfaccia utente grafica. A esempio su un computer Apple Macintosh con sistema operativo Mac OS X è possibile utilizzare la shell dei comandi eseguendo il programma di utilità “Terminal”, presente nella cartella “Utility” della cartella “Application”. Su una workstation Linux dotata di un desktop manager grafico come GNOME o KDE, è possibile aprire la shell dei comandi selezionando il programma “Terminal” dal menù “Applications → Accessories”. In ambiente Windows, come abbiamo accennato nell'introduzione, se è stato installato il pacchetto Cygwin con i suoi moduli di base (che comprendono anche la shell Bash), è possibile eseguire la shell selezionando l'icona presente sul Desktop o sul menù “Start → Programmi” denominata “Cygwin”.
Una volta attivata la shell dei comandi è possibile visualizzare il nome del programma shell che stiamo utilizzando con il seguente comando:
$ echo $SHELL
/bin/bash
Nel caso in cui la shell di default non sia la Bash è possibile verificare se è presente sul sistema in una delle directory elencate nella variabile d'ambiente PATH, utilizzando il comando “which” ed eseguirla con il comando “bash”, come nell'esempio seguente:
$ echo $SHELL
/bin/tcsh
$ which bash
/bin/bash
$ bash
bash-2.03$
La shell in questo modo opera in modalità interattiva, acquisendo in input ogni singolo comando ed i parametri specificati sulla riga di comando e mandando in esecuzione il comando stesso; l'output viene visualizzato sulla medesima finestra di terminale, come negli esempi precedenti.
Ogni comando impartito alla shell viene terminato premendo il tasto Invio/Enter . È possibile impartire più comandi sulla stessa riga, separandoli l'uno dall'altro con il carattere “;” (punto e virgola). È possibile anche spezzare l'inserimento di un comando su due o più righe, terminando ciascuna riga intermedia con il carattere “\” (backslash). Ad esempio:
$ pwd ; echo $SHELL ; hostaname
/home/marco
/bin/bash
aquilante
$ echo \
> $SHELL
/bin/bash
Oltre che in modalità interattiva, come abbiamo visto negli esempi precedenti, è possibile eseguire la shell in modo tale che elabori una sequenza di comandi riportati in un file di testo ASCII; il contenuto del file è il programma che d'ora in avanti chiameremo shell script.
Ad esempio supponiamo di aver predisposto un file denominato “script.sh”, memorizzato nella nostra home directory; il contenuto del file può essere il seguente:
1 echo -n "Oggi e' il "
2 date +%d/%m/%Y
Possiamo eseguire questo script molto elementare specificando il nome del file sulla linea di comando con cui viene invocata la shell:
$ bash script.sh
Oggi e' il 10/6/2011
La shell può anche ricevere la sequenza dei comandi da eseguire attraverso un pipe che rediriga l'output di un altro comando sullo standard input di Bash:
$ cat script.sh | bash
Oggi e' il 10/6/2011
Infine, e forse questo è il metodo più diffuso per eseguire uno shell script, è possibile specificare sulla prima riga del programma, con la notazione “#!”, il path assoluto dell'interprete da utilizzare per l'esecuzione dello script; se al file che contiene lo script vengono assegnati i permessi di esecuzione, allora sarà possibile lanciarlo direttamente, lasciando che sia il sistema operativo ad eseguire la Bash, passandogli in input lo script:
$ cat script.sh
#!/bin/bash
echo -n "Oggi e' il "
date +%d/%m/%Y
$ chmod 755 script.sh
$ ls -l script.sh
-rwxr-xr-x 1 marco users 49 18 Apr 23:58 script.sh
$ ./script.sh
Oggi e' il 10/6/2011
Forse è superfluo osservare che, nell'ultimo comando dell'esempio precedente, invocando direttamente l'esecuzione dello script memorizzato nel file “script.sh” presente nella directory corrente, si è indicato il path relativo “./” prima del nome del file; è stato necessario indicare il path della directory in cui si trova lo script da eseguire perché spesso, per ragioni di sicurezza, la directory corrente, rappresentata dal simbolo “.”, non è presente nella lista delle directory in cui la shell deve cercare i comandi esterni da eseguire (la lista di tali directory, come vedremo meglio in seguito, è memorizzata nella variabile d'ambiente PATH).
Gli shell script devono essere memorizzati in un file di testo ASCII creati utilizzando un programma “editor” che non introduca caratteri o sequenze aggiuntive per la formattazione del testo. Ad esempio degli editor adatti alla creazione di shell script sono i programmi vi o Emacs disponibili in ambiente UNIX/Linux, o programmi come Notepad, TextEdit o UltraEdit in ambiente Microsoft Windows.
Come abbiamo visto nell'esempio precedente, è buona norma inserire come prima riga di ogni script Bash, la sequenza “#!/bin/bash” in cui viene riportato il path assoluto del programma Bash nel filesystem della macchina su cui si intende eseguire lo script. In questo modo è possibile lanciare direttamente lo script sulla linea di comando, senza dover specificare il nome del file come argomento del comando “bash”. L'indicazione del programma interprete che deve essere usato dal sistema operativo per tradurre ed eseguire le istruzioni dello script, viene fornita nella prima riga dello script stesso, immediatamente dopo la sequenza di caratteri “#!”. Nei nostri esempi supporremo che l'eseguibile dell'interprete Bash si trovi nella directory “/bin”, ma su sistemi differenti potrebbe essere installato in altre directory (ad esempio: “/usr/bin”, “/usr/local/bin”, ecc.).
In generale il carattere “#” consente di introdurre un commento nel sorgente dello script: qualunque carattere presente su una riga dello script dopo il carattere “#” viene ignorato dall'interprete dei comandi. Spesso infatti si usa inserire delle frasi di commento nel sorgente dello script per descriverne il funzionamento o per spiegare l'effetto di specifici comandi.
Come nell'inserimento di comandi in modalità interattiva, anche nella codifica di uno script ogni istruzione del programma può essere scritta su una riga a se stante, oppure spezzandola su più righe e terminando ciascuna riga (tranne l'ultima) con il carattere “\”. Più istruzioni possono essere riportate sulla stessa riga utilizzando il carattere “;” come separatore delle istruzioni.
Le istruzioni del programma possono essere “indentate” per rendere più leggibile il codice sorgente, ma si deve porre attenzione nell'uso degli spazi: l'interprete Bash è più “pignolo” di altri interpreti o compilatori e, in alcuni casi, non ammette l'inserimento di spazi arbitrari tra i termini che compongono le istruzioni; in altri casi invece l'uso dello spazio è indispensabile per consentire la corretta interpretazione di una determinata istruzione.
Non esistono caratteri per la delimitazione di blocchi di istruzioni inserite all'interno di una struttura di controllo (es.: le istruzioni da ripetere all'interno di una struttura di controllo iterativa). Esistono invece opportune parole chiave del linguaggio che consentono di circoscrivere correttamente l'inizio e la fine di un determinato blocco di istruzioni; tali parole chiave variano a seconda dell'istruzione utilizzata per il controllo del flusso del programma.
Nella sintassi del linguaggio Bash alcuni caratteri assumono un significato speciale, ossia, se presenti in una stringa di caratteri o come argomento di un comando, questi caratteri svolgono una funzione ben precisa. In Tabella è riportato un elenco con la descrizione di ciascun carattere speciale.
Carattere | Descrizione |
---|---|
\ (backslash) | Precede un altro carattere per comporre una sequenza di escape; come ultimo carattere di una riga indica all'interprete che l'istruzione prosegue alla riga successiva |
# (cancelletto) | Precede un commento del codice sorgente: i caratteri che seguono il cancelletto fino alla fine della riga vengono ignorati dall'interprete Bash |
$ (dollaro) | Precede il nome di una variabile |
; (punto e virgola) | Indica la conclusione di un'istruzione per separarla dalla successiva, se sono riportate sulla stessa riga |
' (apice) | Delimita le stringhe di caratteri costanti, senza consentire alla shell di interpretare eventuali variabili contenute nella stringa |
" (doppi apici) | Delimita le stringhe di caratteri, consentendo alla shell di interpretare e sostituire nella stringa i valori di eventuali variabili in essa contenute |
` (backtick) | Delimita un comando consentendo alla shell di sostituire il comando con l'output da esso prodotto; spesso si usa la forma $(comando) al posto del backtick `comando` |
Ne parleremo più estesamente nelle pagine seguenti, ma per comprendere meglio anche solo i primi esempi, è bene precisare che, come in ogni altro linguaggio di programmazione, anche la Bash possiede il concetto di variabile di memoria. Mediante le variabili possono essere identificate facilmente delle aree della memoria della macchina entro cui memorizzare temporaneamente un'informazione numerica o alfanumerica (un numero o una stringa di caratteri). Per definire una variabile basta assegnarle un valore, come nell'esempio seguente:
$ a=Ciao
$ echo a
a
$ echo $a
Ciao
Dopo aver assegnato un valore ad una certa variabile, per fare riferimento a tale variabile in altri comandi della shell, bisogna anteporre al nome il simbolo “$”: ad esempio, per riferirci al valore della variabile a si utilizza il simbolo “$a”. Quindi, è chiaro che nell'assegnazione di un valore ad una variabile mediante l'operatore “=”, il carattere “$” deve essere omesso: scrivere “$a=3” è sbagliato, mentre l'espressione corretta è “a=3”; analogamente, se si vuole assegnare alla variabile a lo stesso valore della variabile b, allora si dovrà scrivere “a=$b” e non “a=b”. Quest'ultima espressione, infatti, produce l'assegnazione del carattere “b” alla variabile a.
Comunemente nei linguaggi di programmazione gli apici e i doppi apici (le “virgolette”) sono utilizzati per delimitare le stringhe e l'uso dell'uno o dell'altro carattere dipendono dalla sintassi adottata da un particolare linguaggio. Ad esempio in C gli apici sono utilizzati per delimitare singoli caratteri, mentre i doppi apici sono utilizzati per delimitare le stringhe.
Nei linguaggi di scripting l'uso degli apici, delle virgolette e del backtick ha un significato differente e la Bash in questo non fa eccezione.
Gli apici singoli sono utilizzati per delimitare le stringhe di caratteri. L'interprete Bash non entra nel merito del contenuto della stringa e si limita ad utilizzare la sequenza di caratteri delimitata dagli apici. In questo modo possono far parte della stringa anche caratteri che altrimenti assumerebbero un diverso significato. L'unico carattere che non può essere utilizzato all'interno di una stringa delimitata da apici sono gli stessi apici; per definire una stringa che contiene gli apici, è necessario delimitarla con le virgolette.
Anche i doppi apici (virgolette) sono utilizzati per delimitare le stringhe; tuttavia, se la stringa è delimitata da questo carattere, l'interprete Bash esegue quella che in gergo è chiamata “interpolazione” della stringa e risolve il valore di eventuali variabili riportate nella stringa stessa. In pratica, se in una stringa delimitata da doppi apici è presente il riferimento ad una variabile (es.: $a) allora nella stringa al nome della variabile viene sostituito il suo valore.
Per stampare i caratteri (come i doppi apici o il dollaro) che altrimenti verrebbero interpretati ed assumerebbero un altro significato, bisogna anteporre a ciascuno di essi il carattere backslash “\”. Per stampare il carattere backslash in una stringa delimitata da doppi apici bisogna riportare di seguito due backslash.
$ nome='Marco'
$ echo 'Ciao $nome.'
Ciao $nome.
$ echo "Ciao $nome."
Ciao Marco.
$ echo "Ciao \"\$nome\"."
Ciao "$nome".
Il carattere backtick ha il comportamento più particolare, tipico dei linguaggi di scripting e assente invece nei principali linguaggi di programmazione di alto livello. Il backtick consente di delimitare una stringa che viene interpretata dalla Bash come un comando da eseguire, restituendo come valore l'output del comando stesso prodotto sul canale standard output.
Nell'esempio seguente viene utilizzato il comando date che restituisce in output la data corrente. L'output di questo comando è una stringa, che usando il backtick, può essere assegnata ad una variabile per poterla poi riutilizzare in seguito.
1 #!/bin/bash
2 data=`date +%d/%m/%Y`
3 echo "Oggi e' il $data."
4 saluto=`echo "ciao"`
5 echo $saluto
Eseguendo lo script (memorizzato nel file “data.sh”) si ottiene il seguente output:
$ ./data.sh
Oggi e' il 20/06/2011.
ciao
Il backtick viene interpretato anche se è presente all'interno di una stringa delimitata da doppi apici. Ad esempio il seguente comando produce un risultato analogo a quello dello script precedente:
$ echo "Oggi e' il `date +%d/%m/%Y`."
Oggi e' il 20/06/2011.
Il backtick può essere sostituito dalla notazione sintattica “$(comando)” che ha lo stesso comportamento e produce gli stessi risultati. L'esempio precedente può essere modificato come segue:
$ echo "Oggi e' il $(date +%d/%m/%Y)."
Oggi e' il 20/06/2011.
Comandi interni, esterni e composti
Variabili, varibili d'ambiente e variabili speciali