Passa ai contenuti principali

Linux Shell: guida di sopravvivenza - Parte I - Uso avanzato della shell

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

Post popolari in questo blog

Introduzione alle reti 3 - Switch tecnologies

In questa sezione verranno esaminate le Virtual LAN, prima espressione della tecnologia LAN Switching.    Virtual Local Area Network (VLAN) Come già detto, uno dei punti di forza di uno switch risiede nella possibilità di creare sotto-reti a basso costo. Le VLAN ( Virtual Local Area Network ) , in particolare,  rappresentano la risposta a quelle situazioni nelle quali, pur non disponendo di grandi risorse, si renda comunque necessario tener distinti due o più ambiti della stessa rete, o, come è usuale dire, suddividere in più parti un dominio di broadcast.  L'esempio tipico è quello di una (piccola) impresa che abbia necessità di tener separati sulla rete i propri device in base alle attività produttive. La rete potrebbe allora pensarsi composta di tante VLAN quante sono le attività aziendali, ciascuna VLAN potendo contenere al proprio interno l'hw destinato ad ogni specifica attività. Dal punto di vista sistemistico, una  VLAN  è costituita da un sottoinsiem

Switch Commands - istruzioni Cisco HP Extreme "in a nutshell"

Indirizzamento IP

Obiettivo di questo articolo, senza pretesa di esaurire l'argomento, è quello di presentare in modo chiaro e comprensibile un argomento alle volte ostico: l'indirizzamento IP.  Questo, fra l'altro, consentirà di affrontare, anche agli utenti meno esperti, operazioni semplici quali   la configurazione di una scheda di rete con maggiore consapevolezza. Gli indirizzi IP Come noto, con IP  ci si riferisce ad uno dei protocolli di comunicazione di rete più affermati nel mondo delle telecomunicazioni:  Internet Protocol .   Un indirizzo IP è, in particolare, un codice binario che identifica univocamente un dispositivo di rete (una scheda ethernet, la porta di uno switch o di un router, etc.) all'interno di una rete di computer.  Di primaria importanza, nell'ambito dell'indirizzamento, è la lunghezza degli indirizzi, ovvero il numero di bit utilizzati per rappresentare un indirizzo di rete. Questo, evidentemente, perché con n bit sarà possibile ind