PDF a 4 pagine per foglio - Dipartimento di Scienze Ambientali
by user
Comments
Transcript
PDF a 4 pagine per foglio - Dipartimento di Scienze Ambientali
Cos’è una shell Shell significa conchiglia: racchiude il kernel ed è la superficie con cui l’utente entra in contatto quando vuole interagire con il sistema. La shell di Unix Fondamentalmente una shell è un interprete di comandi, che fornisce all’utente un’interfaccia verso un ricco insieme di utility e un linguaggio di programmazione per “combinare” queste utility. Uso interattivo e scripting Paolo Baldan [email protected] Si distinguono due tipi di funzionamento: Dipartimento di Informatica Università di Venezia interattivo: i comandi sono digitati da tastiera dall’utente non interattivo: i comandi sono contenuti in file, detti script, che una volta definiti possono essere utilizzati in modo (quasi) indistingubile rispetto ai comandi ordinari (es. quelli in /bin/) e permettendo così di personalizzare l’ambiente di lavoro. La shell di Unix – p.1/196 La shell di Unix – p.3/196 Alcune caratteristiche della shell Permette un controllo dell’input/output dei comandi (tramite i costrutti di ridirezione) e del loro ambiente di esecuzione. La shell bash fornisce un piccolo insieme di comandi, detti builtin che implementano funzionalità altrimenti difficili da realizzare (es. history, getopts, kill, e pwd) o addirittura impossibili (es. cd, break, continue, e exec). fornisce un linguaggio di programmazione, con variabili, funzioni e costrutti per il controllo del flusso. Nel tempo, le shell sono state aricchite con caratteristiche orientate più verso l’uso interattivo che verso la programmazione. Alcune di tali caratteristiche sono il controllo dei job, l’editor da linea di comando, history ed alias. La shell di Unix – p.2/196 La shell di Unix – p.4/196 Operazioni della shell Bash 1. Legge l’input (dalla tastiera del terminale utente, da una stringa passata come argomento (opzione -c) o da un file di script). Normale programma eseguibile bash (tipic. nella directory /bin/). bash [<opzioni>] [<file-script>] [<argomenti>] 2. Scompone l’input in parole ed operatori, rispettando le regole di quoting. In questo passo viene anche effettuata l’espansione degli alias. 3. Esegue vari tipi di espansioni (es. espansione di percorso o espansione della storia). 4. Esegue ogni redirezione necessaria e rimuove gli operatori di redirezione ed i loro operandi dalla lista degli argomenti. Non interattiva: se <file-script> è presente bash tenta di eseguirlo come uno script. Interattiva: La shell interagisce con l’utente, mostra un prompt, ovvero “invito” a inserire dei comandi (modalità forzata con l’opzione -i). Shell interattiva di login Shell interattiva normale 5. Esegue il comando. 6. Aspetta che l’esecuzione termini e quindi ne rileva lo stato. La distinzione influenza la scelta dei file di configurazione letti. La shell di Unix – p.5/196 Tipi di shell La shell di Unix – p.7/196 Shell interattiva di login Esistono un certo numero di shell, tra cui . . . - Bourne shell (sh) - Korn shell (ksh) - C shell (csh ed il suo successore tcsh) - Bourne again shell (bash) Una shell di login è quella che si ha di fronte quando è stata completata la procedura di login (può essere esplicitamente avviata con l’opzione -login). Le varie shell presentano numerosi aspetti comuni. Differiscono per la sintassi e per alcune caratteristiche e funzionalità più sostanziali. Ci concentreremo su bash, la shell (quasi) standard in ambiente Linux. Il nome è un acronimo di Bourne-Again SHell, un riconoscimento a Steve Bourne, autore di un diretto antenato della shell UNIX originaria /bin/sh. se non è stata specificata l’opzione -noprofile, tenta di leggere ed eseguire il contenuto dei file di configurazione: /etc/profile ˜/.bash profile oppure ˜/.bash login oppure ˜/.profile (in questo ordine). Al termine della sessione di lavoro, se il file esiste, legge ed esegue il contenuto di ˜/.bash logout. Bash è una shell sh-compatibile, che ne migliora aspetti interattivi e programmativi. La shell di Unix – p.6/196 La shell di Unix – p.8/196 Shell interattiva di login Shell non interattiva I file di configurazione letti da una shell di login vengono solitamente utilizzati per Esegue un file di script bash [<opzioni>] script arg1 ...argn stabilire il path (le directory in cui cercare i comandi) definire funzioni definire il valore delle variabili di interesse (di ambiente e non); Parametri posizionali: $1, $2, $3, ecc. usati nello script per indicare gli argomenti. Il parametro $0 indica il nome dello script. Esempio: echo "Script $0" echo "Primo parametro $1" echo "Secondo parametro $2" stabilire il tipo e i parametri del terminale stabilire i permessi di default sui file (con umask) La shell esegue i comandi dello script e quindi termina l’esecuzione. La shell di Unix – p.9/196 Shell interattiva normale La shell di Unix – p.11/196 Shell non interattiva / B Se non viene specificata una delle opzioni -norc o -rcfile), legge ed esegue (se esiste) il file ˜/.bashrc. Collegamenti tra file di configurazione: spesso è opportuno leggere ˜/.bashrc anche per shell di login. A questo fine all’interno del file ˜/.bash profile si inserisce ... if [ -f ˜/.bashrc ]; then source ˜/.bashrc fi ... Se dopo le opzioni ci sono altri argomenti, il primo di questi viene interpretato come il nome di uno script di shell (a meno che non siano state usate le opzioni ...) -c <stringa>: vengono eseguiti i comandi contenuti nella stringa. Gli argomenti successivi sono associati ai parametri posizionali a partire da $0. -s: la shell legge i comandi dallo standard input. All’avvio in modalità non interattiva, Bash esegue il file nella variabile di ambiente BASH ENV (se non vuota). Il significato è semplice: viene controllata l’esistenza del file ˜/.bashrc e, se presente, viene caricato ed eseguito. La shell di Unix – p.10/196 In pratica, BASH ENV indica un file di configurazione da eseguire prima dello script. Tipicamente BASH ENV è vuota, oppure non esiste affatto. La shell di Unix – p.12/196 Conclusione Alias La conclusione del funzionamento della shell, quando si trova in modalità interattiva, si ottiene normalmente attraverso il comando interno exit, oppure eventualmente con il comando interno logout se si tratta di una shell di login. La shell Bash attraverso i comandi interni alias e unalias offre la possibilità di definire ed eliminare alias, ovvero nomi che rappresentano comandi, possibilmente composti e con opzioni alias <nome>=<comando> unalias <nome> Se invece si tratta di una shell avviata per interpretare uno script, questa termina automaticamente alla conclusione dello script stesso. prima di eseguire un comando di qualunque tipo, la shell cerca la prima parola del comando all’interno dell’elenco degli alias; se la trova, la sostituisce con il suo alias. Attenzione: prima e dopo del segno “=” non compaiono spazi. il nome di un alias non può contenere il simbolo =. La shell di Unix – p.13/196 La shell di Unix – p.15/196 Alias / B Possono essere utilizzati per associare un nome mnemonico ad un comando Personalizzazione di Bash alias cerca=grep alias trovatex=’find . -name *.tex’ correggere errori di sintassi comuni alias emcas=emacs come shortcut per riferire comandi molto lunghi alias cdtex=‘cd /home/rossi/projects/LaTeX/sources‘ possono contenere caratteri speciali alias printall=‘lpr *.ps‘ La shell di Unix – p.14/196 La shell di Unix – p.16/196 Alias / C Alias / E Si può effettuare l’alias di un alias: Bash, quando espande un alias considera la prima parola del testo di rimpiazzo e verifica se sono possibili ulteriori espansioni. alias printall=‘lpr *.ps‘ A differenza della shell C, Bash non consente di definire alias con argomenti. Se necessario, bisogna utilizzare le funzioni (che vedremo nel seguito). In generale, l’utilizzo di alias è superato dall’uso delle funzioni. alias pa=printall Per evitare cicli infiniti, se la prima parola del testo di rimpiazzo coincide con un alias già espanso, non si prosegue ulteriormente nell’espansione. Ad esempio è legale: Come accade per altri meccanismi di personalizzazione della shell, gli alias sono tipicamente definiti nei file di configurazione (.bashrc). Per conoscere la lista degli alias attualmente validi Bash fornisce il comando alias: alias ls=‘ls -F‘ Ridefinire comandi esistenti, aggiungendo opzioni desiderate come default, è uno degli usi più comuni degli alias: alias rm=‘rm -i‘ La shell di Unix – p.17/196 /home/rossi$ alias alias c=’clear’ alias cd=’cd ’ alias cp=’cp -i’ ... La shell di Unix – p.19/196 Opzioni Alias / D Nota: Se l’ultimo carattere del testo di rimpiazzo dell’alias è uno spazio o una tabulazione, allora anche la parola successiva viene controllata per una possibile sostituzione attraverso alias. Utile per l’aliasing delle directory. Es. Le opzioni sono una sorta di “switch”, con valore booleano, che influenzano il comportamento della shell. set +o <option> set -o <option> /home/rossi$ alias mydir=/home/rossi/projects/LaTeX/ /home/rossi$ cd mydir bash: cd: mydir: File o directory inesistente /home/rossi$ alias cd=‘cd ‘ /home/rossi$ cd mydir /home/rossi/projects/LaTeX$ disattiva l’opzione attiva l’opzione Attenzione: l’uso di + e - è controintuitivo. Quasi ogni opzione ha una forma breve. Ad es. set -o noglob equivale a set -f Gli alias non vengono espansi quando la shell funziona in modalità non interattiva; di conseguenza, non sono disponibili durante l’esecuzione di uno script. La shell di Unix – p.18/196 la lista delle opzioni e dei relativi valori si ottiene con set -o La shell di Unix – p.20/196 Opzioni / A Variabili di shell / B Una variabile si dice definita quando contiene un valore, possibilmente la stringa vuota. Può essere cancellata con Alcune opzioni comuni ed il relativo significato Opzione Significato emacs emacs editing mode ignoreeof non permette il logout con Ctrl-D noglob non espande i metacaratteri, come * e ? nounset dà errore se si usa una variabile non def. unset <varname> Per riferire il valore di una variabile si utilizza la notazione $<varname> oppure ${<varname>} Alcuni esempi Y = terra echo $Y X = $Y X = Y Z = unset Y assegna valore ‘terra‘ alla variabile Y stampa il valore di Y assegna il valore di Y a X assegna il valore ’Y’ a X assegna la stringa vuota a Z cancella Y La shell di Unix – p.21/196 Variabili di shell La shell di Unix – p.23/196 Variabili di shell / C Alcuni aspetti rilevanti dell’ambiente di esecuzione della shell non sono caratterizzabili semplicemente con valori on/off come le opzioni. Aspetti di questo tipo sono specificati tramite variabili Alcuni operatori permettono di verificare se una variabile esiste e operare di conseguenza ${<varname>:-<val>} una variabile è un nome al quale è associato un valore il nome è una stringa alfanumerica (contiene lettere, cifre e underscore, ma il primo carattere non può essere una cifra) il valore è una stringa di caratteri (vedremo che Bash supporta anche altri tipi ...) Per assegnare un valore ad una variabile Se <varname> esiste ed ha valore non vuoto, ritorna il suo valore, altrimenti ritorna <val> ${<varname>:=<val>} Se <varname> esiste ed ha valore non vuoto, ritorna il suo valore, altrimenti assegna <val> a <varname> e ritorna <val>. ${<varname>:?<message>} <varname>= [<value>] Se la variabile <varname> non esiste allora viene creata, altrimenti il suo valore precedente viene sovrascritto. La shell di Unix – p.22/196 Se <varname> esiste ed ha valore non vuoto, ritorna il suo valore, altrimenti stampa il nome della variabile seguito dal messaggio <message>. La shell di Unix – p.24/196 Variabili di shell - builtin Variabili di shell - builtin / B Alcune variabili sono assegnate automaticamente da Bash stessa. Tipicamente hanno nome costituito da lettere maiuscole. Ecco alcuni esempi: SHELL (csh) - Il pathname completo della shell di login. RANDOM (ksh) - Un numero intero casuale. SECONDS (ksh) - Il numero di secondi trascorsi dall’avvio della shell. PWD (ksh) - La directory corrente. OLDPWD (ksh) - La directory corrente visitata precedentemente. HISTCMD (bash) - L’indice nel registro storico dei comandi. UID (bash) - Lo UID dell’utente. GROUPS (bash) - Un array contenente i numeri GID di cui l’utente è membro. HOME - La propria home directory HOSTTYPE (bash) - Il nome del tipo di elaboratore. OSTYPE (bash) - Il nome del sistema operativo. MACHTYPE (bash) - Architettura e sistema operativo utilizzato. BASH_VERSION (bash) - Il numero di versione di Bash. BASH (bash) - Il percorso completo della copia corrente dell’eseguibile bash. SHLVL (bash) - Il livello di annidamento dell’eseguibile bash. Search path: Alcune variabili specificano i cammini nel file system che la shell deve seguire per cercare comandi e directory. PATH : dove cercare il comando da eseguire La variabile PATH è normalmente già definita ed ha un valore di default. Tipicamente si vorrà solo aggiungere qualche cammino di ricerca PATH = $PATH":˜/bin/" CDPATH : dove cercare la nuova working directory quando si esegue il comando cd <dirname>. Tipicamente conterrà le directory che si accedono più spesso. Ad es.: CDPATH=˜/:˜/projects La shell di Unix – p.25/196 Variabili di shell - builtin / A Variabili di ambiente Editing mode: Alcune variabili sono legate alle funzionalità di editing della riga di comando. HISTFILE HISTSIZE il file dove salvare la storia (default ˜/.bash_history) massimo numero di comandi nella storia Variabili di prompt: in particolare PS1 influenza il cosiddetto prompt primario, il prompt ordinario della shell interattiva. Alcune stringhe hanno un significato particolare \u \s \v \w \h La shell di Unix – p.27/196 nome dell’utente nome della shell versione della shell la working directory hostname Le variabili di shell sono proprie dell’ambiente locale della shell stessa e quindi non sono, normalmente, visibili a programmi / sottoshell avviate dalla shell stessa. Una classe speciale di variabili, dette variabili di ambiente sono invece visibili anche ai sottoprocessi. Una qualsiasi variabile può essere resa una variabile di ambiente “esportandola” export <varnames> dove <varnames> è una lista di nomi di variabili separati da spazi. Una variabile può essere definita ed esportata contemporaneamente con la sintassi Es. con PS1=\u@\h:\w\$, si otterrà un prompt del tipo: rossi@ihoh:/home/rossi$ export <varname> = <value> La shell di Unix – p.26/196 La shell di Unix – p.28/196 Script Variabili di ambiente / A Alcune variabili builtin, come HOME, PATH e PWD sono variabili di ambiente “per default”. Uno script è un programma di shell: file di testo che contiene comandi di shell (più in generale comandi per un interprete). È anche possibile definire variabili nell’ambiente di un particolare comando (sottoprocesso) facendo precedere il comando dalla definizione delle variabili Per eseguire un file di script scriptname lo si fornisce come argomento all’interprete bash scriptname <args> <varnames> = <value> <command> Digitando semplicemente export si ottiene una lista delle variabili esportate dalla shell. Si noti che le definizioni in .bashrc (file di ambiente) sono valide in ogni sottoshell interattiva. Alternativamente si può indicare l’interprete nella prima riga dello script stesso: se contiene solo un simbolo # lo script è intepretato dalla shell da cui è lanciato se contiene #!pathname lo script è intepretato dalla shell identificata da pathname. Es: #!/bin/bash La shell di Unix – p.29/196 La shell di Unix – p.31/196 Script / A Nel secondo caso, se il file di script è eseguibile (chmod permette di dare i diritti di esecuzione) e raggiungibile nei percorsi della variabile PATH può essere eseguito come un normale comando Script e funzioni (Intro) scriptname <args> Nota: Il programma che esegue uno script non è necessariamente una shell. Ad esempio, uno script awk può iniziare con #!/bin/awk Ma sono legittimi anche #!/bin/cat oppure #!/bin/rm La shell di Unix – p.30/196 La shell di Unix – p.32/196 Parametri posizionali Uso dei parametri - esempi La shell prevede alcune variabili builtin, i parametri posizionali e speciali, che risultano utili per lo scripting. I parametri posizionali rappresentano gli argomenti dello script: $n oppure ${n} valore dell’n-mo argomento Se n è costituito da più di una cifra deve essere racchiuso tra graffe (es. ${10}). I parametri sono assegnati dalla shell e possono essere solo letti. Esempio (1) #!/bin/bash echo Ho un parametro che vale $1 echo ed un secondo che vale $2 exit 1 Script che conta i propri argomenti (2): #!/bin/bash echo Sono lo script $0 echo Mi sono stati passati $# argomenti echo Eccoli: $@ Esempio (anticipato) #!/bin/bash while true do if newer $1.dvi $1.ps; then dvips $1 -o fi sleep 2s done La shell di Unix – p.33/196 Parametri speciali (alcuni) Funzioni $0 Nome della shell o dello script. $* Insieme di tutti i parametri posizionali a partire dal primo. Tra apici doppi, rappresenta un’unica parola composta dal contenuto dei parametri posizionali. $@ La shell di Unix – p.35/196 Bash offre la possibilità di definire funzioni, ovvero di associare un nome ad un programma di shell, che viene mantenuto in memoria e può essere richiamato come un comando interno. [function] <nome> () { <lista-di-comandi> } Insieme di tutti i parametri posizionali a partire dal primo. Tra apici doppi rappresenta una serie di parole, ognuna composta dal contenuto del rispettivo parametro posizionale. Quindi "$@" equivale a "$1" "$2"... . Le funzioni sono eseguite nella shell corrente (e non in una sottoshell come gli script). $# numero di parametri posizionali. Parametri posizionali e speciali sono utilizzabili come negli script. $$ PID della shell. La shell di Unix – p.34/196 La shell di Unix – p.36/196 Funzioni - esempi Espansione e quoting Per rimuovere l’indentazione di un file Espansione: La shell di UNIX, prima di eseguire una linea di comando, interpreta le variabili ed i simboli speciali, sostituendoli (espandendoli) con quanto “rappresentano”. noindent () { sed -e ’s/ˆ *//’ $1 >${2:-$1.noindent}; } Quoting: Meccanismi di “quoting” permettono di inibire l’espansione e quindi di interpretare in modo “letterale” simboli che altrimenti avrebbero un significato speciale. Al posto degli alias, più potente rmall () { find . -name "$1" -exec rm -i {} \; } Al termine del procedimento di espansione i simboli di quoting sono rimossi in modo che non ne resti traccia ad un eventuale programma che riceva questi dati in forma di argomenti. E molti altri ... La shell di Unix – p.37/196 La shell di Unix – p.39/196 Espansione Bash prevede vari tipi di espansione della linea di comando, che vengono eseguiti nel seguente ordine Espansione e quoting 1. espansione degli alias e della storia 2. espansione delle parentesi graffe (C shell); 3. espansione della tilde (C shell); 4. espansione delle variabili (Korn); 5. sostituzione dei comandi (Bourne e Korn); 6. espansione delle espressioni aritmetiche; 7. suddivisione in parole; 8. espansione di percorso o pathname. La shell di Unix – p.38/196 La shell di Unix – p.40/196 Espansione degli alias e della storia Se la prima parola della linea di comando è un alias, lo espande (ricorsivamente) secondo le regole descritte in precedenza. In particolare l’espansione si applica anche alla parola successiva quando il testo che sostituisce un alias termini con uno spazio. Se un parola della linea di comando inizia con il carattere speciale “!” allora la shell interpreta la parola come un riferimento alla storia Es. !n → n-ma riga di comando !-n → riga di comando corrente -n !! → riga di comando precedente !string → riga di comando più recente che inizi per string Espansione delle parentesi graffe / A Esempio: /home/rossi$ mkdir agenda/{old,new} Crea due sottodirectory old e new della directory agenda. Esempio: /home/rossi$ rm agenda/{old/file.{?,??},new/file.*} rimuove i file nella directory agenda/old con nome file e suffisso costituito da uno o due caratteri, e i file nella directory agenda/new con nome file e suffisso qualsiasi. Nota: Le stringhe che risultano dall’espansione non sono necessariamente nomi di file esistenti (come accade per l’espansione di percorso). La shell di Unix – p.41/196 Espansione delle parentesi graffe La shell di Unix – p.43/196 Espansione della tilde Meccanismo che permette la generazione di stringhe arbitrarie conformi ad un semplice pattern del tipo <prefisso>{<elenco>}<suffisso> dove <elenco> consiste di una lista di elementi separati da virgole. Il risultato è una serie di parole composte tutte dal prefisso e dal suffisso indicati e all’interno uno degli elementi della lista. Ad es. c{er,as}care si espande in cercare cascare. Se una parola inizia con il simbolo tilde (˜) la shell interpreta quanto segue, fino alla prima barra obliqua (/), come uno username e sostituisce questo prefisso con il nome della home directory dell’utente. ˜username → home directory di username ˜/ e ˜ si espandono nella home directory dell’utente attuale, ovvero nel contenuto della variabile HOME. ˜/ , ˜ → propria home directory Esempi: c{{er,as}c,ucin}are si espande in cercare cascare cucinare. /home/rossi/report$ cd ˜ /home/rossi$ /home/rossi$ cd ˜bianchi /home/bianchi$ La shell di Unix – p.42/196 La shell di Unix – p.44/196 Espansione della tilde / A Sostituzione dei comandi Se la stringa che segue la tilde ˜ non è uno username, non avviene l’espansione. Consente di espandere un comando con il suo (standard) output. La sintassi, derivante dalla shell Korn è: $(<comando>) Altre sostituzioni della tilde: La sostituzione dei comandi può essere annidata. (Es. ls $(ls $(ls))) ˜+ → working directory (contenuto di PWD) ˜- → working directory precedente (contenuto di OLDPWD) Esempio: /home/rossi$ ELENCO=$(ls) /home/rossi$ ELENCON=$(ls [0-9]*) Esempio: /home/rossi/report$ cd ˜ /home/rossi$ cd ˜/home/rossi/report$ (oppure cd -) Assegna alla variabile ELENCO l’elenco dei file della directory corrente e ad ELENCON quello dei file il cui nome inizia con un numero (se ce ne sono). La shell di Unix – p.45/196 Espansione delle variabili Sostituzione dei comandi / A Se la shell trova nella linea di comando una parola che inizia per $ $stringa oppure La shell di Unix – p.47/196 Esempio: /home/rossi$ rm $( find / -name "*.tmp" ) ${stringa} allora interpreta stringa come il nome di una variabile e la espande con il suo contenuto. Elimina da tutto il filesystem i file con estensione tmp. Una diversa sintassi (derivante dalla Bourne shell) Esempio ‘<comando>‘ /home/rossi$ PARTE="Dani" /home/rossi$ echo $PARTEele Attenzione alla direzione degli apici! /home/rossi$ echo ${PARTE}ele Daniele Nel caso di sostituzioni annidate occorre fare precedere gli apici più interni da un backslash (simbolo di escape). Esistono varie altre forme di sostituzione di variabili (forme ${...}) che vedremo nel seguito (principalmente derivate dalla shell Korn). La shell di Unix – p.46/196 Questa sintassi è considerata obsoleta e mantenuta solo per compatibilità con le vecchie versioni. La shell di Unix – p.48/196 Espansione aritmetica Espansione di percorso (globbing) La shell Bash offre come funzionalità built-in il trattamento di espressioni aritmetiche intere (vedi anche variabili intere). Se una parola contiene uno dei simboli speciali ‘*’, ‘?’ e ‘[’, viene interpretata come modello ed espansa con l’elenco, ordinato alfabeticamente, di percorsi (pathname) corrispondenti al modello. Le espressioni aritmetiche si rappresentano come Se il modello non corrisponde ad alcun file la parola resta immutata. $((<espressione>)) (esiste anche una forma alternativa $[<espressione>]) Note: L’espressione è soggetta ad espansione di variabili, sostituzione di comandi ed eliminazione di simboli superflui per il quoting. 1. L’espansione non riguarda i file nascosti, ovvero i file il cui nome inizia con un punto (a meno che il punto non sia indicato espressamente nel modello) La sostituzione aritmetica può essere annidata. 2. L’espansione non genera mai il backslash di separazione dei percorsi. Se l’espressione aritmetica non è valida, si ottiene una segnalazione di errore senza alcuna sostituzione. La shell di Unix – p.49/196 Espansione aritmetica / A La shell di Unix – p.51/196 Espansione di percorso / A Esempi Significato dei metacaratteri /home/rossi$ echo 13+23 13+23 /home/rossi$ echo $((13+23)) 36 /home/rossi$ VALORE=$((13+23)) /home/rossi$ echo $VALORE+1 36+1 * qualsiasi stringa, compresa la stringa vuota ? qualsiasi carattere (uno solo) [a,bc] uno qualsiasi dei caratteri elencati [a-z] uno qualsiasi dei caratteri nell’intervallo [!set] tutti i caratteri non in set Il trattino ‘-’ e la la parentesi quadra chiusa ‘]’ perdono il loro significato speciale se compaiono in un elenco [...] in prima o ultima posizione. Per assegnare il risultato di un’espressione ad una variabile Esempio: let <varname>=<expr> /home/rossi$ rm par[]a] Si possono dichiarare variabili intere rimuove i file par] e para (se presenti), altrimenti . . . . declare -i <varname> La shell di Unix – p.50/196 La shell di Unix – p.52/196 Quoting Apici singoli Il termine quoting deriva dal verbo inglese ‘to quote’ (citare) e fa riferimento ad un meccanismo che inibisce l’espansione, rimuovendo il significato speciale che alcune parole e simboli hanno per la shell. Una stringa racchiusa all’interno di una coppia di apici semplici (’) non è soggetta a nessuna espansione Si distinguono tre meccanismi di quoting, con funzionalità differenti: Attenzione: non confondersi con l’apice inclinato nel modo opposto (‘), utilizzato per la sostituzione dei comandi. carattere di escape (backslash) \ apici semplici ’ ’<text>’ Esempio: doppi apici ", o virgolette. /home/rossi$ A=prova /home/rossi$ echo ’Nessuna espansione di $A oppure *’ Nessuna espansione di $A oppure * /home/rossi$ La shell di Unix – p.53/196 Escape e continuazione La shell di Unix – p.55/196 Apici doppi Il carattere di escape “backslash” indica che il carattere successivo non deve essere visto come carattere speciale. Esempio: /home/rossi$ rm par\* Una forma più lieve di quoting, che inibisce solo l’espansione di percorso, si ottiene racchiudendo il testo tra apici doppi: "<text>" preserva il valore letterale di tutti i caratteri ad eccezione di $, ‘, e \ (questo opera come carattere di escape solo quando è seguito da $, ‘, " e newline). rimuove il file con nome par*, senza espandere il modello. Nota: Se ‘\’ è seguito immediatamente dal codice di interruzione di riga (newline) allora indica che il comando continua sulla riga successiva Attenzione a non lasciare spazi dopo ‘\’, altrimenti questo opererà sullo spazio. La shell di Unix – p.54/196 Permette di definire delle stringhe inserendovi variabili e comandi da sostituire. /home/rossi$ echo "La variabile PWD ha\ > valore $PWD (ovvero $(pwd))" La variabile PWD ha valore /home/rossi (ovvero ˜) /home/rossi$ La shell di Unix – p.56/196 Suddivisione in parole Standard Input, Output, Error Una parola è una sequenza di caratteri che non sia un operatore o un’entità da valutare. È vista come un’entità atomica (es. argomento fornito a un programma). I delimitatori di parole sono contenuti nella variabile IFS (Internal Field Separator), che, contiene, per default, i valori predefiniti: <Spazio><Tab><newline> (ovvero <SP><HT><LF>). Quindi la variabile IFS è molto importante per il funzionamento della shell: non può mancare o essere vuota. La suddivisione in parole non avviene per stringhe delimitate da apici singoli o doppi. Per convenzione ogni programma UNIX comunica seguendo un analogo schema di input/output, che comprende tre canali - riceve l’input dallo standard input (stdin) - manda l’ouput allo standard output (stdout) - segnala gli errori sullo standard error (stderr) Per default la shell associa stdin alla tastiera e stdout, stderr allo schermo del terminale utente. Per esempio, il comando cat, in assenza di argomenti, legge dallo standard input e stampa sullo standard output. Ridirezione e piping permettono di alterare il comportamento standard. La shell di Unix – p.57/196 La shell di Unix – p.59/196 Ridirezione La shell permette di ridirigere stdin, stdout e stderr, connettendoli a generici file. Ridirezione Ogni file aperto è identificato da un descrittore di file. I descrittori standard sono: 0 = standard input; 1 = standard output; 2 = standard error. L’utente può utilizzare altri descrittori di file, a partire da 3 (dalla Bourne shell). La shell di Unix – p.58/196 La shell di Unix – p.60/196 Ridirezione dell’input Ridirezione dell’output - Esempio La sintassi generale è: Il comando command [n]< filename /home/rossi$ ls > dir-content.txt Associa il descrittore n al file filename aperto in lettura. Se n è assente (forma più comune) filename è associato allo standard input (descrittore 0). Crea il file dir.txt nella directory corrente e inserisce in questo l’elenco dei file della directory corrente. Una forma equivalente è /home/rossi$ ls 1> dir.txt Esempio: I comandi In ambedue i casi, se è attiva la modalità noclobber, la lista dei file della directory viene aggiunta a dir.txt. Per forzare comunque la sovrascrittura /home/rossi$ sort < elenco /home/rossi$ sort 0< elenco /home/rossi$ ls 1>| dir.txt Visualizzano il contenuto del file elenco, riordinandone le righe (il comando sort riceve il file da ordinare dallo standard input). Il secondo è equivalente al primo (viene solo indicato esplicitamente il descrittore dello standard input). Il comando /home/rossi$ ls XtgEWSjhy * 2> errori.txt La shell di Unix – p.61/196 La shell di Unix – p.63/196 crea il file errori.txt nella directory corrente e vi inserisce i messaggi di errore generati da ls (ad esempio se il file XtgEWSjhy non esiste). Ridirezione dell’output Ridirezione dell’output “in aggiunta” La sintassi generale è: La sintassi generale è: command [n]>> filename command [n]> filename Associa il descrittore n al file filename aperto in scrittura. Se n è assente (forma più comune) filename è associato allo standard output (descrittore 1). Associa il descrittore n al file filename aperto in scrittura. Se filename esiste già, i dati sono aggiunti in coda. Esempio: /home/rossi$ ls >> dir.txt Se il file da aprire in scrittura esiste già, viene sovrascritto (se invece è attiva la modalità noclobber [comando set] i dati sono aggiunti al file eventualmente esistente.) aggiunge al file dir.txt, il contenuto della directory corrente Per forzare la sovrascrittura di un file, anche se noclobber è attiva, si può utilizzare l’operatore di ridirezione >|. Si può usare, ad esempio, per aggiungere qualcosa di breve ad un file di configurazione La shell di Unix – p.62/196 /home/rossi$ cat >> ˜/.bashrc alias rm=’rm -i’ ˆD La shell di Unix – p.64/196 Ridirezione simultanea Here document Un’ultima forma di ridirezione è il cosiddetto “here document” Bash consente la ridirezione simultanea di standard output e standard error in un unico file: command << word (la prima delle due notazioni è preferita). la shell copia in un buffer il proprio standard input fino alla linea che inizia con la parola word (esclusa) e quindi esegue command utilizzando questi dati copiati come standard input. Non è possibile sfruttare questo meccanismo per appendere informazioni ad un file esistente. usato per fornire l’input “inline” ad un comando all’ interno di uno script. Esempio: Esempio: command &> filename oppure command >& filename /home/rossi$ ls XtgEWSjhy * &> out-err.txt Crea il file out-err.txt e vi inserisce il messaggio di errore causato dall’assenza del file XtgEWSjhy e l’elenco dei file della directory corrente. #!/bin/bash mail $1 << ENDOFMAIL La sua richiesta riguardante $2 e’ stata accetata. Cordiali saluti ENDOFMAIL echo Mail sent to $1 La shell di Unix – p.65/196 La shell di Unix – p.67/196 Ridirezione - esempi /home/rossi$ sort < elenco > elenco ordinato Riordina il contenuto del file elenco e memorizza il risultato nel file elenco ordinato. /home/rossi$ more filename 2> /dev/null Combinare comandi Il file /dev/null è un “buco nero” per i bit. L’effetto è di eliminare qualsiasi messaggio di errore. /home/rossi$ ls * xyz 1> outfile 2> errfile Ridirige standard output ed error su outfile e errfile rispettivamente. Esistono forme più complesse di ridirezione . . . command n >& m il descrittore di file n diviene una copia del descrittore di output m. La shell di Unix – p.66/196 La shell di Unix – p.68/196 Comando semplice/A Exit status Ogni comando UNIX, al termine dell’esecuzione, restituisce un valore numerico, detto exit status. In caso di terminazione “anomala”: comando non trovato: 127 Tipicamente, un valore di uscita pari a zero è considerato indice di una conclusione regolare del comando, senza errori di alcun genere. Se l’exit status viene utilizzato in un’espressione booleana, si assimila zero a true e ogni altro valore a false. file non eseguibile: 126 comando terminato dal segnale n: 128 + n evento/comando Ctrl-c Ctrl-z #!/bin/bash if mkdir $1 2>/dev/null; then echo "Directory $1 creata" else echo "Errore: Directory $1 non creata" fi kill kill -9 Nome Segnale Numero Segnale SIGINT SIGSTOP SIGTERM SIGKILL 2 19 15 9 La shell di Unix – p.69/196 Comandi semplici La shell di Unix – p.71/196 Pipeline Pipeline Comando semplice [!] [<var assign>] <command> <args> <redirs> <command1> [| <command2> ... ] Esempio: A=1 B=2 myscript pippo > outfile.txt Sequenza (opzionale) di assegnamenti a variabili <var assign>, seguiti da una lista di parole (la prima delle quali <command> è interpretata come nome del comando), da eventuali ridirezioni <redirs> e infine da un carattere di controllo (newline o “;”). L’exit status è quello del comando nel caso di terminazione normale, oppure è stabilito dalla shell . . . Sequenza di comandi (anche uno solo!) separati dal carattere di pipe “|”. Lo standard output di <command1> viene connesso, attraverso una pipe, allo standard input di <command2>, ecc. Ogni comando è eseguito in un processo differente (in una sottoshell). La pipeline termina quando termina ogni comando coinvolto (Nota: la terminazione di un comando può causare la terminazione del precedente che tenti di scrivere su una pipe chiusa). L’exit status è quello dell’ultimo comando della pipeline (o la sua negazione logica se la pipeline è preceduta da !). La shell di Unix – p.70/196 La shell di Unix – p.72/196 Pipeline - esempi Liste: Sequenze non condizionali Utilizzate spesso in connessione con i filtri, come more, grep, sort, sed, awk . . . /home/rossi$ who | tee who.capture | sort <command1>; <command2> La shell esegue i comandi sequenzialmente: prima <command1> ed alla terminazione di questo <command2> (‘;’ sostituisce <newline>). L’exit status è quello di <command2>. Mostra la lista ordinata degli utenti collegati. La lista non ordinata viene scritta nel file who.capture. Esempio: /home/rossi$ latex lucidi.tex ; dvips lucidi.dvi -o $ ps -aux | grep rossi Avvia in sequenza una serie di comandi per la compilazione di un file latex e la generazione del un file postscript corrispondente. Mostra la lista dei processi relativi all’utente rossi. $ echo "Vedo $(($(ls -l | wc -l) - 1)) file" Due frammenti di script equivalenti Mostra il numero dei file nella working directory. ls ; echo "Ciao a tutti" ls echo "Ciao a tutti" $ cat /etc/passwd | awk -F: ’{ print $1 }’ | sort La shell di Unix – p.73/196 if who | grep -q La shell di Unix – p.75/196 nel secondo si sostituisce il punto e virgola con un newline. Lista ordinata degli username in /etc/passswd $1; then echo "Utente $1 connesso"; fi Liste e comandi composti Liste: Una lista è una sequenza di una o più pipeline separata da uno degli operatori ; &, && or ||, e possibilmente terminata da ;, &, o <newline>. Liste: Comando in background Ci torneremo dopo . . . Specificato con: <command> & Una lista può essere raggruppata attraverso parentesi (tonde o graffe) per controllarne l’esecuzione. L’exit status della lista corrisponde a quello dell’ultimo comando eseguito della lista stessa. La shell di Unix – p.74/196 La shell esegue <command> in una sottoshell, senza attenderne la terminazione. L’exit status è 0. La shell di Unix – p.76/196 Delimitatori di lista {...} Operatore di controllo && command1 && command2 Esegue <command1>. Quindi <command2> è eseguito se e solo se <command1> ritorna exit status 0 (true). Una lista di comandi <list> può essere raggruppata tramite le parentesi graffe. { <list>; } L’exit status è quello dell’ultimo comando eseguito, ovvero l’AND logico (lazy) degli exit status dei due comandi. Esegue la lista nella shell corrente, senza creare alcuna subshell. L’effetto è dunque semplicemente quello di raggruppare più comandi in un unico comando (blocco). Si usa spesso per eseguire command2 solo se command1 ha terminato il suo compito con successo. Nota: Il ‘;’ finale è necessario, così come lo spazio tra la lista e le parentesi. $ mkdir prova && echo "directory prova creata" Esegue il comando mkdir prova. Se ha successo, esegue il comando successivo che visualizza un messaggio di conferma. L’exit status è quello di <list>. Esempio: /home/rossi$ { date; pwd; } > out scrive in out sia l’output di date che quello di pwd. La shell di Unix – p.77/196 Liste: Operatore di controllo || La shell di Unix – p.79/196 Delimitatori di lista (...) command1 || command2 Esegue <command1>. Quindi <command2> è eseguito se e solo se <command1> ritorna exit status diverso da 0 (false). L’exit status è quello dell’ultimo comando eseguito, ovvero l’OR logico (lazy) degli exit status dei due comandi. Si usa spesso per eseguire command2 solo se command1 non può essere eseguito o riporta qualche tipo di insuccesso. Una lista di comandi può <list> essere racchiusa tra parentesi tonde. ( <list> ) Esegue la lista <list> in una subshell della shell corrente (quindi assegnamenti di variabili e comandi interni che influenzano l’ambiente della shell non lasciano traccia dopo che il comando composto è completato). L’exit status è quello di <list>. Esempi: Esempio: /home/rossi$ mkdir MyDir || mkdir MyDir1 $ (cd Work && { mkdir Dir || mkdir Dir1; }) && echo "Ok" Tenta di creare la directory MyDir e, solo se questa operazione fallisce, tenta di creare MyDir1. La shell di Unix – p.78/196 Tenta di spostarsi nella directory Work e di creare le directory Dir o Dir1. Se ci riesce, visualizza un messaggio di conferma. La shell di Unix – p.80/196 Esecuzione in background / A Esempio: $ yes > /dev/null & echo "yes sta funzionando" Controllo dei job (cenni) Esegue yes in background e visualizza il messaggio. Al termine dell’esecuzione della lista, yes è sempre attivo. Tipicamente sono eseguiti in background job che richiedano tempo di computazione elevato e scarsa interazione con l’utente. $ gzip lucidi.ps & $ sort < file enorme > file enorme ord & La shell di Unix – p.81/196 Esecuzione in background La shell di Unix – p.83/196 Controllo dei job / A Secondo la filosofia multitasking di Unix, la shell permette di eseguire più di un programma contemporaneamente durante una sessione (job). Con la sintassi Il comando interno jobs fornisce la lista dei job della shell corrente: rossi@dsi:˜ > jobs [1] Running [2]- Running [3]+ Running rossi@dsi:˜ > emacs Lez3.tex & emacs script.bash & xdvi Lez3.dvi & command & il comando command viene eseguito in background: il comando è eseguito in una subshell, di cui la shell non attende la terminazione. Quindi passa ad eseguire il comando successivo; l’exit status è sempre zero; stdin non viene connesso alla tastiera (un tentativo di input determina la sospensione del job). La shell di Unix – p.82/196 Il numero tra parentesi quadre è il numero del job all’interno della shell (diverso dal process id PID, che identifica il processo nel sistema! [comando ps]) Il carattere + che segue le parentesi indica il “job corrente” (spostato per ultimo dal foreground al background). Il carattere - indica il penultimo job spostato dal foreground al background. La shell di Unix – p.84/196 Controllo dei job / B Controllo dei job / D Lo stato può essere: Running: in esecuzione. Stopped: sospeso pronto a tornare in azione appena qualcuno lo richieda. Terminated: ucciso da un segnale. Done: terminato con exit status 0. Exit: terminato con exit status diverso da 0. Per fare riferimento ad un job si utilizza il carattere %. %n %<prefisso> job numero n job il cui nome inizia con prefisso (errore se ve n’è più d’uno) %?<stringa> job la cui riga di comando contiene stringa (errore se ve n’è più d’uno) Con jobs -l vengono visualizzati anche i PID dei job rossi@dsiII:/home/rossi/LEZIONI > jobs -l [1] 20647 Running xemacs lez8.tex & [2]- 20650 Running xemacs serve.tex & [3]+ 20662 Running xdvi lez8.dvi & rossi@dsiII:/home/rossi/LEZIONI > %+ (oppure %%) %- job corrente della shell (marcato con +) job marcato con - La shell di Unix – p.85/196 Controllo dei job / C La shell di Unix – p.87/196 Controllo dei job / D Il comando interno kill consente di eliminare un job o un processo (specificato dal suo PID). I job sospesi possono essere gestiti con i comandi interni bg e fg. bg riattiva in background l’esecuzione di un job sospeso. Senza argomenti si riferisce al job corrente (marcato con +). kill [-l] [-signal] { PID | %job }+ L’opzione -l fornisce la lista dei segnali possibili, che possono essere specificati anche con codici numerici. Ad es., SIGKILL è 9. I processi possono proteggersi da tutti i segnali ad esclusione di SIGKILL. fg riporta in foreground l’esecuzione di un job. Senza argomenti si riferisce al job corrente (marcato con +). Con il carattere di controllo Ctrl-z il job in foreground viene sospeso (SIGSTOP). Esempio: rossi@dsiII:˜/LEZIONI > jobs -l [1]- 20647 Running xemacs lez8.tex & [2]+ 20650 Running xemacs serve.tex & rossi@dsiII:˜/LEZIONI > kill -9 %1 [1]- Killed xemacs lez8.tex La shell di Unix – p.86/196 Con il carattere di controllo Ctrl-c il job in foreground viene interrotto (SIGINT). La shell di Unix – p.88/196 Controllo dei job - esempio rossi@dsiII:˜/$ jobs [2] Running [3]- Running [4]+ Running emacs Lezione3.tex & gv Lezione3.ps & ./GenPS.bash Lezione3 & rossi@dsiII:˜/$ fg %2 emacs Lezione3.tex ˆZ [2]+ Stopped emacs Lezione3.tex Operatori su stringhe rossi@dsiII:˜/$ bg [2]+ emacs Lezione3.tex & rossi@dsiII:˜/$ fg emacs Lezione3.tex ˆC rossi@dsiII:˜/$ La shell di Unix – p.89/196 Controllo dei job - altri comandi La shell di Unix – p.91/196 Accesso alle variabili Alcune modalità di accesso alle variabili permettono di verificare se queste sono definite (non vuote) e di specificare valori / azioni di default ps [<opts>] mostra i processi esistenti ed il loro stato nohup <command> esegue il comando rendendolo immune dai segnali HUP (hangup) e TERM (terminate), di modo che il comando non termini alla terminazione della shell. $<var> o ${<var>} ritorna il valore di <var> ${<var>:-<val>} se <var> è vuota ritorna <val> ${<var>:=<val>} se <var> è vuota ritorna <val> e assegna tale valore alla variabile ${<var>:?<mesg>} se <var> è vuota scrive il messaggio <mesg> su stderr ${<var>:+<val>} se <var> non è vuota restituisce il valore <val> wait <pid> Sospende la shell fino alla terminazione del figlio con PID specificato. sleep <n> Attende per un tempo specificato da n. La shell di Unix – p.90/196 La shell di Unix – p.92/196 Sottostringhe Pattern matching È possibile selezionare una sottostringa del valore di una variabile con ${<var>:<offset>} ${<var>:<offset>:<length>} È possibile selezionare parti del valore di una variabile sulla base di un pattern (modello). I pattern possono contenere i caratteri *, ? e [] e sono analoghi a quelli visti per l’espansione di percorso. ${<var>#<pattern>} che ritorna la sottostringa di $<var> che inizia in posizione <offset> (Nota: il primo carattere occupa la posizione 0). Nella seconda forma la sottostringa ha lunghezza <length> caratteri. ${<var>##<pattern>} Se <pattern> occorre all’inizio di $<var>, ritorna la stringa ottenuta eliminando da $<var> la più corta / più lunga occorrenza iniziale di <pattern>. Esempio: ${<var>%<pattern>} /home/rossi$ A=armadillo /home/rossi$ echo ${A:5} illo /home/rossi$ echo ${A:5:2} il ${<var>%%<pattern>} Se <pattern> occorre alla fine di $<var>, ritorna la stringa ottenuta eliminando da $<var> la più corta / più lunga occorrenza finale di <pattern>. La shell di Unix – p.93/196 Lunghezza Pattern matching - Esempio outfile=${infile%.pcx}.gif L’operatore Rimuove (l’eventuale) estensione “.pcx” dal nome del file e aggiunge “.gif”. (Es. trasforma foto.pcx o foto in foto.gif). ${#<var>} consente di ottenere la lunghezza, in caratteri, del valore della variabile <var> (Nota: La lunghezza è comunque una stringa). Esempio: /home/rossi$ /home/rossi$ 9 /home/rossi$ illo /home/rossi$ /home/rossi$ 3 La shell di Unix – p.95/196 A=armadillo echo ${#A} basename=${fullpath##*/} Rimuove la più lunga parte iniziale di fullpath che termini con “/”. In altri termini estrae il nome del file da un path completo. dirname=${fullpath%/*} Rimuove la parte finale più corta di fullpath che inizi con “/”. Ovvero estrae il nome della directory dal path completo di un file. echo ${A:$((${#A}-4))} /home/rossi$ fullpath=/home/rossi/dir/myfile.txt /home/rossi$ echo ${fullpath##*/} myfile.txt /home/rossi$ echo ${fullpath%/*} /home/rossi/dir B=${A:3:3} echo ${#B} La shell di Unix – p.94/196 La shell di Unix – p.96/196 Sostituzione di sottostringhe Esercizio: pushd e popd / A È possibile sostituire occorrenze di un pattern nel valore di una variabile. ${<var>/<pattern>/<string>} ${<var>//<pattern>/<string>} L’occorrenza più lunga di <pattern> in $<var> è sostituita con <string>. Nella prima forma sostituisce solo la prima occorrenza, nella seconda forma tutte le occorrenze. Se <string> è vuota elimina le occorrenze incontrate. Se il primo carattere del pattern è # l’occorrenza deve trovarsi all’inizio della variabile, se è % deve trovarsi alla fine. Se <var> è @ oppure * l’operazione è applicata ad ogni parametro posizionale, e viene ritornata la lista risultante. Lo stack viene implementato come una variabile DIRSTACK che contiene le varie directory, divise da uno spazio. pushd <dir> si sposta nella directory argomento <dir> e la inserisce nello stack. Alla prima chiamata crea lo stack inserendovi anche la directory corrente. pushd () { DIRNAME=${1:?"missing directory name."} cd $DIRNAME && DIRSTACK="$PWD ${DIRSTACK:-$OLDPWD }" echo "$DIRSTACK" } La shell di Unix – p.97/196 Esercizio: pushd e popd La shell di Unix – p.99/196 Esercizio: pushd e popd / B Le funzioni pushd e popd implementano uno stack di directory. popd rimuove la directory dal top dello stack e si sposta nella nuova top directory. pushd <dir> popd cambia la working directory, che diviene <dir>, e la salva sullo stack. estrae la directory sul top dello stack e si sposta nella nuova directory top. Esempio: /home/rossi$ pushd Work/Didat /home/rossi/Work/Didat /home/rossi /home/rossi/Work/Didat$ popd /home/rossi /home/rossi$ Si vogliono implementare questi comandi (builtin in bash) come funzioni. La shell di Unix – p.98/196 popd () { DIRSTACK=${DIRSTACK#* } cd ${DIRSTACK%% *} echo "$PWD" } Questa implementazione ha numerosi problemi (Es. non gestisce o gestisce male errori come directory non esistente, stack vuoto, . . . , non consente di trattare directory che contengano uno spazio, implementa solo alcune funzionalità di pushd e popd, . . . ). La shell di Unix – p.100/196 Strutture di Controllo / A Le strutture di controllo offerte da bash sono: if-then-else Esegue una lista di istruzioni se una condizione è / non è vera. Controllo del Flusso for Ripete una lista di istruzioni un numero prefissato di volte. while, until Ripete una lista di istruzioni fino a che una data condizione diviene falsa / vera. case Esegue una lista di istruzioni scelta in base al valore di una variabile. select Permette all’utente di scegliere una tra le possibilità in una lista. La shell di Unix – p.101/196 Strutture di Controllo La shell di Unix – p.103/196 Costrutto if Le strutture di controllo permettono al programmatore di specificare che certe porzioni di codice devono essere eseguite o meno, oppure devono essere eseguite ripetutamente, concordemente al verificarsi di certe condizioni (riguardanti, ad esempio, il valore delle variabili). Bash offre le strutture di controllo tipiche dei linguaggi di programmazione ad alto livello (imperativi). Sono particolarmente utili per la preparazione di script di shell, ma possono essere usate anche nella riga di comando di una shell interattiva. Il costrutto if permette di eseguire liste di comandi differenti, in funzione di condizioni, espresse anch’esse in forma di liste di comandi. La sintassi è la seguente: if <condition>; then <command-list> [elif <condition>; then <command-list>]... [else <command-list>] fi if <condition> then <command-list> [elif <condition> then <command-list>] ... [else <command-list>] fi dove <condition> e <command-list> sono liste di comandi, La shell di Unix – p.102/196 La shell di Unix – p.104/196 Costrutto if / A Costrutto if / C Esempio: Modificare il comando cd in modo che mostri le directory di partenza e di arrivo, e fornisca l’exit status corretto. Esegue la lista di comandi <condition> che segue if. Se l’exit status è 0 (da interpretarsi come vero), esegue la <command-list> che segue then e quindi termina. Altrimenti esegue ogni elif in sequenza, fino a trovarne uno la cui condizione è verificata. Se nessuna condizione si verifica, esegue la <command-list> che segue else, qualora esista. L’exit status è quello dell’ultimo comando eseguito (0 se non ne è stato eseguito alcuno). La shell di Unix – p.105/196 Costrutto if / B Dato che un exit status pari a 0 indica generalmente una conclusione regolare del comando, un uso tipico del costrutto condizionale è if <esecuzione regolare del comando>; then <elaborazione normale> else <gestione dell’errore> fi Esempio: pushd con gestione (parziale) di directory non corrette function pushd () { DIRNAME=${1:?"missing directory name."} if cd $DIRNAME; then DIRSTACK="$DIRNAME ${DIRSTACK:-$OLDPWD }" echo "$DIRSTACK" else echo "Error. Still in $PWD" La shell di Unix – p.106/196 fi } function cd () { builtin cd "$@" es=$? # in es l’exit status di cd echo "$OLDPWD --> $PWD" return $es } return n consente ad una funzione di terminare esplicitamente con exit status n (in assenza di return la funzione ritorna l’exit status dell’ultimo comando eseguito). builtin permette di richiedere esplicitamente l’esecuzione del comando cd builtin in bash. La shell di Unix – p.107/196 Condizione - combinare exit status Gli operatori &&, || e ! possono essere utilizzati come operatori logici (and, or, e negazione) per combinare exit status. Verifica se un dato file contiene una tra due parole date. file=$1 word1=$2; word2=$3 if grep $word1 $file || grep $word2 $file; then echo "$word1 oppure $word2 sono in $file" fi Per verificare se ambedue le parole sono presenti nel file . . . ... if grep $word1 $file && grep $word2 $file; then echo "$word1 e $word2 sono in $file ... La shell di Unix – p.108/196 Test - Stringhe / B Test La condizione nel costrutto if è l’exit status di un comando (possibilmente composto). Questo non significa che if permetta di verificare solo se un comando conclude normalmente la sue esecuzione. Bash offre un comando interno test test <condition> oppure [ <condition> ] che permette di effettuare vari controlli proprietà dei file (esistenza, tipo, permessi, data) confronti tra stringhe e interi Esempio: Miglioramento di popd() con la gestione dell’errore di stack vuoto. popd () { DIRSTACK=${DIRSTACK#* } if [ -n "$DIRSTACK" ]; then cd ${DIRSTACK%% *} echo "$PWD" else echo "stack empty, still in $PWD." fi } combinare logicamente condizioni. La shell di Unix – p.109/196 Test - Stringhe La shell di Unix – p.111/196 Test - Attributi dei file Bash permette di effettuare numerosi confronti tra stringhe. Ecco alcuni dei più comuni: Alcuni test relativi alle proprietà dei file: -e file file esiste str1 = str2 str1 e str2 sono uguali -d file file esiste ed è una directory str1 != str2 str1 e str2 differiscono -f file file esiste e non è speciale (dir, dev) str1 < str2 str1 è minore di str2 -s file file esiste e non è vuoto str1 > str2 str1 è maggiore di str2 -r, -w, -x file hai diritti di lettura, scrittura, esecuzione su file -n str1 str1 è non nulla (lunghezza > 0) -O file sei l’owner del file -z str1 str1 è nulla (lunghezza = 0) -G file un tuo gruppo è il gruppo di file file1 -nt file2 file1 è più nuovo di file2 file1 -ot file2 file1 è più vecchio di file2 Per verificare l’uguaglianza di stringhe si può utilizzare anche “==”. Nota: Con -nt e -ot si confronta la data di ultima modifica dei file. Gli operatori “<” e “>” si riferiscono all’ordinamento lessicografico. La shell di Unix – p.110/196 La shell di Unix – p.112/196 Test - Stringhe e file: Esempio Test - Esempio Esempio: Realizzare uno script mygunzip che decomprima con gunzip un file argomento, senza richiedere che questo abbia suffisso gz. file=$1 if [ -z "$file" ] || ! [ -e "$file" ]; then echo "Usage: mygunzip filename" exit 1 else # determina il suffisso ext=${file##*.} if ! [ $ext = gz ]; then # e se non e’ "gz" lo aggiunge mv $file $file.gz file=$file.gz fi gunzip $file fi Esempio: Miglioramento di pushd per la gestione della situazione in cui la directory specificata non è accessibile. pushd () { DIRNAME=$1 if [ -n "$DIRNAME" ] && [ \( -d "$DIRNAME" \) -a \( -x "$DIRNAME" \) ]; then cd $DIRNAME DIRSTACK="$DIRNAME ${DIRSTACK:-$OLDPWD }" echo $DIRSTACK else echo "still in $PWD."; return 1 fi } Si noti l’uso di && nella condizione dell’if che evita di verificare proprietà di DIRNAME quando questa sia la stringa vuota. La shell di Unix – p.113/196 Test - Operatori logici Test - Interi Diverse condizioni su stringhe e file possono essere combinate all’interno di un test, tramite gli operatori logici -a (and) -o (or) La shell di Unix – p.115/196 ! (not) All’interno di una condizione (test . . . oppure [ ...]) la sintassi è: Bash consente di effettuare test su stringhe interpretate come valori interi: -lt minore -gt maggiore -le minore o uguale -ge maggiore o uguale -eq uguale -ne diverso \( expr1 \) -a \( expr1 \) Si noti che danno esito diverso dai confronti tra stringhe. Ad es. vale [ 6 > 57 ] ma non vale [ 6 -gt 57 ]. \( expr1 \) -o \( expr1 \) ! expr1 Sono utili quando si devono mescolare test su stringhe e su interi. Per operare solo su interi vi sono test più efficienti ed eleganti (((<cond>)) che include =, <, >, <=, >=, ==, !=, &&, ||). La shell di Unix – p.114/196 La shell di Unix – p.116/196 Costrutto for Esercizio Permette di eseguire un blocco di istruzioni un numero prefissato di volte. Una variabile, detta variabile di loop, tipicamente riferita nel blocco, assume un valore diverso ad ogni iterazione. Diversamente dal costrutto analogo dei linguaggi convenzionali, non permette di specificare quante iterazioni effettuare, ma una lista di valori assunti dalla variabile di loop. Esercizio: Script rename old new che rinomina tutti i file con suffisso “.<old>" della directory corrente sostituendo il suffisso con ".<new>". #!/bin/bash OLD=$1 NEW=$2 for FILE in *.$OLD; do mv $FILE ${FILE%$OLD}.$NEW done Sintassi: for <var> [ in <list> ]; do <command-list> done Se in <list> è omessa, vale per default "$@", la lista degli argomenti dello script. La shell di Unix – p.117/196 Costrutto for / A La shell di Unix – p.119/196 Esercizio Espande l’elenco <list>, generando una lista di elementi. Esercizio: Implementare uno script che, analogamente a ls -R, mostri ricorsivamente il contenuto delle directory fornite come agomento, evidenziandone, con spaziature, la struttura. Esegue una scansione degli elementi in <list> (separatore: primo carattere in $IFS). Esempio di output Alla variabile var è attribuito, ad ogni iterazione, un valore nella lista e quindi si esegue il blocco <command-list> (che conterrà, tipicamente, riferimenti a questa variabile). L’exit status è quello dell’ultimo comando eseguito all’interno della lista do, oppure 0 se nessun comando è stato eseguito. La shell di Unix – p.118/196 dir1 subdir1 file1 ... subdir2 ... subsubdir1 ... subsubdir2 ... La shell di Unix – p.120/196 Esercizio / A Costrutto case Vediamo in primo luogo uno script analogo a ls -R, senza struttura. tracedir() { for file in "$@"; do echo $file if [ -d "$file" ]; then cd $file tracedir $(command ls) cd .. fi done } Consente di confrontare una stringa con una lista di pattern, e di eseguire quindi conseguentemente diversi blocchi di istruzioni. (simile a switch C, Java o case Pascal) Sintassi: case <expr> in <pattern> ) <command-list> ;; <pattern> ) <command-list> ;; ... Si noti che la funzione è ricorsiva. Inoltre con command ci si assicura che venga eseguito il comando ls e non eventuali funzioni. esac La shell di Unix – p.121/196 Esercizio / B Costrutto case / A Soluzione dell’esercizio: recdir() { singletab="\t" for file in "$@"; do echo -e $tab$file if [ -d "$file" ]; then cd $file tab=$tab$singletab recdir $(command ls) cd .. tab=${tab%"\t"} fi done } La shell di Unix – p.123/196 # con -e, echo interpreta \t come # carattere di escape "<tab>" L’espressione (in genere una variabile) <expr> che segue case viene espansa, e quindi confrontata con ognuno dei <pattern> (stesse regole dell’espansione di percorso) in sequenza (l’ordine è importante). Ogni <pattern> può in realtà comporsi di più pattern separati da | <pattern1> | ...| <patternn> ed è “soddisfatto” se lo è almeno uno tra <pattern1>, . . . , <patternn>. Se viene trovata una corrispondenza con uno dei pattern, la lista di comandi relativa viene eseguita. Quindi si esce dal case. Estensioni: distinguere directory vuote e file, stabilire una profondità massima di ricorsione, raffinare l’output. La shell di Unix – p.122/196 L’exit status è quello dell’ultimo comando del blocco eseguito (0 se nessun pattern combacia). La shell di Unix – p.124/196 Esercizio Esercizio Scrivere una funzione che implementi il comando cd old new: cerca nel pathname della directory corrente la stringa old. Se la trova, la sostituisce con new e cerca di spostarsi nella directory corrispondente. cd() { case "$#" in 0 | 1) builtin cd $1;; 2 ) newdir="${$PWD//$1/$2}" case "$newdir" in $PWD) echo "bash: cd: bad substitution" 1>&2 return 1 ;; * ) builtin cd "$newdir" ;; esac ;; * ) echo "bash: cd: wrong arg count" 1>&2 ; return 1 ;; esac } Realizzare uno script replace <str> [<rep>] [<ext>] che sostituisce ogni occorrenza della stringa <str> con la stringa <rep> in tutti i file della directory corrente con estensione <ext>. Se <rep> è -m#, marca ogni occorrenza della stringa, inserendo all’inizio ed alla fine un carattere #. Se <ext> è assente, opera su tutti i file con suffisso txt. La shell di Unix – p.125/196 Esercizio La shell di Unix – p.127/196 Esercizio Simulazione di un costrutto for dove la variabile assume valori scalari in un range (for i=1 to n; do ... done) #!/bin/bash [ $# -ge 2 ] || { echo "replace.sh: Bad usage"; exit 1; } Esempio: Script concat.sh file i j che concatena i file con nome file-i, file-i+1, . . . , file-j, scrivendo il risultato in file. STR="$1" # prepara le stringhe da usare con sed if [ "$2" = "-m#" ]; then REP="#&#" else REP="$2" fi EXT=${3:-txt} #!/bin/bash # non si gestisccono gli errori ... FILE=$1 INF=$2 # seq i j: produce in output la sequenza SUP=$3 # i, i+1, i+2, ..., j-1, j TMP=/tmp/replace$$.tmp for FILE in *.$EXT; do if [ -f $FILE ]; then sed -re "s/$STR/$REP/g" $FILE > $TMP mv $TMP $FILE fi done for I in $(seq $INF $SUP); do cat $FILE-$I >> $FILE done La shell di Unix – p.126/196 La shell di Unix – p.128/196 Costrutto select Costrutto select / B Permette di generare in modo semplice un menù e quindi di gestire la scelta da tastiera dell’utente (non ha strette analogie con costrutti dei linguaggi convenzionali) Sintassi: select <var> [ in <list> ]; do <command-list> done L’elenco <list> che segue in viene espanso, generando una lista di elementi (se la lista è assente, per default si usa in "$@"). Ogni elemento di tale lista viene proposto sullo standard error, ciascuno preceduto da un numero. Quindi viene mostrato il prompt nella variabile PS3 (per default è #) e chiesto un numero all’utente. Esempio: Una funzione icd che elenca le directory presenti nella directory corrente e, in base alla scelta dell’utente, si sposta in una di queste. icd () { PS3="Scelta? " select dest in $(command ls -aF | grep "/"); do if [ "$dest" ]; then cd $dest echo "bash: icd: Changed to $dest" break else echo "bash: icd: $REPLY wrong choice" fi done } La shell di Unix – p.129/196 Costrutto select / A La shell di Unix – p.131/196 Costrutti while e until Memorizza la scelta nella variabile built-in REPLY e l’elemento corrispondente della lista in <var>. Se l’utente fornisce input vuoto, il menù viene riproposto. Una scelta non valida viene comunque memorizzata in REPLY, mentre a <var> viene assegnata la stringa vuota. Esegue la lista di comandi <command-list> e quindi ripete l’intero processo. Si esce dal costrutto select con il comando builtin break. L’exit status è quello dell’ultimo comando eseguito, oppure 0 se nessun comando viene eseguito. Permettono di ripetere l’esecuzione di un blocco di istruzioni fino al verificarsi / falsificarsi di una condizione (simili ai costrutti dei linguaggi convenzionali). Sintassi: while <condition>; do <command-list> done until <condition>; do <command-list> done Esegue la lista di comandi <command-list> fino a che <condition> è vera (0) / falsa (6= 0). La condizione <condition> è analoga a quella dell’if. L’exit status è quello dell’ultima esecuzione di <command-list>, oppure 0 se non si entra nel ciclo. La shell di Unix – p.130/196 La shell di Unix – p.132/196 Esempio / A Elenca i percorsi indicati nella variabile PATH, uno per riga (si ricordi che in PATH i vari percorsi sono separati da “:”). Opzioni, array, input-output, eval (solo cenni) #!/bin/bash path=${PATH%:} declare -i I=0 path=${path#:} echo "Le directory nel PATH sono:" while [ "$path" ]; do echo " $I) ${path%%:*}" path=${path#*:} I=$I+1 done La shell di Unix – p.133/196 Esempio / B La shell di Unix – p.135/196 Intro Comando trycp <file> <dir>. Tenta di copiare il file <file> nella directory <dir>, e se fallisce riprova fino a riuscirci. Opzioni nella riga di comando #!/bin/bash if [ "$#" -ne 2 ]; then echo "bash: trycp: wrong number of arguments."; exit 1 fi Attributi delle variabili, dichiarazioni file=$1; destdir=$2 gestione dell’I/O (comando read) if [ \( ! -d "$destdir" \) -o \( ! -e "$file" \) ]; then echo "bash: trycp: Usage trycp <file> <dir>."; exit 1 else until cp $file $destdir; do echo "Attempt to copy failed. Waiting ..."; sleep 5 done fi La shell di Unix – p.134/196 Array comando eval La shell di Unix – p.136/196 Opzioni nella linea di comando / B Si può ovviare a questo problema con . . . if [ $1 = -o ]; then elabora opzione -o file1=$2; file2=$3 else file1=$1; file2=$2 fi codice che opera su file1 e file2 Gestioni delle Opzioni Al crescere del numero di opzioni e di argomenti la gestione risulta sempre più involuta, ed è ancora più difficile gestire il caso di un numero arbitrario di argomenti Es.: Provare rename -r <ext> <files>, che aggiunge l’estensione <ext> ai file elencati (in modo ricorsivo sulle sottodirectory con -r). La shell di Unix – p.137/196 Opzioni nella linea di comando La shell di Unix – p.139/196 Comando shift I comandi UNIX e così i nostri script / funzioni possono prevedere delle opzioni (con parametri propri). Il comando builtin shift di Bash permette di gestire più agevolmente le opzioni. command [-options] args shift [n] Le opzioni possono essere gestite con gli strumenti già visti. Ad es., si supponga di voler scrivere uno script con sintassi Senza argomenti shift elimina il parametro posizionale $1 e rinomina $2, $3, $4, . . . in $1, $2, $3 ($0 non è coinvolto) myscript [-o] file1 file2 1 ← $2, Lo schema dello script 2 ← $3, 3 ← $4, ... { shift-try } if [ $1 = -o ]; then elabora opzione -o 1=$2; 2=$3 fi codice che opera su $1 e $2 Esempio: comando myscript [-o] file1 file2 if [ $1 = -o ]; then elabora opzione -o shift fi codice che opera su $1 e $2 Illegale: I parametri posizionali sono read-only! La shell di Unix – p.138/196 La shell di Unix – p.140/196 Esempio / B Comando shift / B #!/bin/bash In generale shift n effettua lo shift di n posizioni: elimina i parametri posizionali $1, . . . , $n e rinomina $n+1, $n+2, . . . in $1, $2, . . . 1 ← $n+1, 2 ← $n+2, 3 ← $n+3, ... Se n è 0, nessun parametro viene modificato. Il valore di n deve essere un numero non negativo, minore o uguale al numero di parametri $#. L’exit status sarà 0 in questo caso e 1 altrimenti. # se $1 e’ del tipo ’-...’ if [ -z ${1##-*} ]; then # opzione e’ numerica ? OPT=${1#-} if [ -n "${OPT//[0-9]/}" ]; then echo "The option must be a number." exit 1 else HEADOPT=$1 shift fi else HEADOPT="-4" # se non e’ specificata l’opzione fi # assegna il default file=${1:?Missing file name.} sort -k2 $file | head $HEADOPT La shell di Unix – p.141/196 Esempio La shell di Unix – p.143/196 Opzioni Multiple: Schema Scrivere uno script Esempio: Si supponga di voler realizzare uno script con opzioni multiple del tipo cheap [-N] file che, dato un file file nel quale ogni riga contiene una coppia <oggetto> <valore>, stampa la lista ordinata degli N oggetti di minor valore in file. In assenza dell’opzione -N stampa i 4 oggetti più economici. myscript [-a][-b][-c] arg Si potrà seguire lo schema: while [ -z "${1##-*}" ]; do case $1 in -a ) elabora l’opzione -a ;; -b ) elabora l’opzione -b ;; -c ) elabora l’opzione -c ;; * ) echo "Usage: myscript [-a][-b][-c] arg" exit 1 ;; esac shift done elaborazione normale su $1 La shell di Unix – p.142/196 La shell di Unix – p.144/196 Opzioni con Argomenti: Schema Comando getopts / A Esempio: Si supponga di voler realizzare uno script con opzioni multiple con argomenti del tipo myscript [-a][-b barg][-c] arg Il comando getopts, con sintassi getopts <opt-string> <var> è tipicamente usato come condizione del ciclo che esamina le opzioni. Si potrà seguire lo schema: while [ -z "${1##-*}" ]; do case $1 in -a ) elabora l’opzione -a ;; -b ) elabora l’opzione -b $2 e’ l’argomento dell’opzione shift;; -c ) elabora l’opzione -c ;; * ) echo "Usage: myscript [-a][-b arg][-c] arg" exit 1 ;; esac shift done Gli argomenti <opt-string>: è una stringa contenente lettere che indicano le opzioni valide e ‘:’. Se una lettera è seguita da da ‘:’ allora l’opzione corrispondente ha un argomento (separato o meno tramite spazi dall’opzione stessa). Es. Per le opzioni [-a] [-b arg] [-c] la stringa sarà "ab:c". <var> è il nome di una variabile che conterrà l’opzione estratta. La shell di Unix – p.145/196 Comando getopts La shell di Unix – p.147/196 Comando getopts / B Gli schemi discussi per la gestione delle opzioni presentano delle limitazioni non gestiscono opzioni multiple raggruppate dopo un solo ‘-’ (ovvero la sintassi -abc per -a -b -c). non gestiscono opzioni con argomenti non separati da uno spazio (ovvero la sintassi -barg per -b arg). Gli schemi si possono complicare per prendere in considerazione questi casi . . . La shell offre un comando builtin getopts per la gestione efficiente e flessibile di opzioni multiple con argomento. La shell di Unix – p.146/196 Funzionamento del comando getopts <opt-string> <var> estrae dalla linea di comando l’opzione (senza ‘-’) “corrente” e la memorizza nella variabile <var>. aggiorna la variabile builtin OPTIND che contiene l’indice del prossimo argomento da elaborare (la variabile OPTIND è inizializzata ad 1 all’invocazione dello script). se l’opzione richiede un argomento, lo memorizza in OPTARG (aggiornando di conseguenza OPTIND). l’exit status è 0 qualora ci siano ancora opzioni e 1 altrimenti. La shell di Unix – p.148/196 Comando getopts - Errori Qualora getopts incontri un’opzione non valida e quando manca l’argomento di un opzione, fornisce un messaggio di errore generalmente poco informativo (es. cmdname: getopts: illegal option -x). Attributi delle variabili È possibile (e consigliato) gestire autonomamente gli errori, chiedendo a getopts un’informazione di errore silenziosa. Questo si ottiene facendo iniziare la stringa delle opzioni <opt-string> con ‘:’ Nel caso di Opzione errata: Assegna ‘?’ a <var> e l’opzione errata a OPTARG. Opzione senza argomento: Assegna ‘:’ ad <var> e l’opzione a OPTARG. La shell di Unix – p.149/196 Comando getopts - Esempio La shell di Unix – p.151/196 Attributi delle variabili Es.: Comando con sintassi opts.sh [-a] [-b argb] [-c] cmdarg. while getopts ":ab:c" opt; do case $opt in a ) echo "Option \"a"\" ;; b ) echo "Option \"b\" with argument \"$OPTARG\"";; c ) echo "Option \"c\"" ;; : ) echo "Missing argument for option \"$OPTARG\"." exit 1 ;; \? ) echo "Wrong option \"$OPTARG\"!" exit 1 ;; esac done shift $(($OPTIND-1)) Finora abbiamo visto variabili il cui valore è una stringa di caratteri (e qualche cenno di aritmetica intera). Il comando Bash declare consente di verificare / assegnare / modificare gli attributi delle variabili. declare [opt] var[=value] Alcune opzioni comuni sono -r var è read-only -x var è esportata -i var è intera -a var è un array Se non si specifica alcuna variabile, declare fornisce la lista delle variabili esistenti, con le proprietà specificate da [opt]. if [ -z "$1" ]; then echo "Missing argument!" else echo "Cmd argument: $1"; fi La shell di Unix – p.150/196 La shell di Unix – p.152/196 Attributi delle variabili / A Variabili e funzioni Le variabili dichiarate all’interno di una funzione sono locali alla funzione stessa. Ad esempio data la funzione readonly: Con declare -r var (equiv. a readonly var) si specifica che alla variabile var si può accedere solo in lettura (il suo valore non può essere modificato). export: Con declare -x var (equiv. a export var) si specifica che var è variabile di ambiente (visibile alle sottoshell). myfun() { declare A=modified ; B=modified; } si avrà /home/rossi$ A=start; B=start /home/rossi$ myfun /home/rossi$ echo $A $B start modified Lo stesso effetto si ottiene con local var (che può essere utilizzato solo all’interno di una funzione). La shell di Unix – p.153/196 Attributi delle variabili / B Array La shell di Unix – p.155/196 Aritmetica intera Altre opzioni del comando declare: Bash permette di valutare espressioni aritmetiche intere $(( <expr> )) nelle quali le variabili possono non essere precedute da ‘$’. declare -f Mostra solo le funzioni e le relative definizioni. Operatori aritmetici comuni declare -F Mostra solo i nomi delle funzioni, senza le relative definizioni. declare -p var Mostra tutti gli attributi della variabile var. La shell di Unix – p.154/196 + somma << e >> - differenza & and (bit a bit) * prodotto | or (bit a bit) / divisione intera e ! not (bit a bit e logico) % resto ˆ x-or (bit a bit) ˜ shift a sx, dx dei bit La shell di Unix – p.156/196 Aritmetica Intera / A Aritmetica intera / C Per assegnare un’espressione intera <expr> ad una variabile intera, dichiarata con declare -i varname, non occorre racchiuderla tra $(( ...)). Es. Operatori relazionali comuni < <= = && maggiore > minore maggiore o uguale >= minore o uguale uguale != diverso and logico || or logico /home/rossi$ declare -i intvar /home/rossi$ intvar=3+2 /home/rossi$ echo $intvar 5 Per valutare espressioni aritmetiche ed assegnarle ad una variabile, Bash offre il costrutto let <var>=<expr> Un’espressione relazionale intera dà come risultato 1 se è vera 0 se è falsa /home/rossi$ let A=3+2 /home/rossi$ echo $A 5 (Attenzione: è la convenzione contraria rispetto agli exit status.) Nota: let non crea variabili intere! La shell di Unix – p.157/196 La shell di Unix – p.159/196 Aritmetica intera - Esempio Aritmetica intera / B Come utilizzare un’espressione relazionale intera come condizione: Tramite gli operatori -lt, -gt, -le, -ge, -eq, -ne all’interno del costrutto test. Ad es., [ 2 -gt 12 ] dà exit status 1 (falso). Si può incapsulare un’espressione relazionale intera in un test: [ $((2 > 12)) = 1 ] Scrivere uno script ndu <dir> che per ogni argomento che sia una directory stampa lo spazio utilizzato, in byte e Kbyte (se > 1Kb e < 1Mb) o Mbyte (se > 1Mb). (Nota: Si pùo specificare un numero in base diversa da 10 con Base#Numero). for dir in ${*:-.}; do if [ -d $dir ]; then result=$(du -s $dir | cut -f 1) let total=result*1024 echo -n "Total for $dir = $total bytes" Il modo più sintetico è quello di utilizzare il costrutto (( ...)). Data un’espressione aritmetica <expr> if ((total >= 16#100000)); then echo " ($((total/16#100000)) Mb)" elif ((total >= 16#400)); then echo " ($((total/16#400)) Kb)" fi ((<expr>)) è un comando che ritorna exit status 0 (vero) se l’espressione è vera (diversa da 0) 1 (falso) se l’espressione è falsa (uguale a 0) fi done La shell di Unix – p.158/196 La shell di Unix – p.160/196 Array Array - assegnamenti Esistono vari modi di assegnare valori agli elementi di un array. I tre gruppi di assegnamenti che seguono producono lo stesso array: Un array è una sequenza di elementi dello stesso tipo Bash supporta array dinamici (di dimensione non specificata a priori), unidimensionali. A[0]=zero; A[2]=due; A[1]=giallo Assegna i valori specificati agli elementi di indice 0,2 e 1. Un array può essere creato con una dichiarazione esplicita A=(zero giallo due) declare -a elenco Assegna gli elementi elencati in sequenza, a partire dall’indice 0. oppure assegnando elementi alle sue componenti. Ad es. elenco[2]=secondo A=([1]=giallo due [0]=zero) Gli array di Bash sono indicizzati da interi, con base a 0 (senza massimo . . . in realtà fino a 599147937791). Quindi l’i+1-mo elemento di un array A sarà riferito come A[i]. Si noti che dopo un assegnamento ad un indice specificato, i successivi proseguono in sequenza, fino a che non viene specificato un nuovo indice. La shell di Unix – p.161/196 Array / A La shell di Unix – p.163/196 Array - riferimenti Ogni elemento di un array può essere (sostanzialmente) visto come una normale variabile. Ad esempio unset A[i] elimina l’elemento i-mo dell’array A. L’intero array viene eliminato con unset A. Gli attributi di un array sono assegnati in modo globale. Ad esempio declare -i A[1] equivale a declare -i A: ogni elemento dell’array diviene una variabile intera. Per riferire il contenuto di una cella di un array A si usa la notazione ${A[Indice]}, dove Indice è una qualsiasi espressione aritmetica che produca un valore non negativo. Riferendo un array A come una normale variabile scalare si ottiene il suo primo elemento A[0]. È possibile espandere contemporaneamente tutti gli elementi (definiti) di un array utilizzando come indice * oppure @. Per ottenere la lunghezza di un array A si possono usare le notazioni: ${#A[*]} La shell di Unix – p.162/196 oppure ${#A[@]} La shell di Unix – p.164/196 Array - esempio Esempio: /home/rossi$ A=(zero giallo due [6]=sei) Input / Output /home/rossi$ echo ${A[1]} giallo /home/rossi$ echo $A zero /home/rossi$ echo ${A[@]} zero giallo due sei /home/rossi$ echo ${#A[*]} 4 La shell di Unix – p.165/196 Array - Esercizio La shell di Unix – p.167/196 Gestione dell’Input/Output Realizzare uno script baseN.sh base num con due argomenti interi, che converte num (interpretato come numero decimale) in base num (<= 10). declare -i base=$1 numero=$2 num=$2 cont=0 declare -ia cifra while ((num)); do cifra[cont]=num%base num=num/base cont=cont+1 done I meccanismi per la gestione dell’I/O offerti da Bash possono essere suddivisi essenzialmente in due classi: Meccanismi di Ridirezione: permettono di gestire e modificare le sorgenti di input e la destinazione dell’output di comandi di shell e utilities. Meccanismi per spostare dati tra file e variabili: Sostituzione di comando $((...)), comandi per la gestione dell’I/O a livello di linee e parole (echo, read). echo -n "Il numero $numero in base $base e’ " cont=${#cifra[*]} while ((cont)); do echo -n ${cifra[cont-1]} cont=cont-1 done Read La shell di Unix – p.166/196 La shell di Unix – p.168/196 Lettura dell’input utente / A Comando echo Il comando echo visualizza sullo standard output i suoi argomenti, separati da uno spazio e terminati da <newline>. echo [-n] [-e] [-E] <args> read -p <string> <var> Stampa (sullo stderr) il prompt <string> prima di leggere la riga dallo stdin. read -a <string> <var> Significato delle opzioni: Opzione -n: sopprime il <newline> finale. Opzione -e (-E): Abilita (disabilita) l’interpretazione delle sequenze di escape. Vede la variabile <var> come un array e memorizza le parole lette sullo stdin, in successione, agli elementi dell’array, partendo da quello di indice 0. read -r <var> \a \c \n \NNN alert bell elimina ogni <newline> line feed (codice di interruzione di riga) carattere ASCII con codice ottale NNN \b \E \t \\ backspace caratteri di escape tab orizzontale Ignora i caratteri di escape, che vengono interpretati letteralmente (‘raw’ input). backslash La shell di Unix – p.169/196 Lettura dell’input utente La shell di Unix – p.171/196 Lettura dell’input utente / B read -n <nchar> <var> Il comando builtin read permette di leggere l’input utente. Legge al più <nchar> caratteri e quindi termina. read <var1> <var2> ...<varn> Legge una riga dallo stdin e la spezza in parole (secondo i separatori in IFS). Assegna tali parole a <var1>, <var2>, . . . in sequenza. Se il numero di parole eccede il numero di variabili, le parole dall’n-ma in poi sono assegnate a <varn>. In particolare read <var> legge una riga dallo stdin nella variabile <var>. read -t <timeout> <var> Attende al più <timeout> secondi il completamento della riga di input (-t sta appunto per ‘timeout’). L’exit status di read è 0, a meno che non si incontri il carattere di fine file o non si verifichi un timeout. Qualora non sia indicata nessuna variabile, per default assegna la linea letta alla variabile REPLY. La shell di Unix – p.170/196 La shell di Unix – p.172/196 Elaborazione per linee Esempio / A Problema: Il codice descritto deve prendere l’input da /etc/usrprompts. Il comando read consente di elaborare l’input linea per linea Creare uno script separato SetPrompt ed utilizzare gli operatori di ridirezione non è una soluzione adeguata. while [ read LINE ]; do elabora LINE stampa la linea elaborata done SetPrompt < /etc/usrprompts In generale uno script del genere ha la stessa struttura e risulta meno efficiente di un programma analogo realizzato in un linguaggio ad alto livello (C, Java). Tuttavia in alcuni casi, se il file da elaborare non è enorme e la massimizzazione dell’efficienza non è fondamentale, può essere utile utilizzare script con questa forma. Lo script è eseguito in una sottoshell e quindi la modifica della variabile PS1 non risulta visibile nella shell corrente. Si può eseguire il codice nella shell corrente con “source” (o “.”) source SetPrompt < /etc/usrprompts Una soluzione alternativa consiste nell’utilizzare una funzione function SetPrompt () { <codice> } richiamata come SetPrompt < /etc/usrprompts La shell di Unix – p.173/196 Esempio Comandi multipli e ridirezione Si supponga che il file /etc/usrprompts contenga, per ogni utente, il prompt desiderato rossi baldan La shell di Unix – p.175/196 \u@\h:\w\$ [\u@\h:\w] Gli operatori di ridirezione possono essere applicati a definizioni di funzione ed a comandi composti. Ridirezione nella definizione della funzione. Si vuole scrivere un programma Bash che assegni alla variabile PS1 il valore corretto (es. porzione di /etc/profile). PS1=’\u@\h’ # valore di default per il prompt MYSELF=$(whoami) while read -r USR PROMPT; do if [ "$USR" = "$MYSELF" ]; then PS1=$PROMPT echo "L’utente $USR ha prompt $PROMPT" break fi done La shell di Unix – p.174/196 function Fun () { <codice> } < file Ad ogni chiamata la funzione Fun prenderà l’input da file. Ridirezione per costrutti di controllo. while [ ... ]; do ... done < file La stessa tecnica si può usare per gli altri costrutti if ... fi, case ... esac, select ... done e until ... done. La shell di Unix – p.176/196 Comandi multipli e ridirezione / A Ridirezione di blocchi comandi Anche per un comando composto { <list> } si possono utilizzare gli operatori di ridirezione già analizzati. Ad esempio con { Dettagli sull’elaborazione della riga di comando ed eval <command list> } < file tutti i comandi eseguiti nel blocco avranno lo standard input ridiretto su file. La shell di Unix – p.177/196 Esempi La shell di Unix – p.179/196 Elab. della linea di comando Le tecniche descritte offrono soluzioni alternative per SetPrompt . . . Per ogni linea di comando la shell: Funzione SetPrompt: function SetPrompt () { <codice> } < /etc/usrprompts 1. Divide il comando in token, separati da metacaratteri (spazio, tab, newline, ‘;’, ‘(’, ‘)’, ‘<’, ‘>’, ‘|’, ‘&’). Ridirezione dell’input per il blocco while 2. Se il primo token è una opening keyword, ad esempio if, while, . . . , function, ‘{’ oppure ‘(’, il comando è composto. La shell effettua operazioni interne opportune, quindi legge il comando successivo e ricomincia il processo di elaborazione. PS1=’\u@\h’; MYSELF=$(whoami) while read -r USER PROMPT; do ... done < /etc/usrprompts 3. Se il primo token è un’alias, lo espande e torna al punto 1 (quindi gli alias possono essere ricorsivi e rappresentare parole chiave). Creazione e ridirezione di un blocco di comandi: { PS1=’u@\h’; MYSELF=$(whoami) while read -r USER PROMPT; do ... done } < /etc/usrprompts 4. Esegue l’espansione delle graffe (es. a{b,c} diventa ab ac). 5. Esegue l’espansione della tilde (il simbolo ‘ ˜ ’ all’inizio di una parola diviene la home directory dell’utente corrispondente). La shell di Unix – p.178/196 La shell di Unix – p.180/196 Funzioni, builtin e comandi Elab. della linea di comando / A Quando una parola è interpretata come comando, la shell cerca prima tra le funzioni, quindi tra i builtin ed infine tra gli eseguibili nel PATH. Per alterare questo ordine: 6. Esegue l’espansione delle variabili ( $var oppure ${...}). 7. Esegue l’espansione dei comandi $(cmd). 8. Esegue l’espansione delle espressioni aritmetiche $((...)). 9. Divide in parole il risultato delle espansioni delle variabili, dei comandi e delle espressioni aritmetiche, utilizzando come separatori i caratteri nella variabile IFS. command <cmd>: Forza la shell a cercare <cmd> tra builtin e comandi. Trascura alias, per side effect (in quanto il comando non è più la prima parola), e funzioni. Esempio: Per ridefinire il comando rm tramite una funzione: 10. Esegue l’espansione di percorso (metacaratteri *, ?, [, ]). 11. Usa la prima parola come comando e ne reperisce il codice, cercando nel seguente ordine: funzioni builtin eseguibili nelle directory indicate in PATH. function rm () { ... command rm ... } builtin <cmd>: Forza la shell a cercare <cmd> solo tra i builtin. 12. Esegue il comando. La shell di Unix – p.181/196 Quoting La shell di Unix – p.183/196 Funzioni, builtin e comandi / A Il comando enable permette di abilitare e di disabilitare i comandi builtin di Bash. È possibile eliminare alcuni dei passi descritti tramite il quoting: 1. Apici singoli ’...’: Eliminano i passi 1–10. La stringa tra apici, immutata, è interpretata come una singola parola. 2. Apici doppi "...": Eliminano i passi 1–4 e 9–10. La stringa tra apici, immutata, è interpretata come una singola parola (elimina l’espansione di alias, tilde, di percorso e la suddivisione in parole, ed esegue l’espansione delle variabili, delle espressioni aritmetiche e la sostituzione di comando). 3. \<char>: inibisce l’interpretazione di <char> come simbolo speciale. La shell di Unix – p.182/196 enable -n <builtin> Disabilita il builtin <builtin> (senza argomenti elenca i builtin disabilitati). enable <builtin> Abilita il builtin <builtin> (senza argomenti elenca i builtin abilitati). enable -a Elenca tutti builtin, indicando quali sono abilitati / disabilitati. La shell di Unix – p.184/196 Esempio: make Comando eval Il comando eval permette di iterare l’elaborazione della linea di comando eseguita dalla shell. L’esecuzione di eval <cmd> consiste quindi di due fasi: 1. La shell elabora la linea di comando (come già spiegato) 2. Esegue eval, ovvero riesegue l’elaborazione della linea di comando, e quindi esegue quanto ottenuto. Molto potente: permette di costruire dinamicamente comandi all’interno di uno script e di eseguirli. Implementazione di un make “primitivo”, in grado di interpretare un singolo costrutto del tipo: target : source1 source2 ... commandlist makecmd () { read target colon sources for src in $sources; do if [ $src -nt $target ]; then while read cmd; do echo "$cmd" eval $cmd done break fi done; } La shell di Unix – p.185/196 La shell di Unix – p.187/196 Esempi /home/rossi$ alias lf=’ls -F’ /home/rossi$ LL=lf /home/rossi$ $LF bash: lf: command not found /home/rossi$ eval $LF Lezione6.ps bash.tex Gestione dei Segnali Script highest <file> [<num>]: mostra le prime <num> righe di <file>, dopo averlo ordinato. In assenza di <num>, mostra tutto il file. if [-n "$2" ]; then sort $1 | head -$2 else sort $1 fi oppure, utilizzando eval eval sort \$1 ${2:+"| head -\$2"} La shell di Unix – p.186/196 La shell di Unix – p.188/196 Gestione dei segnali trap Il comando trap permette di “catturare” e gestire i segnali inviati ad un processo. trap [-p] [<cmd>] [<signal list>] Se giunge uno dei segnali nella lista, esegue il comando <cmd>. I segnali possono essere indicati in modo simbolico o numerico (Es. trap ’echo schiacciato Ctrl-c’ SIGINT). Consigli per il debugging Se l’argomento <cmd> è la stringa nulla trap ’’ <siglist> il segnale (o i segnali) verrà ignorato. Senza argomenti o con l’opzione -p, fornisce la lista di comandi associati con ciascun numero di segnale. La shell di Unix – p.189/196 Gestione dei segnali trap / A La shell di Unix – p.191/196 Opzioni per il debug oppure trap <siglist> Ripristina il gestore di ogni segnale in <siglist> al valore originale (il valore che avevano al momento dell’inizio della shell). trap - <siglist> Per quanto riguarda le sottoshell: I segnali intercettati sono riportati al loro valore originale nelle sottoshell . . . . . . ma i segnali ignorati in una shell sono ignorati anche nelle sottoshell e qui non possono essere successivamente intercettati o inizializzati. La shell di Unix – p.190/196 Alcune opzioni di Bash (comando set [-/+o]) utili per il debug: opzione noexec (-n) Non esegue i comandi, ma ne verifica solo la correttezza sintattica (ad es. utile se lo script è molto complesso, e potrebbe essere pericoloso in presenza di errori). opzione verbose (-v) Stampa ogni comando prima di eseguirlo (ad. es. utile per verificare dove si ferma uno script). La shell di Unix – p.192/196 Opzioni per il debug / A Un debugger per bash opzione xtrace (-x) Mostra il risultato dell’espansione prima di eseguire il comando. $ ls 12 file1 23 file11 $ set -x $ ls $(echo file?) ++echo file1 +ls -sF --color file1 12 file1 Il libro Learning the Bash shell (C. Newham and B. Rosenblatt O’Reilly eds.) presenta un debugger per Bash bashdb non particolarmente sofisticato, ma funzionale (sorgenti nella pagina del corso). Lanciato come bashdb testfile permette di Specificare dei punti di stop nell’esecuzione del programma (breakpoint e break condition). Il carattere ‘+’ nell’output, che indica il livello di espansione, è il contenuto della variabile PS4. Può essere personalizzata . . . $ PS4=’xt ’ $ ls $(echo file?) xxt echo file1 xt ls -sF --color file1 12 file1 Eseguire un numero prestabilito di istruzioni del programma. Esaminare e modificare lo stato del programma durante l’esecuzione. Visualizzare il codice in esecuzione con indicazione dei breakpoint. La shell di Unix – p.193/196 Segnali per il debug La shell di Unix – p.195/196 Un debugger per bash - Comandi Esistono due segnali generati dalla SHELL, che rappresentano eventi runtime di interesse per un debugger (umano o software che sia). bp N: Fissa un breakpoint alla linea N bp: Elenca i breakpoint e le break condition segnale EXIT Generato all’uscita di uno script. bc string: Fissa string come break condition. segnale DEBUG Generato al termine dell’esecuzione di ogni istruzione. Ad es., permette di ispezionare il valore delle variabili in parti critiche del programma: cb: Elimina tutti i breakpoint. bc: Elimina la break condition. function inspect () { echo "La variabile XYZ vale $XYZ" } ... trap inspect DEBUG <parte del codice che da’ problemi> trap - DEBUG cb N: Elimina il breakpoint alla linea N. ds: Mostra lo script, con i numeri di linea ed i breakpoint. g: Inizia/riprende l’esecuzione. s [N]: Esegue N istruzioni (per default 1). x: Attiva/disattiva la traccia. h, ?: help. ! string: Passa string alla shell. q string: Esce. La shell di Unix – p.194/196 La shell di Unix – p.196/196