...

PDF a 4 pagine per foglio - Dipartimento di Scienze Ambientali

by user

on
Category: Documents
16

views

Report

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
Fly UP