Ancor prima della possibilità di essere programmata mediante script, la shell già in modalità interattiva offre all'utente una nutrita gamma di strumenti per rispondere alle principali esigenze dell'amministrazione Linux. Di seguito alcune semplici linee guida.
Le variabili
L'utilizzo di variabili nella ahell si dimostra di grande utilità sia per l'acquisizione di informazioni sullo stato del sistema, sia per l'automazione della normale attività amministrativa. Esistono infatti due tipi di variabili:
Variabili d'ambiente e variabili definite dall'utente
La shell nasce dotata di un insieme di variabili predefinite che, proprio per questo, prendono il nome di
variabili d'ambiente. Le variabili d'ambiente vengono costantemente aggiornate per rispecchiano lo stato attuale della shell che, proprio attraverso queste, è quindi in grado di fornire informazioni utili alle applicazioni qui residenti. É possibile ottenere una lista delle variabili d'ambiente eseguendo:
$ env
TZ=UTC-01:00
HISTCONTROL=ignoredups
HOSTNAME=localhost
OLDPWD=/home
USER=root
PWD=/root
HOME=/root
MAIL=/var/spool/mail/root
TERM=linux
SHLVL=2
LOGNAME=root
PATH=/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin
HISTSIZE=1000
CVS_RSH=ssh
LESSOPEN=||/usr/bin/lesspipe.sh %s
_=/bin/env
Di seguito, al contrario, degli esempi di creazione di variabili da parte dell'utente (come nella maggior parte dei linguaggi di programmazione, anche in questo caso i nomi di variabile ammettono esclusivamente caratteri alfabetici, il carattere underscore "_" e numeri, purché non in prima posizione). Si noti in particolare come non sia prevista un'esplicita dichiarazione di variabili: nella shell la tipizzazione è dinamica e la creazione delle variabili è contestuale alla loro assegnazione.
$ s=test_string
$ s="test string"
$ var='test string'
$ n=10
In linea di principio è consentito anche ridefinire anche le variabili d'ambiente assegnando loro nuovi valori, ma non è assicurato che una simile strategia conduca agli obiettivi desiderati. L'esempio seguente dimostra al riguardo che non è possibile provocare un cambio della directory corrente (
cd) semplicemente modificando la relativa variabile d'ambiente:
$ cd /
$ pwd
/
$ echo $PWD
/
PWD=/home
$ echo $PWD
/home
$ pwd
/
É consentita anche la creazione run-time di variabili:
$ read var
_
$ read -p "Please, insert a number: " n
Please, insert a number:
Espansione di variabile
Per accedere al contenuto di una variabile - sia questa d'ambiente o definita dall'utente - la shell prescrive l'uso del costrutto $<
var_name> o ${<
var_name>} come illustrato negli esempi che seguono. Si usa anche dire che l'operatore "$" opera l'
espansione o la
sostituzione di variabile.
$ s=test_string
$ echo ${s}
test_string
$ s1=$s
$ echo $s1
test_string
$ s2=$s"2"
$ echo $s2
test_string2
$ s3=${s}3
$ echo $s3
test_string3
Espansione di comando
Nella shell l'idea dell'espansione si estende anche ad altri contesti. L'esempio seguente mostra come una variabile possa assumere per valore il testo di un'istruzione e come sia poi possibile eseguire quest'ultima attraverso la prima. Si parla in questo caso di
espansione (o
sostituzione)
di comando.
$ cd /home
$ cmd="pwd"
$ $cmd
/home
$ cmd="echo Ok"
$ $cmd
Ok
Di seguito invece una variabile viene valorizzata col risultato di un'istruzione (si noti in questo caso l'utilizzo delle parentesi: in generale quanto racchiuso fra parentesi tonde viene pre-eseguito in una sub-shell; un simile meccanismo è quindi frequentemente utilizzato per imporre un ordine di precedenza nell'esecuzione dei comandi).
$ dt=$(date)
$ echo $dt
Wed Nov 20 14:03:12 UTC 2019
É anche previsto il costrutto:
$ dt=`date`
$ echo $dt
Wed Nov 20 14:03:12 UTC 2019
Espansione numerica
Data la particolare missione della shell, al suo interno anche gli argomenti di per sè numerici, in mancanza di esplicita indicazione contraria, vengono interpretati come testo:
$ n=2+3
$ echo $n
2+3
Fermo il limite per cui la shell non gestisce nativamente i valori decimali (per i quali sono invece necessari programmi esterni quali bs, awk, expr e per i quali si rimanda alle rispettive pagine di manuale), per valorizzare una variabile col risultato di un'espressione numerica è possibile utilizzare in prima istanza l'istruzione
let:
$ let a=2+3
$ echo $a
5
a=2
b=3
$ let c=($a+$b)**2
$echo $c
25
Di seguito gli operatori matematici consentiti
+ |
(addizione) |
- |
(sottrazione) |
* |
(moltiplicazione) |
/ |
(divisione) |
** |
(elevazione a potenza) |
% |
(modulo) |
++<var> |
(pre-incremento; equivale a <var>=<var>+1; <expr>) |
<var>++ |
(post-incremento; equivale a <expr>; <var>=<var>+1) |
--<var> |
(pre-decremento; equivale a <var>=<var>-1; <expr>) |
--<var> |
(post-decremento; equivale a <expr>; <var>=<var>-1) |
<expr1>op=<expr2> |
(forma contratta per <expr1> = <expr1> op <expr2>) |
È anche possibile racchiudere un argomento numerico all'interno di doppie parentesi tonde:
echo $(( (3+2)*(5+1) ))
30
$a=2
$b=3
echo $(( (a+b)*2 ))
10
Eliminazione di variabili
Per eliminare una variabile precedentemente creata dall'utente è possibile utilizzare l'istruzione
unset. Si noti anche che far riferimento ad una variabile non (più) esistente non genererà un errore, ma solo la stringa nulla.
$ k=10
$ unset k
$ echo $k
<null>
Visibilità delle variabili
Di default la visibilità di una variabile definita dall'utente è confinata all'ambito in cui questa è stata creata come mostra l'esempio seguente dove una variabile perde visibilità all'interno di una shell figlia di quella in cui la stessa variabile è stata creata:
$ my_var=3
$ echo $my_var
3
$ bash
$ echo $my_var
<null>
Differente è per le variabili d'ambiente che, al contrario, godono di visibilità globale (essendo tutte le possibili shell discendenti della prima che vede predefinite le variabili d'ambiente)
$ cd /home
$ echo $PWD
/home
$ bash
$ echo $PWD
/home
Il comando
export ha però il potere di far sì che una variabile, pur definita dall'utente, entri a far parte di quelle d'ambiente e acquisti così visibilità globale (come già detto, presso tutte le shell discendenti dalla shell attuale):
$ export my_var=3
$ echo $my_var
3
$ bash
$ echo $my_var
3
Il quoting
Con questo termine viene indicata l'azione di preservare l'interpretazione di un argomento da parte della shell. Esistono più tipi di quoting.
Escape
L'operatore "\" inibisce l'interpretazione del (singolo) carattere che segue. Ecco ad esempio quel che si ottiene scrivendo:
$ echo this is the symbol of single quote: '
>
La shell ritiene cioè il comando incompleto e resta in attesa di ulteriori istruzioni. Al risultato desiderato si arriva, al contrario, digitando:
$ echo this is the symbol of single quote: \'
this is the symbol of single quote: '
Più in generale si ha:
\n
|
new line |
\r
|
carriage return |
\t
|
tab orizzontale |
\v
|
tab verticale |
\\
|
back slash |
\'
|
apice |
\"
|
doppio apice |
Single Quotes
Tutto ciò che viene racchiuso fra apici singoli non viene interpretato dalla shell: si usa anche dire che gli apici singoli implementano lo
strong quoting:
$ a=test_string
$ echo writing '$a' returns: $a
writing $a returns: test_string
$ echo 'I like withe-spaces very m u c h'
I like withe-spaces very m u c h
Double quotes
I doppi apici realizzano invece un quoting più lasco, il
weak quoting, consentendo al proprio interno l'interpretazione dei costrutti \
special_char, $
var_name e `
cmd`.
$ d=`date`
$ echo "Current date: $d"
$ a=10
$ echo "valore di a: $a"
valore di a: 10
Redirezione e concatenazione dei comandi
La possibilità di reindirizzare l'input, l'output e/o gli eventuali messaggi di errore ad un device, ad un file o ad altra istruzione costituisce uno dei principali punti di forza della shell. Comuni operatori di redirezione (osservare l'associazione implicita 0-input, 1-output, 2-error):
>, 1>
|
redirigono del testo, eventualmente il risultato di un'istruzione, verso un file in sovrascrittura o verso un device |
>>
|
redirige del testo, eventualmente il risultato di un'istruzione, verso un file in append |
<
|
reindirizza l'input, eventualmente il contenuto di un file, verso un'istruzione |
2>
|
reindirizza l'eventuale messaggio di errore di un'istruzione verso un file o un device |
&>
|
redirige sia il risultato, sia l'eventuale messaggio di errore di un'istruzione verso un file o un device |
2>&1
|
redirige l'eventuale messaggio di errore di un'istruzione verso il target dell'output |
>&2, 1>&2
|
redirigono l'output verso il target dello standard error |
|
|
redirige del testo, eventualmente il risultato di un'istruzione, verso altra istruzione |
Ad esempio:
$ echo "Hi, how are you?" > Hi.txt
stampa la stringa "Hi, how are you?" direttamente all'interno del file Hi.txt (se il file non esiste lo crea, in ogni caso ne sovrascrive il contenuto);
$ ls -l > ls_list.txt
stampa l'elenco dei file nella directory corrente all'interno del file
ls_list.txt (anche in questo caso se il file non esiste lo crea, in ogni caso ne sovrascrive il contenuto);
$ echo "Hello" >> Hi.txt
aggiunge al file
Hi.txt una riga contenete la stringa 'Hello';
$ cat < Hi.txt
visualizza il contenuto del file
Hi.txt;
$ ech0 "Hi, how are you?" 2> /dev/null
redirige il messaggio d'errore verso il device nullo, ovvero ne annulla la stampa;
$ echo "test" &> file1.txt
e
$ echo "test" > file1.txt 2>&1
sono istruzioni equivalenti poiché redirigono entrambe sia output, sia errore verso il file
file1.txt;
$ cat Hi.txt | grep Hello
estrae e visualizza dal file
Hi.txt le righe contenenti la stringa 'Hello';
$ cat Hi.txt | sort
visualizza il contenuto del file
Hi.txt ordinato alfabeticamente per righe.
Istruzioni condizionali
In Linux ogni comando è in sè condizionale, nel senso che dà riscontro del proprio esito. Un'istruzione restituisce in particolare un
exit status pari a 0 se la sua esecuzione non ha prodotto errori, di valore compreso fra 1 e 255 in caso contrario. É possibile conoscere l'exit status dell'ultimo comando eseguito interrogando la relativa variabile speciale "?".
$ echo exit status test: $?
exit status test
$ echo $?
0
$ expr 3 + two
expr: non-integer argument
$ echo $?
2
$ ls -l
-rwxr--r-- 1 root root test.sh
$ ./test.sh
sh: ./test.sh: Permission denied
$ echo $?
126
L'exit status restituisce anche l'esito di condizioni: 0 per condizione verificata; 1 in caso contrario. Gli esempi che seguono illustrano i 3 diversi costrutti che è possibile utilizzare per esprimere delle condizioni e alcuni operatori di confronto (in generale i costrutti
test <expression> e
[<expression>] sono equivalenti e, poichè portabili - POSIX -, preferibili alla forma
[[<expression>]] che tuttavia presenta altri vantaggi: ad esempio in Bash, consente l'uso di espressioni regolari)
$ a=test
$ [ $a == test ]; echo $?
0
$ [ $a == test1 ]; echo $?
1
$ n=5
$ [[ $n -gt 0 ]]; echo $?
0
$ [[ $n -lt 0 ]]; echo $?
1
$ test -e test.sh; echo $?
0
$ test -e test1.sh; echo $?
1
Per gli operatori di confronto si ha:
-n STRING |
|
the length of STRING is nonzero |
-z STRING |
|
the length of STRING is zero |
STRING1 = STRING2 |
|
the strings are equal |
STRING1 != STRING2 |
|
the strings are not equal |
|
INTEGER1 -eq INTEGER2 |
|
INTEGER1 is equal to INTEGER2 |
INTEGER1 -ge INTEGER2 |
|
INTEGER1 is greater than or equal to INTEGER2 |
INTEGER1 -gt INTEGER2 |
|
INTEGER1 is greater than INTEGER2 |
INTEGER1 -le INTEGER2 |
|
INTEGER1 is less than or equal to INTEGER2 |
INTEGER1 -lt INTEGER2 |
|
INTEGER1 is less than INTEGER2 |
INTEGER1 -ne INTEGER2 |
|
INTEGER1 is not equal to INTEGER2 |
|
FILE1 -ef FILE2 |
|
FILE1 and FILE2 have the same device and inode numbers |
FILE1 -nt FILE2 |
|
FILE1 is newer (modification date) than FILE2 |
FILE1 -ot FILE2 |
|
FILE1 is older than FILE2 |
-b FILE |
|
FILE exists and is block special |
-c FILE |
|
FILE exists and is character special |
-d FILE |
|
FILE exists and is a directory |
-e FILE |
|
FILE exists |
-f FILE |
|
FILE exists and is a regular file |
-g FILE |
|
FILE exists and is set-group-ID |
-G FILE |
|
FILE exists and is owned by the effective group ID |
-h FILE |
|
FILE exists and is a symbolic link (same as -L) |
-k FILE |
|
FILE exists and has its sticky bit set |
-L FILE |
|
FILE exists and is a symbolic link (same as -h) |
-N FILE |
|
FILE exists and has been modified since it was last read |
-O FILE |
|
FILE exists and is owned by the effective user ID |
-p FILE |
|
FILE exists and is a named pipe |
-r FILE |
|
FILE exists and read permission is granted |
-s FILE |
|
FILE exists and has a size greater than zero |
-S FILE |
|
FILE exists and is a socket |
-t FD |
|
file descriptor FD is opened on a terminal |
-u FILE |
|
FILE exists and its set-user-ID bit is set |
-w FILE |
|
FILE exists and write permission is grantedv
|
-x FILE |
|
FILE exists and execute (or search) permission is granted |
L'operatore booleano "&&" (AND_IF) si dimostra particolarmente utile in questo contesto perchè consente di eseguire un comando dietro condizione che l'exit status attuale sia pari a 0. Nell'esempio che segue (dove l'eventuale messaggio d'errore viene scartato mediante re-indirizzamento) il messaggio di avvenuta rimozione del file
test.txt viene visualizzato solo se l'operazione è andata effettivamente a buon fine.
$ rm test.txt 2> /dev/null && echo "Il file test.txt è stato eliminato."
L'operatore "||" (OR_IF) svolge invece l'azione inversa (ovvero il messaggio viene visualizzato se la prima istruzione fallisce (ovvero l'exit-status è diverso da 0) come illustra l'esempio seguente:
$ rm test.txt 2> /dev/null || echo "Impossibile eliminare il file test.txt."
I due operatori possono anche coesistere all'interno della stessa istruzione:
$ rm test.txt 2> /dev/null && echo "Il file test.txt è stato eliminato." || echo "Impossibile eliminare il file test.txt."
E, nel caso si renda necessario eseguire più istruzioni in subordine ad un'altra, basterà racchiudere le prime tra parentesi graffe e terminarle col carattere ";"
$ rm test.txt 2> /dev/null && { clear; echo "Il file test.txt è stato eliminato."; } || { clear; echo "Impossibile eliminare il file test.txt."; }
Ovviamente l'esecuzione di comandi può subordinarsi anche alla valutazione di condizione
$ read -p "Please insert a number: " n; [ $n -ge 0 ] && echo Positive || echo Negative
Please insert a number: -3
Negative
# echo -n "2 + 3 = "; read x; [ $x == "5" ] && echo "Good, you're right." || echo "No, you're wrong."
2 + 3 = 5
Good, you're right.
Commenti
Posta un commento