paper.gifioProgrammo N°19, Novembre 1998 ©Copyright DIEMME Editori

fire.gif

PRIMI PASSI CON TCL
parte 1
Inizia il nostro viaggio nella programmazione dell'ambiente grafico di Linux. In questi primi articoli studieremo i fondamenti del linguaggio tcl, che utilizzeremo in seguito con il toolkit tk per progettare interfacce utente grafiche
Tcl (Tool Command Language), creato da John Ousterhout nei laboratori della Sun Microsystems, è insieme al Perl uno dei più completi linguaggi di scripting. Le regole che il programmatore deve osservare per scrivere codice tcl sono poche ma precise. La tecnica che useremo sarà quella di scrivere il programma con un editor, salvare il file appena editato con estensione .tcl e renderlo eseguibile con il comando "chmod +x nomefile.tcl". Daremo per scontato che l'interprete tclsh sia stato installato nella directory /usr/bin/, per cui tutti i nostri esempi inizieranno con la riga #!/usr/bin/tclsh. Come dicevo la sintassi di tcl è molto chiara e pone pochi vincoli. Ogni riga dello script è composta da uno o più comandi. Quest'ultimi sono costituiti da singole parole separate da spazi. La prima parola è il nome del comando mentre le successive sono gli argomenti del comando stesso. Gli argomenti possono essere racchiusi tra doppi apici, parentesi quadre e parentesi graffe a seconda di come l'argomento dovrà essere valutato dall'interprete. Le parentesi quadre vengono usate per annidare i comandi consentendo il passaggio del risultato di un comando come argomento per il comando precedente. Le doppie virgolette si usano per raggruppare una serie di argomenti in modo che siano valutati come un argomento unico (si pensi alla stampa di una stringa composta da più parole che altrimenti verrebbero considerate come singoli argomenti). Si osservi che i caratteri speciali come il dollaro $ e le parentesi quadre, vengono sempre valutati quando sono racchiusi tra doppi apici. Ciò non accade invece se si usano le parentesi graffe per raggruppare più argomenti in uno. Le graffe inoltre sono obbligatorie in altri contesti come quello dei cicli di iterazione e nella dichiarazione delle procedure. Il segno di dollaro $, anteposto al nome di una variabile, è usato per referenziare il contenuto dalla variabile stessa, ovvero sostituisce il nome della variabile con il suo valore. Il backslash \ oltre a "quotare" alcuni caratteri assegnando loro funzioni speciali (per esempio \n è usato per inserire un ritorno a capo nella stampa di stringhe), è usato per disattivare il significato speciale dei segni $ { e [, oppure per continuare comandi e commenti sulla riga seguente. Le regole per la punteggiatura riguardano solo il punto e virgola, usato per permettere la scrittura di più comandi sulla stessa riga di codice o per iniziare un commento alla fine di un comando. Ogni commento è preceduto dal segno #. Che ne dite di fissare per bene tutti questi concetti con un esempio? Useremo molto il comando predefinito puts che, se usato senza argomenti speciali, permette la stampa su terminale.

#!/usr/bin/tclsh
# si inizia con un bel commento
puts "Ciao, mondo"; puts stdout {Ciao, mondo}
# stampa due volte  Ciao, mondo
# per default il comando puts \
stampa anche un ritorno a capo
# stdout è un argomento predefinito di puts \
e forza la scrittura sullo standard output
puts "Sono le ore [clock format [clock seconds]]"
# stampa l'ora corrente
# clock format e clock second sono comandi predefiniti
# il risultato di clock second viene passato a clock format
puts {Sono le ore {clock format [clock seconds]}}
# stampa Sono le ore clock format [clock seconds]
set variabile 5
# assegna a variabile il valore 5
puts -nonewline "variabile vale $variabile"
# stampa variabile vale 5 senza tornare a capo
# -nonewline è un argomento predefinito di puts \
usato per impedire il ritorno a capo
puts Linux # stampa Linux
# errore, il commento non è preceduto \
dal punto e virgola
puts "Slackware \
      vs \
      RedHat"; # stampa Slackware vs RedHat

E' giunto il momento di usare queste regole con gli altri elementi del linguaggio. Iniziamo subito da i più importanti.
linea.gif
VARIABILI ARRAY E LISTE
L'elemento più semplice da usare con tcl è la variabile scalare. Poiché non è necessario allocare spazio in memoria per usarla non è nemmeno necessario ricorrere ad una sua preliminare dichiarazione. Le variabili quindi si possono usare al volo in qualsiasi punto del programma. Il comando usato per assegnare un valore ad una variabile è set. Tcl dispone degli stessi operatori del linguaggio C con i quali è possibile costruire espressioni matematiche o logiche a seconda dell'operatore usato. Se vogliamo eseguire dei calcoli è necessario il comando expr. E' possibile infine verificare l'esistenza di una variabile con il comando info exist oppure cancellare una variabile con unset. Tutti questi elementi associati alle tecniche di sostituzione permesse dall'uso delle parentesi quadre, consentono un uso efficace delle variabili:

set nome pippo
# assegno a nome il valore pippo
set a 5; set b 2
set c [expr 5/2]
# assegno a c il risultato della divisione 5/2
puts $c
# stampa 2
set d $c
# assego alla variabile d \
il contenuto della variabile c
puts [expr 5.0/2]
# stampa 2.5
set pi_greco [expr 2*asin(1.0)]
# assegno a pi_greco 3.1415926535897931
unset pi_greco; # elimino pi_greco
if {![info exist pi_greco]} {
  puts "pi_greco e' stato cancellato"
}
# la condizione e' vera per cui stamperà \
pi_greco e' stato cancellato
set .o oggetto!
puts $.oy; # errore, servono le graffe
puts ${.o}y; # stampa oggetto!y

Vi sarete accorti che nell'esempio l'operazione 5/2 non stampa 2.5, ma 2. Questo perchè tcl, per default, considera i numeri degli interi. Se vogliamo stampare la parte decimale del risultato si deve scrivere almeno un numero che partecipa all'operazione con la virgola (la virgola viene scritta con un punto). Un altro approccio consiste nell' indicare all'interprete il tipo di precisione che si dovrà usare nei calcoli, e questo viene fatto con un apposito comando:

set tcl_precision 5
# 5 cifre dopo la virgola
puts [expr 1/3]
# stampa 0.33333

Il numero ed il valore degli argomenti passati allo script sulla riga di comando sono rispettivamente memorizzati in due variabili speciali: argc e argv

puts "Programma: $argv0"
# $argv0 contiene per default il nome dello script
puts "Numero di argomenti: $argc"
# stampa il numero di argomenti passati allo script
set i 0
foreach arg $argv {
  puts "Argomento $i: $arg"
  incr i
}
# stampa il numero di ogni argomento ed il suo contenuto

Passiamo agli array. Gli array in tcl sono implementai come tabelle hash nel senso che l'indice di un elemento dell'array non è un intero ma una stringa. Il comando più usato per manipolare gli array è array dotato di un discreto numero di opzioni come size, get, names.Vediamo qualche esempio.

set password(root) segreta
# root e' l'indice
# segreta e' il primo elemento dell'array
set password(pino) goldrake
set password(mario) mazinga
# ho definito un array di nome password \
di tre elementi
puts "La password di root e': $password(root)"
# stampa La password di root e': segreta
puts [array size password]
# stampa 3
# array size e' un comando predefinito

Lavorando con Linux spesso capita di far riferimento alle variabili d'ambiente. Quest'ultime in tcl sono memorizzate in un array speciale di nome env.

puts "$env(TERM)"
# stampa a video il valore della variabile d'ambiente TERM
set environment  [array get env]
foreach {var valore} $environment {
  puts "$var=$valore"
}
# stampa tutte le variabili d'ambinete
# come il comando set di Linux

Ed eccoci arrivati alle liste. Innanzitutto una lista è un elenco di variabili scalari (prese singolarmente o a gruppi) o di array.Il "tipo" lista di array è quindi un tipo di variabile valido. Ogni variabile o gruppo di variabili è chiamato elemento della lista. Per esempio la scrittura

set mialista {a b c d e f}
set mialista2 {{a b}  {c d} {e f}}

definisce una lista di nome mialista composta da 6 elementi (le prime sei lettere dell'alfabeto) ed un'altra di nome mialista2 dotata di tre elementi (le coppie a b, c d, e f). I comandi per il trattamento delle liste sono molto usati in tcl. I più utili sono

llength lista Restituisce il numero di elementi di una lista
lindex lista indice Restituisce il valore di un elemento della lista (se l'indice è uguale a zero, si punta al primo elemento della lista)
linsert lista indice elemento ?elemento elemento ...? Inserisce uno o più elementi in una lista
lsort ?opzioni? lista Ordina una lista. Le opzioni più comuni sono:
-ascii (ordina con logica lessicografica)
-integer (converte egli elementi in numeri interi ed ordina in base al valore numerico)
-increasing (ordina in senso crescente)
-decreasing (ordina in senso decrescente)
linea.gif
LAVORIAMO CON LE STRINGHE
In tcl qualsiasi cosa che proponiamo all'interprete viene considerata una stringa. Anche i numeri sono stringhe fino a che non vengono usati con il comando expr. Solo in questo caso vengono valutati per quello che sono realmente, cioè numeri, e quindi usati per eseguire operazioni matematiche. Premesso ciò, visto che tcl è come il Perl un linguaggio rivolto alla gestione di dati in formato testuale, il programmatore dispone di una nutrita gamma di comandi per la manipolazione delle stringhe. string, split e join sono quelli più usati. Il primo è usato per confrontare due stringhe oppure per compiere speciali operazioni su di esse. String ha la seguente sintassi

string opzione argomento ?argomento ...?
Le opzioni più comuni sono compare, length, match e trim. Ce ne sono molte altre ma per una disamina più dettagliata consiglio di far riferimento al manuale (lo spazio è tiranno, ahimè). La prima, compare ("string compare stringa1 stringa2"), esegue una comparazione carattere per carattere di due strighe. Il confronto avviene secondo logica lessicografica ovvero secondo l'ordine dei caratteri nel dizionario. Il risultato può essere -1, 0 o 1 rispettivamente se stringa1 è minore, uguale o maggiore di stringa2. Il comando "string length stringa" ritorna il numero di caratteri di una stringa. L'opzione trim ("string trim stringa ?caratteri?") restituisce la stessa stringa senza i caratteri specificati che si trovano all'inizio o alla fine della stringa stessa. In sostanza "tagliamo" dalla stringa i caratteri iniziali e finali che non ci interessano. Se l'argomento caratteri non viene specificato, dalla stringa verranno eliminati i blank (spazi, tab, ritorni a capo). Infine il comando string associato all'opzione match ("string match modello stringa") permette il confronto di una stringa con un modello. Se il riscontro ha successo il comando restituisce 1 (per esempio nel caso in cui modello e stringa siano uguali), 0 in caso contrario. E' interessante osservare come il modello di confronto possa contenere alcuni caratteri speciali che possiamo considerare come operatori di matching. L'asterisco * riscontra qualsiasi sequenza di caratteri nella stringa. Il punto di domanda ? matcha (orribile questo termine, ma dà l'idea del successo dell'operazione di riscontro) un singolo carattere. Con le parentesi quadre ([abcd] oppure [a-d]) possiamo forzare il riscontro di un set di caratteri, mentre con il backslash \ riscontriamo un singolo carattere. Il backslash inoltre è indispensabile per matchare (questo è ancora più orribile, sigh!) proprio i carattrei * ? [ ] e \ togliendo loro il significato speciale che possiedono.

string compare "abcdefgh" "ilm"
# ritorna -1
# infatti "ilm" è più grande di "abcdefgh"
# e questo perché ha dei caratteri che nel
# dizionario occupano una posizione superiore
string compare "abcd" "abcd"
# ritorna 0
# infatti le due strighe sono
# lessicograficamente uguali
string compare "z" "a"
# ritorna 1
puts [string trim ".abc." .] 
# stampa abc
set nome agilulfo
if {[string length $nome] > 5} {
  puts "Nome lungo"
} else {
  puts "Nome corto"
}
# stampa Nome lungo
set numero "041-123456"
if {[string match 041* $numero]} {
  puts "Prefisso di Venezia"
} else {
  puts "Prefisso fuori Venezia"
}
# stampa Prefisso di Venezia

Proseguiamo. Il comando split viene usato in questo modo:
split stringa ?caratteri separatori?
e ritorna una lista i cui elementi sono le porzioni della stringa divise dai caratteri separatori. Se l'argomento caratteri è una stringa vuota, allora ogni carattere della stringa viene considerato carattere separatore, mentre se viene omesso, per default il carattere separatore sarà lo spazio. Tra l'altro se la stringa contiene dei caratteri separatori adiacenti verrà generato in corrispondenza di quei caratteri un elemento vuoto della lista.

puts [split root:0:0 :]
# stampa root 0 0
puts [split www.linux.org .]
# stampa www linux org 
puts [split "Ciao, mondo" {}]
# stampa c i a o , {} m o n d o
puts [split "tizio caio sempronio"] 
# stampa tizio caio sempronio
split "rosso||verde||blu" |
# ritorna "rosso {} verde {} blu"

Per finire vediamo come funziona il comando join. Si tratta dell'esatto contrario del comando split, per cui invece di creare una lista spezzando una stringa, crea una stringa a partire da una lista.
join lista ?caratteri di unione?
Se l'argomento "caratteri di unione" viene omesso, la stringa risultante sarà composta da ogni singolo elemento della lista separato da uno spazio.

puts [join [split "www.sun.com" .] \0]
# stampa wwwsuncom
set lista {www diemme it}
set url [join $lista .]
puts http://$url
# stampa http://www.diemme.it

linea.gif
TEORIA E TECNICA
Installare Tcl/Tk

L'installazione di tcl/tk su un pc Linux di recente distribuzione (slackware 3.0 o redhat 4.0 e successive) non presenta alcuna difficoltà. Tutti gli esempi degli articoli sono testati con tcl 7.6 e tk 4.2 ai quali è stata applicata l'ultima patch disponibile. Il codice proposto è tuttavia compatibile con la versione precedente e successiva di tcl. I sorgenti sono liberamente scaricabili dal sito http://sunscript.sun.com dove possiamo prelevare i file tcl7_6p2.tar.gz e tk4_2p2.tar.gz, due archivi tar compressi con gzip. Ci logghiamo come root e supponendo di aver scaricato il file compresso nella home directory eseguiamo i comandi:
tar -zxf tcl7_6p2.tar.gz
tar -zxf tk4_2p2.tar.gz
I pacchetti verranno decompressi rispettivamente in ~/tcl7.6 e ~/tk4.2 creando per entrambi una nuova struttura di directory in cui sono contenuti i sorgenti in C. Entriamo in ~/tcl7.6/unix ed eseguiamo i comandi
configure
make
make install
Facciamo la stessa cosa nella directory ~/tk4.2/unix Al termine delle compilazioni troveremo nella directory /usr/local/lib i file di libreria di tcl/tk mentre i binari saranno posizionati in /usr/local/bin Non ci resta che creare due link simbolici in /usr/bin con i comandi
ln -sf /usr/local/bin/tclsh7.6 /usr/bin/tclsh
ln -sf /usr/local/bin/wish4.2 /usr/bin/wish
e settare le variabili d'ambiente TCL_LIBRARY e TK_LIBRARY scrivendo nel file /etc/profile o ~/.profile le righe
export TCL_LIBRARY=/usr/local/lib
export TK_LIBRARY=/usr/local/lib
linea.gif
NEL PROSSIMO NUMERO
Procedure, espressioni regolari, i/o su file e chiamate di sistema. Saranno questi gli argomenti del prossimo articolo. Concluderemo in bellezza questa introduzione abbastanza dettagliata del linguaggio tcl e poi via libera alla grafica con tk. A presto...

fire.gif

Data creazione HTML: Marzo 1999
Autore: Francesco Munaretto
E-mail: NoSpam@thank.you
exclaim.gif