In questa sezione vediamo lo shell scripting. Diamo innanzitutto una carrellata di variabili d'ambiente che possono essere utili nell shell scripting.
$PWD
# la directory corrente
$IFS
# contiene il carattere utilizzato come separatore,
# rappresenta come la shell separa i parametri e i valori in
# genere, di default è assegnato al carattere "spazio" (space)
$PS1
# il prompt visualizzato dalla shell
$CDPATH
# è una variabile d'ambiente che può contenere
# directory aggiuntive che vengono considerate sempre nel momento
# in cui eseguiamo "cd", ad esempio se accediamo spesso alla
# directory "/etc" in quanto nella directory "/etc" sono
# contenute le directory "/etc/x" ed "/etc/y" che sono di nostro
# interesse, allora possiamo eseguire "export CDPATH=/etc" a
# questo punto in qualasiasi directory ci dovessimo trovare,
# possiamo eseguire semplicemente un "cd x" per accedere alla
# directory /etc/x ed "cd y" per accedere alla directory "/etc/y"
# , è anche utilizzata per impostare la directory a cui andiamo
# quando eseguiamo "cd", che di default oggigiorno è la "home"
# dell'utente
$PS2
# il secondo prompt visualizzato dalla shell, quello
# comumente indicato con ">"
$$
# il pid della shell attuale, in uno script è il PID dello
# script
$SHELL
# il tipo di shell utiizzata
$LD_LIBRARY_PATH
# il percorso delle librerie
$EDITOR
# il tipo di editor che viene aperto in automatico
# quando richiesto
$PATH
# il percorso da dove vengono presi gli eseguibili per
# essere lanciati come comandi
$RANDOM
# se stampata, mostra un valore casuale generato dalla
# bash
$#
# (utile negli script) il numero di parametri passati ad uno
# script
$0
# (utile negli script) nome dello script
$@
# (utile negli script) una variabile unica contenente la
# lista di parametri passati allo script, usa come separatore lo "
# spazio", questo è preferibile rispetto alla versione "$*", in
# quanto questa dipende da IFS
$*
# (utile negli script) una variabile unica contenente la
# lista di parametri passati allo script, usa come separatore il
# carattere nella variabile "IFS"
$1, $2, ...
# (utile negli script), è il parametro passato, ad
# esempio $1, è il primo parametro passato, $2 il secondo
# parametro passato e così via
Vediamo alcune operazioni sulle stringhe in Bash.
var=ciao
# stampa "ciao"
echo "$var"
# uguale al precedente, stampa "ciao"
echo "$var"
# stampa "$var", gli apici singoli sono diversi dagli apici doppi
echo '$var'
# stampa solo il primo carattere, cioè "c"
echo "${var::1}"
# stampa solo i primi due caratteri, posso leggerlo come
# "stampa #fino al carattere 2", quindi "ci"
echo "${var::2}"
# stampa tutta la stringa senza l'ultimo carattere
echo "${var::(-1)}"
# stampa tutta la stringa senza gli ultimi due caratteri
echo "${var::(-2)}"
# stampa tutta la stringa senza l'ultimo carattere
echo "${var:0:-1}"
# stampa solo i caratteri dal secondo al penultimo
echo "${var:1:-1}"
# in questo modo concateno le stringhe
var_seconda=($var$var)
# stampa la stringa senza gli ultimi 4 caratteri
echo "${var::-4}"
# stampa gli ultimi 3 caratteri della stringa, quindi "iao"
echo "${var:(-3)}"
# stampa i caratteri dal 3 alla fine quindi "ao"
echo "${var:3}"
# per ricavare la lunghezza di una stringa eseguiamo
length=${#var}
# salva l'output di un programma/comando in una variabile
output=$(comando -arg1 23 -opz1)
Ci sono due modi per effettuare test,
# il comando test
- come ad esempio: if test -f nome.txt
il comando [
# che per convenzione di leggibilità viene
# aggiunto il carattere ] alla fine del conditional
- come ad esempio: if [ -f nome.txt ]
Vediamo un esempio di blocco if
if [ -f fred.c ]
then
...
elif
...
else
...
fi
# versione alternativa su una sola linea
# dobbiamo specificare il ; se non mettiamo il then
# su una nuova linea
if [ -f fred.c ]; then
...
elif
...
else
...
endif
i tipi di condizioni che possiamo usare col comando "test", sono 3:
- string comparison
- arithmetic comparison
- file conditionals
The following table describes these condition types:
String Comparison | Result |
---|---|
string1==string2 | true if the strings are equal |
string1 != string2 | true if the strings are not equal |
-n string | true if the string is not null |
-z string | true if the string is null (an empty string) |
Arithmetic Comparison | Result |
---|---|
expression1 -eq expression2 | vera se le espressioni sono uguali |
expression1 -ne expression2 | vera se le espressioni non sono uguali |
expression1 -gt expression2 | vera se expression1 è maggiore dell'altra |
expression1 -ge expression2 | vera se expression1 è maggiore o uguale dell'altra |
expression1 -lt expression2 | vera se expression1 è minore dell'altra |
expression1 -le expression2 | vera se expression1 minore o uguale di expression2 |
! expression | vera se l'espressione è falsa, e viceversa |
File Conditionals | Result |
---|---|
-d file | vera se il file è una directory, o se la directory esiste |
-e file | vera se il file esiste, nota che storicamente l'opzione "-e" non è portable, quindi si usa "-f" |
-f file | vera se il file è un file regolare (esiste) |
-g file | vera se il set-group-id (SGID) è settato su un file |
-r file | vera se il file è readable |
-s file | vera se il file ha dimensioni diverse da zero |
-u file | vera se il set-user-id SUID è settato sul file |
-w file | vera se il file è writable |
-x file | vera se il file è executable |
è da ricordare che uno script termina correttamente con "exit 0", mentre si usa un numero diverso da zero per segnalare una terminazione unsuccessful dello script.
Inoltre è convenzione racchiudere le stringhe sempre tra "" doppi apici, anche se contenute in variabili, in quanto l'assenza di questi ultimi potrebbe dare problemi, quindi dobbiamo usare " $miaStringa" e non $miaStringa.
La struttura base di un ciclo for è:
#!/bin/sh
for variable in values
do
statements
...
done
exit 0
vediamo un esempio pratico:
#!/bin/sh
for foo in ciao1 ciao2 32 6 lol
do
echo $foo
done
exit 0
in questa caso vengono stampate le stringhe mostrate dopo "in". Vediamo un altro esempio, con una notazione "one-line":
#!/bin/sh
# this is wrong... we should not use ls, since it is an
# interactive tool we should use "f*.sh"
# the correct form is
# for file in f*.sh do ; echo "$file
for file in $(ls f*.sh); do
echo $file
done
exit 0
quello compreso tra $()
viene eseguito come comando e preso
l'output.
Vediamo un esempio di ciclo in stile C:
for i in {1..5}
do
echo "Welcome $i times"
done
un'altro esempio, potrebbe essere:
# in questo caso specifichiamo anche lo step da usare
for i in {1..10..2}
do
echo "Welcome $i times"
done
possiamo eseguire cicli infiniti con:
for (( ; ; ))
do
echo "infinite loops [ hit CTRL+C to stop]"
done
La struttura base di un ciclo while è:
#!/bin/sh
echo "Enter Password"
read trythis
while [ "$trythis" != "secret" ];do
echo "Sorry, try again"
read trythis
done
exit 0
La struttura base di un ciclo until è:
#!/bin/sh
until condizione
do
statements
...
...
done
exit 0
Un esempio di base è:
#!/bin/sh
until who | grep "$1" > /dev/null
#in questo caso viene fatto un un do fino a che non c'è l'utente
# indicato come parametro nella lista degli account loggati,
# la parte ">/dev/null" serve solo per redirigere l'output di alcuni comandi
do
statements
...
...
done
exit 0
La struttura base di un case switch è:
#!/bin/sh
##N.B.: Attenzione a come vengono usate le wildcard
##nei case switch, perchè in realtà solo la
##prima opzione di una wildcard verrà
##presa in considerazione
echo "Is it morning ? Please answer yes or no"
read timeofday
case "$timeofday" in
yes) echo "Good Morning";;
no ) echo "Good Afternoon";;
y ) echo "Good Morning";;
n ) echo "Good Afternoon";;
* ) echo "Sorry, answer not recognized";;
esac
exit 0
vediamo un'alternativa struttura di uno switch case:
#!/bin/sh
##N.B.: Attenzione a come vengono usate le wildcard
##nei case switch, perchè in realtà solo la
##prima opzione di una wildcard verrà
##presa in considerazione
echo "Is it morning ? Please answer yes or no"
read timeofday
case "$timeofday" in
yes | Y | Yes | YES) echo "Good Morning";;
n* | N* ) echo "Good Afternoon";;
* ) echo "Sorry, answer not recognized";;
esac
##in questo caso le wildcard funzionano
##correttamente, l'unico problema è che
##stringhe come never o Never
##avranno la stessa valenza di "no" ad esempio
exit 0
una struttura alternativa di uno switch case in cui vengono specificate più istruzioni all'interno di un case è:
#!/bin/sh
##N.B.: Attenzione a come vengono usate le wildcard
##nei case switch, perchè in realtà solo la
##prima opzione di una wildcard verrà
##presa in considerazione
echo "Is it morning ? Please answer yes or no"
read timeofday
case "$timeofday" in
[yY] | [yY][eE][sS])
echo "Good Morning";;
echo "Up bright and early this morning"
;;
[nN]*)
echo "Good Afternoon"
;;
*)
echo "Sorry, answer not recognized"
echo "Please answer yes or no"
exit 1
;;
esac
##in questo caso le wildcard funzionano
##correttamente, l'unico problema è che
##stringhe come never o Never
##avranno la stessa valenza di "no" ad esempio
exit 0
Possiamo concatenare comandi a livello condizionale, ad esempio:
#!/bin/sh
##Esempio Lista AND
if [ -f file_one ] && echo "ciao" && [ -f file_two]
then
echo "in if"
fi
##Esempio Lista OR
if [ -f file_one ] || echo "ciao" || [ -f file_two ]
then
echo "The first one is executed"
fi
exit 0
l'esempio con la lista AND, esegue i vari comandi da sinistra a destra solo se lìultimo eseguito è vero, cioè se la prima condizione è vera, allora viene eseguita la seconda (echo è sempre vero), se la seconda è vera viene eseguita la terza, nel caso una condizione non fosse vera, allora l'istruzione successiva non viene eseguita e l'intero if risulterebbe falso.
Mentre nel caso di lista OR, in pratica viene eseguito il primo comando vero e tutti gli altri vengono scartati dopo che è stato eseguito il primo comando vero.
Per effettuare operazioni matematiche utilizziamo generalmente bc, infatti la bash shell può effettuare operazioni matematiche nativamente solo con numeri interi, per operazioni più complesse siamo costretti ad usare bc, vediamo alcuni esempi:
#effettuo il calcolo 1/3
echo "1/3" | bc -l;
#oppure
a=2;
b=5;
res=$(bc -l <<< "$a * 3.4 + 4 / $b")
#l'operatore "<<<" serve a redirigere nello standard input del
#programma avviato
#setta la precisione del risultato a 2 cifredopo la virgola
echo "scale=2; 3/8" | bc
#radice quadrata
echo "sqrt(100)" | bc
#elevamento a potenza
echo "10^10" | bc
vediamo ora alcuni esempi di operazioni con interi supportate nativamente dalla bash:
#operazioni con numeri interi di bash
A=$(( B * C ))
B=$(( 1 + 5 / (59 *3) ))
Possiamo lanciare anche bc da terminale eseguendo:
bc -l
# il flag "-l" ci permette di avere numeri con la
# virgola, altrimenti nel caso non venisse abilitato, all'interno
# di bc, dovremmo eseguire "scale=5" ad esempio per impostare il
# numero di cifre dopo la virgola a 5
Possiamo definire funzioni shell in questo modo:
nome_funzione() {
statements
...
...
}
# se nessun return è usato, viene usato come valore
# di return dell'ultimo comando eseguito
è utile ricordare che possiamo fare prototipi di funzioni dalla shell anche solo eseguendo dalla command-line:
nome_funzione() {
istruzione1
istruzione2
commento
...
istruzioneN
}
# questo indica la fine del programma
e per visualizzare un funzione possiamo eseguire:
declare -f nome_funzione
# questo mostrerà il codice della
# funzione
il comando declare è molto utile anche nel caso volessimo salvare funzioni create sul momento e metterle in un file, andando a redirigere l'output nel file di configurazione della shell o nel file in cui andiamo a salvare le funzioni builtin.
## arr pag81 continuare dal libro beginning linux programming
arr=(Hello world) #definisco un array con due elementi stringa
arr2=(`echo {0..9}{0..9}{0..9}{0..9}`)
arr[0]=Hello
arr[1]=world
echo ${arr[0]} ${arr[1]} #stampa i vettori
vediamo un altro esempio:
DIGIT_CODE_ARRAY=(`echo {0..9}{0..9}{0..9}{0..9}`)
password="UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ"
number_combo=${#DIGIT_CODE_ARRAY[@]}
# use for loop read all combos
for (( i=0; i<${number_combo}; i++ ));
do
echo "$password ${DIGIT_CODE_ARRAY[$i]}"
done
Vediamo un esempio di script che seleziona una finestra il cui titolo contiene la stringa "stringInAWindow" (questo potrebbe anche non essere il titolo completo della finestra), la attiva e poi preme la combinazione di tasti "space+z" all'infinito:
xdotool search "stringInAWindow" windowactivate; while true; do xdotool key space+z; done
altro esempio, in cui attiviamo una finestra e premiamo la combinazione Ctrl+q:
xdotool search "giuseppe" windowactivate && xdotool key ctrl+q
per i tasti f1-f12 usiamo:
xdotool search "giuseppe" windowactivate && xdotool key alt+f4
possiamo mandare stringhe, ad esempio:
xdotool type 'ab cd' in questo caso stiamo mandando "a" poi "b" poi "spazio" poi "c" e poi "d".
Possiamo mandare ue tasti con:
xdotool key a b in questo caso mandiamo a e poi b.
Per mandare caratteri distanziati da 1ms, facciamo:
xdotool type --delay 1 'abc'
Possiamo anche aspettare che un'applicazione sia prima partita prima di lanciargli comandi, ad esempio:
google-chrome & xdotool search --sync --onlyvisible --class "google-chrome"x-terminal-emulator
# === Check per super-user (i.e., sei root ?) ===
#Ricorda che il valore UID per l'utente root è 0
if [ $UID -ne 0 ]; then
echo "Non root user. Please run as root."
else
echo "Root user"
fi
import namefile.jpg
# ci permette di selezionare un'area rettangolare
# in questo caso salviamo un file jpeg
# cambiando il nome del file in namefile.png
# il file in output sara' del formato png