Presento una veloce guida per dummies riguardante la Bash; vari temi in ordine spero non troppo sparso.
Table of contents
Alla fine ho ceduto e mi sono fatto una breve guida in quanto ogni volta mi devo scervellare su "come cavolo si faceva a...". Svariati concetti che cerco di non piazzare troppo alla rinfusa con trucchetti vari.
Una sola riga per ricordare che il Bash scripting serve principalmente per automatizzare operazioni ripetitive e dunque salvare tempo e fatica.
Aprire il terminale (CTRL+ALT+T) e digitare quanto segue:
touch NomeFile.sh
gedit NomeFile.sh
chmod u+x NomeFile.sh
./NomeFile.sh
Con il comando touch si crea un file vuoto, mentre con gedit si apre l'editor di testo Gnome (ovviamente dovremmo scriverci qualcosa dentro che vedremo nel seguito, salvare ed uscire).
Apro una piccola parentesi sull'editor di testo: no, non uso vi, quasi me ne vergogno visto che sono un linuxiano convinto. Per chi non sapesse cos'è vi: è un editor di testo linux presente fin dagli albori del sistema operativo; si usa solo mediante comandi da tastiera, se impari tutti i comandi e le combo divieni iper-rapido e vieni idolatrato dai giovani e rispettato dal resto della comunità (nerd) di programmatori mondiali; certo devi dimenticarti qualche piccola comodità e qualunque vezzo.
Con il comando chmod u+x si rende il file eseguibile (u sta per user, x executable) , ./ è la directory corrente, seguita dal file sta ad indicare alla shell che lo deve eseguire.
NOTA :
l'estensione .sh non è obbligatoria in teoria, nella pratica io ho deciso che è obbligatoria, così sappiamo che cos'è quel file senza doversi tanto scervellare.
Visto che sono uno origniale creiamo il nostro primo programma che non farà altro che scrivere le stringa Hello World.
Nel file HelloWorld.sh andremo a scriverci:
#! /bin/bash
echo Hello World
Veloce analisi.
#! è lo shebang o sha-Bang e va inserito nella prima riga del file eseguibile (al solito ci sono altre vie ma consiglio vivamente di fare così e non pensarci più). Lo shebang indica che l'interprete da utilizzare per eseguire il codice che seguirà è /bin/bash ovvero l'interprete Bash (al solito ce ne sono altri ma vi consiglio vivamente di usare questo e non pensarci più).
echo non fa nient'altro che reindirizzare quanto segue allo standard output, ovvero stampare a schermo la stringa.
Quindi procediamo con premere un pò di pulsanti alla tastiera.
ruffo@ubuntu:~$ touch HelloWorld.sh
ruffo@ubuntu:~$ gedit HelloWorld.sh
ruffo@ubuntu:~$ cat HelloWorld.sh
#! /bin/bash
echo Hello World
ruffo@ubuntu:~$ chmod u+x HelloWorld.sh
ruffo@ubuntu:~$ ./HelloWorld.sh
Hello World
ruffo@ubuntu:~$
Oserei dire meraviglioso ed il tutto in una manciata di secondi!
Da notare il comando cat che non fa nient'altro che mostrare il contenuto del file.
Avrei dovuto iniziare con questo paragrafo ma come detto poche righe fa sono uno originale. Documentazione se ne trova molta, però è troppo vasta e se non si sa cosa cercare è bene lasciar perdere in molti casi.
Usiamo questo paragrafo anche per introdurre altri concetti della Bash così da prendere due piccioni con una fava.
Dunque, manuale ? C'è un comando ovviamente:
man bash
Quindi c'è il comando man per avere un manuale testuale della bash o di un comando e molto altro.
Di seguito la pagina che si palesa.
Figure 1: man bash, first page
Il manuale è decisamente esteso. La domanda che potrebbe sorgere spontanea è: quanto?
Beh, ci viene in aiuto il comando wc che sta per word count, per prima cosa si provi a digitare:
man wc
Così apparirà la descrizione del comando con le opzioni, noi useremo l'opzione -l che conta il numero di righe del documento.
Ma come fare a dare come input il manuale della bash?
Semplice, si utilizza l'operatore pipe | che permette il piping, ovvero quello che sta a sinistra dell'opeartore funge da input a quello che sta a destra.
Quindi:
ruffo@ubuntu:~$ man bash | wc -l
6418
E scopriamo che con il manuale della bash siamo vicini alle 6500 righe, non male direi.
Non è l'unico supporto documentativo della bash testuale disponibile. C'è anche:
info bash
Il comando info di norma dà qualche informazione in più.
Comunque sia consiglio vivamente la documentazione online che potete trovare qui.
Usare le variabili è immediato,
ruffo@ubuntu:~$ myVar=2
ruffo@ubuntu:~$ echo my variable is $myVar
my variable is 2
ruffo@ubuntu:~$ myVar="hello"
ruffo@ubuntu:~$ echo my variable is $myVar
my variable is hello
ruffo@ubuntu:~$
Poche regole da ricordare.
Per assegnare un valore usare il simbolo = senza spazi prima o dopo (ricordare, SENZA SPAZI);
Se voglio il valore contenuto in una variabile utilizzare il simbolo $ seguito dal nome della variabile;
Le stringhe vanno all'interno delle virgolette "";
Per rimuovere una variabile usare il comando unset seguito dal nome della variabile.
Non parliamo di visibilità delle variabili, valgono le consuete regole della programmazione.
Gli alias sono delle semplici ridefinizioni di comandi, create per comodità, per avere un comando più corto o per utilizzare un nome più familiare.
L'alias più famoso è ll che equivale a digitare ls - l (ovvero list directory content with long listing format).
Creare un alias è banale:
alias ll="ls -l"
Così con due lettere si avrà una lista del contenuto della directory in formato esteso.
Un alias che uso spesso oltre a quello sopra è upgrade:
alias upgrade="sudo apt-get update && sudo apt-get upgrade"
Per conoscere la lista degli alias già definiti nel sistema basta digitare:
alias
Per rimuovere un alias si usa il comando
unalias
La domanda che sorge spontanea per gli alias è: come fare per avere disponibile automaticamente un alias quando apriamo un terminale?
Quando si fa partire un terminale alcuni file di inizializzazione vengono letti ed eseguiti. Tali file sono nella directory di home e sono (per Ubuntu):
.profile ( .bash_profile per le altre distribuzioni linux )
.bashrc
Il file profile è caricato quando la Bash è invocata (una volta), il file bashrc invece è eseguito ogni volta che una nuova shell viene fatta partire (annidamento).
Il file profile ad esempio si modifica se si vuole aggiungere una directory nel path, ad esempio:
PATH=$PATH:/usr/local/bin
Gli alias e le funzioni invece di norma si introducono nel file bashrc.
Quando eseguiamo un programma Bash possiamo aggiungere nella chiamata dei parametri separati da spazio chiamati parametri posizionali.
Nello script saranno identificati come $1,$2,.... Il nome del comando sarà contenuto in $0 mentre tutta la stringa dei parametri in $@.
NOTA: Nel caso si debba referenizarsi ad un parametro con più di un digit si usino le parentesi graffe, ad esempio ${10}.
Il comando
shift
muove i parametri posizionali di una posizione in sinistra ($2 in $1, $3 in $2, ....).
Vediamo un semplice programma esplicativo (param_01.sh):
#! /bin/bash
echo -e "Command name:\t" $0
echo -e "1st:\t" $1
echo -e "2nd:\t" $2
echo -e "3rd:\t" $3
echo -e "4th:\t" $4
echo -e "5th:\t" $5
echo -e "10th:\t" ${10}
echo -e "All:\t" $@
shift
echo Shift
echo -e "1st:\t" $1
echo -e "2nd:\t" $2
echo -e "3rd:\t" $3
Un esempio di output:
ruffo@ubuntu:~$ ./param_01.sh a b c d e f g h i l m n o p q
Command name: ./param_01.sh
1st: a
2nd: b
3rd: c
4th: d
5th: e
10th: l
All: a b c d e f g h i l m n o p q
Shift
1st: b
2nd: c
3rd: d
ruffo@ubuntu:~$
Si fa anche notare l'opzione -e del comando
echo
che permette di usare le stringhe di formattazione dette backslash escaped characters ( \t , \n rispettivamente il tabulatore e accapo) .
La sintassi per la dichiarazione del costrutto if-then-else è:
if
<condition>
then
<command list>
[else
<command list>]
fi
Si noti che per chiudere il costrutto si usa laparola speciale fi (contrario di if) e che else è opzionale.
L'istruzione if è strettamente correlata al comando test, spesso e volentieri sostituito nell'istruzione da [<test> ] o (( <test> )).
Gli operatori utilizzabili per test sono definiti nella seguente tabella.
NOTA: Gli operatori =, >, < e loro combinazioni sono utilizzati per confronti (lessicografici) tra stringhe.
Riportiamo di seguito un esempio esplicativo (if_01.sh).
#! /bin/bash
# FILES
f='data_file.txt' #remember NO space before and after '='
if
test -f $f #Is a file?
then
echo $f is a file
else
echo $f is not a file
fi
if
[ -d $f ] #Is a direcotry?
then
echo $f is a directory
else
echo $f is not a directory
fi
#NUMBERS AND STRINGS
int1=100
int2=0100
if
[ $int1 == $int2 ] #are STRINGS equals?
then
echo $int1 = $int2 compared as strings
else
echo $int1 != $int2 compared as strings
fi
if
[ $int1 -eq $int2 ] #are NUMBERS equals?
then
echo $int1 = $int2 compared as numbers
else
echo $int1 != $int2 compared as numbers
fi
L'output risultante sarà il segunte.
ruffo@ubuntu:~$ ./if_01.sh
data_file.txt is a file
data_file.txt is not a directory
100 != 0100 compared as strings
100 = 0100 compared as numbers
ruffo@ubuntu:~$
Gli operatori aritmetici vanno utilizzati fra doppia parentesi tonda (( <exp> )) .
Di seguito riporto la lista.
Riportiamo un file di esempio (arithm_01.sh).
#! /bin/bash
i=10
echo -e "i\t=" $i
(( i++ )) #value expected 11
echo -e "i++\t=" $i
(( i-- )) #value expected 10
echo -e "i--\t=" $i
(( i+=5 )) #value expected 15
echo -e "i+=5\t=" $i
(( i*=2 )) #value expected 30
echo -e "i*=2\t=" $i
((i=4*3)) #value expected 12
echo -e "4*3\t=" $i
((i=10/2)) #value expected 5
echo -e "10/2\t=" $i
((i=4%3)) #value expected 1, Modulo
echo -e "4%3\t=" $i
((i=2**3)) #value expected 8, Exponential
echo -e "2**3\t=" $i
((i=14^2)) #value expected 12, Exponential
echo -e "14^2\t=" $i
((i=14>>1)) #value expected 7, Exponential
echo -e "14>>1\t=" $i
Come facilmente prevedibile l'ouput risulante è il seguente.
ruffo@ubuntu:~$ ./arithm_01.sh
i = 10
i++ = 11
i-- = 10
i+=5 = 15
i*=2 = 30
4*3 = 12
10/2 = 5
4%3 = 1
2**3 = 8
14^2 = 12
14<< = 7
ruffo@ubuntu:~$
La sintassi del ciclo while è la seguente:
while
<conditions>
do
<commands>
done #loops while <conditions> succeeds
Riporto un ciclo while di seguito in un sempio esplicativo ( while_01.sh ) che aiuta ad introdurre anche un paio di altri concetti/comandi:
#! /bin/bash
typeset -i x #x must be an integer
x=0
while
((x<3)) #while condition, pay attention arithmetic expression inside (( ))
do
echo loop $x #write number of loop
sleep 1 #wait a second
((x++)) # pay attention, arithmetic expression inside (( ))
done
In questo esempio si eseguono 3 cicli, si scrive a standard output il numero di ciclo, si attende tra un ciclo e l'altro un secondo.
Eseguendolo si ottiene semplicemente:
ruffo@ubuntu:~$ time ./while_01.sh
loop 0
loop 1
loop 2
real 0m3.015s
user 0m0.005s
sys 0m0.008s
ruffo@ubuntu:~$
Si noti che ho usato prima dell'invocazione della script il comando
time
molto utile per conoscere il tempo di esecuzione del programma, si nota che in effetti è di circa 3 secondi come ci si poteva attendere.
Inoltre si noti la dichiarazione della variabile x avviene mediante il comando
typeset -i
che definisce che la variabile in questione è un intero. Non è necessario farlo ma fornire il tipo velocizza le operazioni aritmetiche su di essa e permette di utilizzare alcuni operatori molto utili quali:
((x++)) #increment
((y=x**2)) #square
((x*=y)) #x=x*y
NOTA IMPORTANTE: Si noti che ogni espressione aritmetica va inserita all'interno di doppie parentesi tonde (( )).
Il comando
sleep
serve per attendere un certo numero di secondi.
Di norma i comandi, o meglio i processi in genere, hanno tre standard file aperti o meglio tre standard file descriptors (il motivo del perchè sono file è legato al kernel, il discorso si allungherebbe, prendete l'affermazione per buona). Questi file sono lo standard input, lo standard output e lo standard error.
Per reindirizzare dunque l'input, l'output e l'output di errore si possono utilizzare degli operatori che sono rispettivamente > , < e 2>. I due file di output possono essere reindirizzati contemporaneamente con &> (ritorna utile per avere l'output globale di un comando).
Gli output possono essere dunque reindirizzati in un altro file che se non esistente viene creato, altrimenti sovrascritto. Se si vuole fare un append al file si usino >> , 2>>, &<<.
Di seguito un esempio (redir_01.sh) ed il file di testo utilizzato nell'esempio (file_to_be_sort.txt).
#! /bin/bash
echo EXAMPLE 1
echo cat >myFile.txt
echo wins >>myFile.txt
cat myFile.txt
echo -e "\nEXAMPLE 2"
sort < file_to_be_sort.txt
donkey
cow
cat
dog
bull
mouse
goat
L'output è il seguente.
ruffo@ubuntu:~$ ./redir_01.sh
EXAMPLE 1
cat
wins
EXAMPLE 2
bull
cat
cow
dog
donkey
goat
mouse
ruffo@ubuntu:~$
Si noti il comando
sort
che non fa null'altro che riordinare in ordine alfabetico (per righe) il file in ingresso.
Risulta utile in taluni casi non visualizzare lo standard error di un dato processo, per fare ciò si può reindirizzare lo standard error ad un file di sistema di convenienza detto bit bucket:
/dev/null
si veda a tal proposito il seguente esempio esplicativo.
ruffo@ubuntu:~$ wrong
wrong: command not found
ruffo@ubuntu:~$ wrong 2> /dev/null
ruffo@ubuntu:~$
L' operatore di piping | l'abbiamo già visto in precedenza, non fa nient'altro che utilizzare lo standard output di un comando come standard input del successivo, analogamente al reindirizzamento c'è anche l'operatore |& che reidnrizza sia lo standard output che lo standard error come standard input del comando successivo.
Il loop while è comodo per leggere righe da un file, la sintassi è mostrata nel seguente semplice esempio ( while_02.sh ).
#! /bin/bash
while
read first second third
do
echo -e 1st is $first "\t" 2nd is $second "\t" 3rd is $third
done <data_file.txt
Il comando
read
legge una riga dallo standard input, nelle variabili che seguono si avranno le stringhe fra uno spazio all'altro (le parole), nell'ultima variabile il resto della stringa.
Per utilizzare un file come standard input si usa l'operatore
<
seguito dal nome del file, va messo (forse constrointuitivamente) dopo il comando di fine loop done.
Per l'esempio usiamo il seguente file ( data_file.txt )
cat dog donkey cow
orange lemon apple strawberry
Alfa Romeo Ferrari Maserati
L'escuzione dello script ritorna il seguente ouput (come ci si potrebbe attendere).
ruffo@ubuntu:~/Examples$ ./while_02.sh
1st is cat 2nd is dog 3rd is donkey cow
1st is orange 2nd is lemon 3rd is apple strawberry
1st is Alfa 2nd is Romeo 3rd is Ferrari Maserati
ruffo@ubuntu:~/Examples$
Se si volesse usare lo standard input dato da un'uscita da un'altro comando basterà utilizzare il comando di piping | come usuale, ad esempio è possibile riscrivere il primo script come segue (while_03.sh):
#! /bin/bash
cat data_file.txt | while
read a b c
do
echo -e 1st is $first "\t" 2nd is $second "\t" 3rd is $third
done
L'output dato dall'esecuzione dello script è esattamente lo stesso.
La sintassi del ciclo for è la seguente.
for <var> in <list>
do
<command list>
done
La cosa più interessante è il come generare la lista su cui ciclare, ci sono svariate opzioni, di seguito ne riporto alcune:
si possono scrivere stringhe separate da uno spazio;
si possono utilizzare le stringhe (separate da uno spazion) contenute in un file mediante la sintassi $(<file) che sfrutta il reindirizzamento dello standard input;
si può usare il comando seq X Y che genera una sequenza da X ad Y, si deve inserire fra apici singoli ( 'seq X Y' ) nella riga di intestazione del ciclo for;
si possono generare attraverso la sintassi {x..y} dove x ed y possono essere numeri o caratteri;
si possono utilizzare i file all'interno di una cartella mediante il simbolo *;
si può utilizzare l'output convertito in stringhe di una funzione facendo attenzione di utilizzare la sintassi $( command ).
Di seguito vari esempi di generazione di liste su cui ciclare con il for (for_01.sh).
#! /bin/bash
echo -e "Example 1"
for i in first second third
do
echo -e "\t" $i
done
echo -e "\nExample 2"
for i in $(<data_file.txt)
do
echo -e "\t" $i
done
echo -e "\nExample 3"
for i in 'seq 3 7'
do
echo -e "\t" $i
done
echo -e "\nExample 4"
for i in {2..4}
do
echo -e "\t" $i
done
echo -e "\nExample 5"
for i in {a..c}
do
echo -e "\t" $i
done
echo -e "\nExample 6"
for i in {W..Z}
do
echo -e "\t" $i
done
echo -e "\nExample 7"
for i in *.txt
do
echo -e "\t" $i
done
echo -e "\nExample 8"
for i in $(echo cat dog donkey cow)
do
echo -e "\t" $i
done
L'ouput è il seguente.
ruffo@ubuntu:~/Examples$ ./for_01.sh
Example 1
first
second
third
Example 2
cat
dog
donkey
cow
orange
lemon
apple
strawberry
Alfa
Romeo
Ferrari
Maserati
Example 3
seq 3 7
Example 4
2
3
4
Example 5
a
b
c
Example 6
W
X
Y
Z
Example 7
dataBigFile.txt
data_file.txt
dateFile0.txt
dateFile1.txt
dateFile2.txt
Example 7
cat
dog
donkey
cow
ruffo@ubuntu:~/Examples$
La sintassi per la dichiarazione del costrutto case è:
case <expression> in
<pattern 1> )
<command list> ;;
<pattern 2> )
<command list> ;;
...
esac
Si noti che:
per chiudere il costrutto si usa laparola speciale esac (contrario di case)
per ogni scelta si deve chiudere con una parentesi tonda ) e per chiuderla necessita del doppio punto e virgola ;;
non c'è la scelta else , viene sostituita ove necessario da *.
Interessante vedere un tipico esempio di uso di case, utile per chiedere in uno script che cosa l'utente voglia fare e procedere di conseguenza (case_01.sh).
#! /bin/bash
echo -n "Print message? "
valid=0
while
[ $valid == 0 ]
do
read answer
case $answer in
y|Y|yes|YES|[yY][eE][sS] )
echo Message: you digit $answer
valid=1
;;
n|N|[nN][oO] )
valid=1
;;
* )
echo Yes or No of some form please
;;
esac
done
NOTA: L'aspetto più interessante riguarda la composizione delle scelte ammesse per ogni espressione, sia per lo "yes" che per il "no" si accettano le forme brevi con un carattere minuscolo o maiuscolo o la parola intera in qualunque combinazione (la successione delle parentesi quadrate), le opzioni sono separate dal simbolo di or | (che è anche il simbolo di pipe in un diverso contesto ovviametne).
Vediamo di seguito un esempio di output.
ruffo@ubuntu:~/Examples$ ./case_01.sh
Print message? Which message?
Yes or No of some form please
AH! Now I understood, I need to write yes or no!
Yes or No of some form please
YeS
Message: you digit YeS
ruffo@ubuntu:~/Examples$ ./case_01.sh
Print message? N
ruffo@ubuntu:~/Examples$
E' possibile definire una funzione in uno script bash, in pratica si riesce a rinominare una sezione di codice da richiamare più volte durante l'esecuzione dello script.
La sintassi per la dichiarazione di una funzione è il seguente:
function <function name> {
<function body code>
}
E' possibile dichiare delle variabili interne alla funzione mediante il comando:
typeset
Di seguito un esempio esplicativo (funct_01.sh).
#! /bin/bash
#FUNCTIONS
function funYesNo {
typeset valid=0 #function local variable
typeset answer #function local variable
while
[ $valid == 0 ]
do
echo -n $msgYesNo #message string is a global variable
read answer
case $answer in
y|Y|yes|YES|[yY][eE][sS] )
ansYes=1 #answer variable is a global variale, 1 means "yes"
valid=1
;;
n|N|[nN][oO] )
ansYes=0 #answer variable is a global variale, 0 means "mo"
valid=1
;;
* )
echo Yes or No of some form please
;;
esac
done
}
#MAIN
ansYes=0
msgYesNo="Continue ? "
funYesNo
if
(( $ansYes==1 )) #YES, continue
then
msgYesNo="Are you sure ? "
funYesNo
if
(( $ansYes==1 )) #YES, continue
then
msgYesNo="Are you really sure sure ? "
funYesNo
if
(( $ansYes==1 )) #YES, continue
then
echo "Glad you are sure"
fi
fi
fi
Segue un esempio di output.
ruffo@ubuntu:~/Examples$ ./funct_01.sh
Continue ?y
Are you sure ?mmmmmm.... no
Yes or No of some form please
Are you sure ?no
ruffo@ubuntu:~/Examples$ ./funct_01.sh
Continue ?y
Are you sure ?yes
Are you really sure sure ?YEEEEEEEES
Yes or No of some form please
Are you really sure sure ?YES
Glad you are sure
ruffo@ubuntu:~/Examples$
Per interrompere l'esecuzione di una funzione mediante il comando:
return
Il funzionamento è così facilmente esplicabile:
function muFunct {
echo this will be executed
return
echo this will not be executed
}
Per terminare il processo di shell è possibile usare il comando
exit
si ribadisce che esce dal processo, anche se lanciato in una funzione, chiude quindi l'intero programma, non esce semplicemente dalla funzione.
Il codice di uscita è disponibile nella variabile
$?
è convenzione che il codice 0 significhi che ilk programma è uscito senza problema alcuno mentre dei valori non nulli stanno ad indicare un errore durante l'esecuzione del programma.
Esempio semplicissimo (exit_01.sh).
#! /bin/bash
exit 7
Ouptput.
ruffo@ubuntu:~/Examples$ ./exit_01.sh
ruffo@ubuntu:~/Examples$ echo $?
7
ruffo@ubuntu:~/Examples$
E' possibile conoscere anche l'identificativo del processo di bash corrente tramite la variabile
$$
Come si può evincere da questi pochi semplici comandi:
ruffo@ubuntu:~/Examples$ echo $$
44962
ruffo@ubuntu:~/Examples$ ps -l
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 44962 44944 0 80 0 - 2773 do_wai pts/1 00:00:00 bash
4 R 1000 44968 44962 0 80 0 - 3129 - pts/1 00:00:00 ps
ruffo@ubuntu:~/Examples$
Il comando:
ps
non fa nient'altro che mostrare la lista dei processi correnti, l'opzione -l mostra le informazioni in formato esteso.
In Linux, un programma è detto filtro (filter) se legge dallo standard input (stdin) e scrive su standard output (stdout).
Una delle caratteristiche peculiari dei filtri è la possibilità di usarli in pipe.
I filtri sono molto utili per generare dei report o formattare in un dato modo i dati in ingresso.
In questa sezione vediamo qualche comando che può fungere da filtro o riportarci delle informazioni.
Comandi:
head , tail
Prendono capo e coda di uno standard input e ne mostrano le prime/ ultime n righe.
Un esempio esplicativo.
ruffo@ubuntu:~/Examples$ cat data_file.txt
cat dog donkey cow
orange lemon apple strawberry
Alfa Romeo Ferrari Maserati
ruffo@ubuntu:~/Examples$ cat data_file.txt | head -1
cat dog donkey cow
ruffo@ubuntu:~/Examples$ cat data_file.txt | tail -1
Alfa Romeo Ferrari Maserati
ruffo@ubuntu:~/Examples$ cat data_file.txt | head -2 | tail -1
orange lemon apple strawberry
ruffo@ubuntu:~/Examples$
Comando:
tr
Traduce o cancella parti di uno standard input.
Un esempio esplicativo.
ruffo@ubuntu:~/Examples$ cat data_file.txt
cat dog donkey cow
orange lemon apple strawberry
Alfa Romeo Ferrari Maserati
ruffo@ubuntu:~/Examples$ cat data_file.txt | tr aeiouy AEIOUY
cAt dOg dOnkEY cOw
OrAngE lEmOn ApplE strAwbErrY
AlfA ROmEO FErrArI MAsErAtI
ruffo@ubuntu:~/Examples$ cat data_file.txt | tr -d dg
cat o onkey cow
orane lemon apple strawberry
Alfa Romeo Ferrari Maserati
ruffo@ubuntu:~/Examples$
Comando:
sed
Questo comando è uno stream editor, funge da filtro ed ha moolte possibilità di utilizzo, la più utilizzata è la sostituzione.
Per eseguire un trova e sostituisci(find and replace) in un testo la sintassi da usare è riportato nel seguente esempio.
ruffo@ubuntu:~/Examples$ cat file_to_change.txt
I have a lion.
My lion is a very nice animal, I like a lot to play with my lion.
The name of my cat is Lion.
ruffo@ubuntu:~/Examples$ sed 's/lion/cat/g' file_to_change.txt
I have a cat.
My cat is a very nice animal, I like a lot to play with my cat.
The name of my cat is Lion.
ruffo@ubuntu:~/Examples$
Si passa dunque al comando una stringa di configurazione fra apici ('string') ed il file su cui fare l'operazione di sostituzione. La stringa contiene una s che sta per sostituisci (substitute), seguito dal simbolo / (slash), la parola da ricercare, / (slash), la parola da sostituire, / (slash) e la lettera g opzionale, che sta per ricerca globale (global), senza l'opzione di ricerca globale sostituirebbe solo la prima occorrenza in ogni riga. Se necessario eseguire più sostituzioni contemporaneamente si utilizzi l'opzione -e prima di ogni stringa, si veda l'esempio che segue.
ruffo@ubuntu:~/Examples$ sed -e 's/lion/cat/g' -e 's/Lion/Snowball/' file_to_change.txt
I have a cat.
My cat is a very nice animal, I like a lot to play with my cat.
The name of my cat is Snowball.
ruffo@ubuntu:~/Examples$
Entriamo qui in un nodo spinoso, fare debug con Bash di primo acchitto può parere complesso, ci sono delle tecniche e degli strumenti che ci possono venire in aiuto.
Prima di tutto un consiglio, sfruttiamo le finestre in parallelo; trovo utilissimo quando sviluppo un nuovo script lanciare in task parallelo dalla bash l'editor di testo (uso gedit di norma) contenente lo script e continuare ad avere il terminale libero per eseguire lo script.
Come notate in caso di errore viene ritornato in che riga si è verificato, dunque avere le righe numerate nell'editor è un grande aiuto.
Ci sono poi degli strumenti molto utili.
Prima di tutto parliamo del comando
bash
lanciato senza opzioni esegue semplicemente lo script, ma con le opzioni -x e -n esegue un debug; con -x prima di eseguire la riga di bash la mostra (ne fal'echo), l'opzione -n invece non esegue i comandi, cerca solo errori di sintassi.
Si prenda l'esemipo del seguente codice con un paio di grossolani errori (debug_01.sh).
#! /bin/bash
echo How to debug a Bash script
varTest = 0 #NO SPACE BEFORE AND AFTER '='!!!
if varTest > 0 then
echo ok
else #ELSE WHAT???
fi
Provando quindi ad eseguire il comando con le due opzioni otteniamo quanto segue.
ruffo@ubuntu:~/Examples$ bash -x debug_01.sh
+ echo How to debug a Bash script
How to debug a Bash script
+ varTest = 0
debug_01.sh: line 3: varTest: command not found
debug_01.sh: line 6: syntax error near unexpected token `else'
debug_01.sh: line 6: `else #ELSE WHAT???'
ruffo@ubuntu:~/Examples$ bash -n debug_01.sh
debug_01.sh: line 6: syntax error near unexpected token `else'
debug_01.sh: line 6: `else #ELSE WHAT???'
ruffo@ubuntu:~/Examples$
Una tecnica valida è inserire passo passo svariati echo in modo tale da seguire lo sviluppo dello script, in abbinata magari con il comando:
tee
che non fa nient'altro che leggere dallo standard input e scrivere sia su standard output che in un file.
ruffo@ubuntu:~/Examples$ ./debug_01.sh | tee log.file
./debug_01.sh: line 3: varTest: command not found
./debug_01.sh: line 6: syntax error near unexpected token `else'
./debug_01.sh: line 6: `else #ELSE WHAT???'
How to debug a Bash script
ruffo@ubuntu:~/Examples$ cat log.file
How to debug a Bash script
ruffo@ubuntu:~/Examples$
Altro utile strumento è il comando
set
che ha principalmente due opzioni -u e -x.
Il comando set con opzione -u (posta in una riga dello script) riporta l'uso di una variabile che non è stata dichiarata, mentre con l'opzione -x fa l'eco delle righe di codice dello script (similmente al comando bash -x per l'esecuzione dello script).
Segue un esempio esplicativo (set_01.sh):
#! /bin/bash
set -u
set -x
a=1
b=2
echo a is $a , b is $b
echo c is $c
set +x
set +u
Si noti che con l'opzione +x e +u è possibile terminare la parte di codice in debug.
Eseguendo lo script l'output è il seguente.
ruffo@ubuntu:~/Examples$ ./set_01.sh
+ a=1
+ b=2
+ echo a is 1 , b is 2
a is 1 , b is 2
./set_01.sh: line 7: c: unbound variable
ruffo@ubuntu:~/Examples$