Programmation Tcl/Vos premiers pas en Tcl
Comment j'ai divisé par 10 le nombre de lignes de code de mes programmes !
Auteur de la version initiale : Arnaud LAPREVOTE
Introduction
modifierPourquoi un cours sur le tcl/tk ?
modifierJe me présente, je m'appelle Henri. Euh non, Arnaud LAPREVOTE.
Il était une fois un programmeur qui avait passé des milliers d'heures à programmer en C, particulièrement des applications de calculs scientifiques (traitement vidéo). Avec les années, il avait acquis une grande facilité dans l'écriture des programmes C, et en même temps une certaine impatience.
Autant le C le satisfaisait pour le calcul scientifique, autant dès qu'il fallait traiter des fichiers ou des chaînes de caractères, il trouvait que c'était fastidieux et surtout générateur d'erreurs de manière inacceptable.
Comme tout le monde, il avait entendu parler des langages de script genre Perl, tcl/tk, et autres (python). À l'occasion de l'installation de son PC sous Linux, il prit le temps de commencer à programmer en tcl.
Et ce fut un choc. Plus de pointeurs, plus de gestion-mémoire, de l'idée au programme en un minimum de lignes. Tout cela trivial à apprendre, gratuit, qui peut être redistribué, fonctionnant sous Unix (tous les Unix) et sous Windows. Capable de faire des interfaces graphiques de manière très facile, de la programmation CGI en plaisantant. Facile à expliquer. Génial.
Ce programmeur, c'est moi. Et j'ai très envie de vous faire partager cette passion pour mon langage préféré.
Et la concurrence ?
modifierIl y a de très nombreux concurrents au Tcl/Tk :
- Perl,
- Python,
- Scheme (LISP),
- dans une certaine mesure PHP,
- Visual Basic,
- Java.
Tous ces langages ont des avantages et des inconvénients. Mes critères de choix principaux sont :
- open source (source disponible, gratuite et qui peut être redistribuée),
- lisibilité (facilité à maintenir et déboguer),
- multiplateforme (Unix, Linux, Windows),
- grand nombre d'extensions.
Un dernier avantage est que le Tcl n'a pas une ambition infinie. Le Tcl ne veut pas TOUT faire. C'est juste un langage de "colle" pour faire tenir un ensemble d'application ensemble. Si vous voulez faire des choses très grosses ou très complexes ou très rapides, vous êtes priés de vous tourner vers le C, le C++ ou le java. À cette lumière, il ne reste que le python et le tcl, à la limite le Perl. La syntaxe du Perl me rend fou, donc je l'exclus. Je m'intéresse au Python.
Autres avantages
modifierLes points suivants méritent d'être soulignés :
- fonctions réseau (socket) intégrées très élégamment au langage. Un serveur Web se fait en claquant des doigts,
- facilité d'intégration du tcl dans une application existante,
- très grande robustesse du langage,
- facilité d'intégration de fonctions C dans le Tcl,
- la compatibilité ascendante n'est pas une théorie, mais une réalité,
- très forte cohérence due à une origine universitaire.
Inconvénients
modifierTout n'est pas parfait en tcl.
- le langage n'est pas en GPL => moins grande dynamique du langage que le python ou le Perl,
- il n'est pas possible de définir de vraies structures en tcl (au sens C du terme). Cela peut nuire à la lisibilité des programmes et limite la taille de ce que l'on peut programmer. On peut se tourner vers les extensions objet du tcl pour avoir ces fonctions,
- il manque un IDE libre avec un débogueur intégré pour faciliter la prise en main par les débutants.
Historique
modifierTcl fut créé en 1990 par John OUSTERHOUT à l'Université de Berkeley. C'est un langage de "collage" pour attacher ensemble plusieurs applications. C'est un langage interprété, mais compilé à la volée depuis la version 8.0. La version actuelle (en janvier 2016) est Tcl 8.6.4.
Après Berkeley, John Ousterhout est passé chez Sun, puis il a créé sa propre société Scriptix qui est devenue ensuite Ajuba Solutions et a été rachetée récemment. Des centaines de programmes et de société utilisent le tcl, mais souvent de manière souterraine. Tcl est donc un langage discret.
Ressources utiles
modifierSites Web
modifier- Le père de tous les sites : http://www.tcl.tk/ .
- Une foultitude d'informations se trouvent sur : http://wiki.tcl.tk/ .
- Le site Web de francophone La Rochelle Innovation consacré au tcl/tk : http://www.larochelle-innovation.com/tcltk/ .
- Pour charger tcl/tk : http://www.activestate.com/Products/Download/Register.plex?id=ActiveTcl : ne pas remplir le formulaire et cliquer sur "Download".
- Toute la documentation et plus sur : http://www.linbox.com/ucome.rvt?file=/any/doc_distrib/tcltk-8.3.2/index.html .
Les livres
modifier- "Practical Programming in Tcl/Tk" ISBN: 0-13-038560-3 par Brent Welch <welch@acm.org>, Ken Jones, et Jeff Hobbs ; en partie en ligne sur "http://www.beedub.com/book/" : la bible incontournable.
- "Graphical Appications with Tcl&Tk" ISBN : 1-55851-471-6 par Eric F.Johnson : un très bon livre pour commencer.
- "TCL/TK Apprentissage et référence" ISBN : 2-7117-8679-X par Bernard Desgraupes. Je l'ai seulement feuilleté, je suis très vexé de ne pas l'avoir écrit.
Vos premiers pas en Tcl
modifierLe premier pas
modifierPour démarrer un interpréteur tcl, tapez :
tclsh
Alternativement, sous Windows, allez dans le menu "démarrer", déroulez-le sous menu tcl puis cliquez sur tclsh ou wish (plutôt wish).
Vous obtenez alors un invite de commande en %. Taper les lignes suivantes :
% set mytext "Wikilivre pour apprendre Tcl"
Wikilivre pour apprendre Tcl
% puts $mytext
Wikilivre pour apprendre Tcl
% set i 0
0
% puts $i
0
% incr i
1
% string toupper $mytext
WIKILIVRE POUR APPRENDRE TCL
Les points clés de cet exemple sont :
COMMANDES
set nom_de_variable "valeur"
puts "chaîne de caractères"
incr nom_de_variable_numérique [incrément]
Le deuxième pas
modifier% for { set i 0 } { $i < 3 } { incr i } {
puts $i
puts "$i"
puts [string toupper "Free&ALter Soft : $i"]
}
0
0
FREE&ALTER SOFT : 0
1
1
FREE&ALTER SOFT : 1
2
2
FREE&ALTER SOFT : 2
% #this is a remarque
% set i 0; set j 1; #that also
1
COMMANDES
for { initialisation } { end condition } { incrementation } {
code running at each loop
}
SYNTAXE
command [argument1] [argunment2] first_command; second_command "$substitution" "\$caracter printed as is" [immediate execution] {execute as late as possible} #remarque
La syntaxe du tcl
modifierUn des problèmes du tcl est sa simplicité. Concernant sa syntaxe, il n'y a que 2 choses à savoir.
Le premier mot de la commande est TOUJOURS la commande.
commande argument1 argument2 argument3 ...
Donc en tcl, pour initialiser une variable on écrit :
set toto "xxxx"
ET PAS
toto = "xxxx"
Les arguments de la commande sont séparés les uns des autres par des espaces. D'où obligatoirement :
for {set i 0} {$i < 4} {incr i} { }
Et non pas
for{set i 0}{$i < 4}{incr i}{ }
La clé du tcl : la substitution
modifierIl y a une finesse en tcl. Les substitutions. L'interprétation d'une ligne se fait en 2 temps :
- substitution de tout ce qui est substituable (variable, code entre crochets []),
- exécution de la commande.
Donc :
% set toto "TOTO" TOTO % set tata "$toto" TOTO % set titi "[expr 1 + 2]" 3 % set tutu [string trim [string tolower \ "Phrase avec des espaces : $toto va "]] phrase avec des espaces : toto va % set tutu "[string trim [string tolower \ "Phrase avec des espaces : $toto va "]]" phrase avec des espaces : toto va % puts "--$tutu--" --phrase avec des espaces : toto va--
L'exécution d'un code entre [] et la substitution dans l'expression appelante du contenu de [] par son résultat. C'est ce que l'on appelle de la programmation fonctionnelle. Le LISP est l'archétype de ces langages. Le tcl permet de mélanger élégamment programmation fonctionnelle et procédurale. Dans certains cas (les traitements sur des chaînes de caractères) la programmation fonctionnelle est TRÈS (très, vraiment très, j'insiste encore ? non) pratique.
Pour empêcher la substitution, on utilise les accolades { } :
% set toto {TOTO} TOTO % set tata {$toto} $toto % set titi {[expr 1 + 2]} set titi {[expr 1 + 2]} % set tutu [string trim \ [string tolower "Phrase avec des espaces : $toto va "]] phrase avec des espaces au bout : toto va % set tutu {[string trim \ [string tolower "Phrase avec des espaces : $toto va "]]} [string trim [string tolower "Phrase avec des espaces au bout : $toto va "]] % puts {--$tutu--} --$tutu--
Pour affiner ces notions, on peut ajouter que le caractère\ (barre oblique inverse), force l'interprétation du caractère suivant comme étant un simple caractère et rien d'autre :
set toto {TOTO} TOTO % set tata "\$toto" $toto % set tata Ce\ qui\ suit\ est\ une\ seule\ chaîne Ce qui suit est une seule chaîne % puts "\[ pas d'interprétation hâtive ]" [ pas d'interprétation hâtive ]
Et que l'on peut utiliser les accolades autour d'un nom de variable pour lever l'ambiguïté :
% set var1 "CONTENU ORIGINE" CONTENU ORIGINE % set var12 "AUTRE CONTENU" AUTRE CONTENU % puts "${var1}2" CONTENU ORIGINE2 % puts "${var12} == $var12" AUTRE CONTENU == AUTRE CONTENU
Si vous avez complètement compris ce qui précédait, alors une friandise (sinon c'est le moment de piquer un roupillon, de papoter avec les voisins, de se taper un carton, de relever ses textos, et de noter qu'il faut relire le paragraphe qui suit dans 15 jours).
L'instruction eval permet de forcer une évaluation supplémentaire, et de temps en temps c'est fantastique (le préprocesseur de tcl est tcl contrairement au C):
#!/usr/bin/tclsh set var1 "Un" set var2 "Deux" set var3 "Trois" set var4 "Quatre" set var5 "Cinq" set var6 "Six" for { set i 1 } { $i < 7 } { incr i } { set command "puts \$var$i" eval $command } Un Deux Trois Quatre Cinq Six
Types de données
modifierLes chaînes de caractères et les scalaires
modifier* scalaire : tcl 7.6 - chaînes seulement => tcl 8.0 - chaînes et valeurs.
Tout est chaîne en tcl : c'est la clé de la facilité d'interaction : toute fonction peut envoyer des résultats à n'importe quelle autre.
%set str1 "0123456789" %string length $str1 10 %string index $str1 5 5 %string range $str1 0 4 01234 % string compare $str1 "101112131415" -1 %proc frame_string { str } { format "###->%<-###" $str } %frame_string "Arnaud LAPREVOTE" ###->Arnaud LAPREVOTE<-### %frame_string "Arnaud LAPREVOTE " ###->Arnaud LAPREVOTE <-### %frame_string [string trimright "Arnaud LAPREVOTE "] ###->Arnaud LAPREVOTE<-###
COMMANDES
string length $a_string string index $a_string index ; #(0 is first) string range $a_string first_index last_index
SYNTAXE
proc function_name { list of args } { instructions return 5 }
Les listes
modifierOn utilise beaucoup les listes en tcl.
set mylist [list "toto et tata" 1 [list 1 2 3] stop] {toto et tata} 1 {1 2 3} stop % puts [llength $mylist] 4 % lindex $mylist 0 toto et tata % lrange $mylist 0 1 {toto et tata} 1 % lsort $mylist 1 {1 2 3} stop {toto et tata} % set mylist [linsert $mylist 1 coucou] {toto et tata} coucou 1 {1 2 3} stop % lappend mylist "why not" {toto et tata} coucou 1 {1 2 3} stop {why not} % puts $mylist {toto et tata} coucou 1 {1 2 3} stop {why not} % split "1,2,3,4,5,6" , 1 2 3 4 5 6
COMMANDES | Description |
---|---|
list first_elt second_elt ... | Création d'une liste. Renvoi une liste. |
llength $a_list | Renvoi le nombre d'éléments de la liste |
lindex $a_list elt_nber | Renvoi l'élément n° elt_nber de la liste. elt_nber peut être end (dernier élément). |
lrange $a_list start_nber end_nber | Renvoi une liste composée des éléments commençants ) start_nber et finissant à end_nber |
lsort $a_liste | Ordonnancement de la liste. De nombreuses options permettent de classer en ordre croissant/ décroissant, en utilisant un élément d'une sous-liste comme clé, en ordre numérique, ... Renvoi une liste |
linsert $a_list nber elt_to_insert | Insère un élément dans une liste à l'endroit indiqué. Renvoi une liste. |
lappend a_list elt_to_append_at_the_end | Ajoute les éléments suivant à la fin de la liste. ATTENTION LAPPEND NE RENVOIE PAS DE LISTE. IL MET AU BOUT DE LA LISTE NOMMÉE a_list LES ÉLÉMENTS. |
split "chaîne de caractère" [caractère] | Transforme une chaîne de caractères en une liste. Le séparateur est le caractère fourni en second paramètre. |
Chaînes et expressions régulières
modifierLes expressions régulières sont une fonction clé des langages de scripts (ksh, Perl, awk, tcl, python, ...). Elles ne sont pas du tout naturelles, mais une fois comprises, elles sont un outil très puissant. Vous devez les essayer !
Une seule méthode pour survivre en United States of Regular Expressions : essayez d'abord, puis programmez. Même une ceinture noire 4ème dan de tcl fait comme cela.
COMMANDES
regexp {sf(first expr)(second expr)} $string \ matching_string first_matching_str second_matching_string
- . n'importe quel caractère,
- * le caractère précédent 0 ou plusieurs fois,
- + preceding character at least once or more,
- [a-zA-Z] character list or range,
- [^a-z] not these characters,
- \$ exactly the character $ forget rules,
- ^ first character of the string,
- $ last character of the string,
- ? matches preceding character once or nothing,
- pattern1|pattern2 matches pattern1 or pattern2.
%set reg "This is a string = 12" This is a string = 12 % regexp {([a-zA-Z]*) *= *([0-9]*)} $reg string var val 1 % puts $string string = 12 % puts $var string % puts $val 12 % set reg "string = 12; # forget the rest" string = 12; # forget the rest % regsub {([a-zA-Z]*) *= *([0-9]*)} $reg \ {and \2 = \1} string 1 %puts $string and 12 = string; # forget the rest
Les tableaux associatifs
modifier%set good(name) "Free&ALter Soft" %set good(first_name) "Laprevote" %set good(sur_name) "Arnaud" %proc puts_array { current_array } { upvar $current_array bad foreach name [array names bad] { puts "$name = $bad($name)" } } %puts_array good first_name = Laprevote name = Free&ALter Soft sur_name = Arnaud
COMMANDES | EXPLICATION |
---|---|
set toto(tata) "string" | Initialisation à string de l'entrée tata dans le tableau toto |
array exists name | Renvoi 1 si le tableau name existe |
array names name | Renvoi la liste des entrées du tableau |
array get name | Liste de paires clé valeur du tableau name |
array set name list | Initialise le tableau name en utilisant une liste à la syntaxe identique au résultat de array get name |
parray name | Affichage du tableau name |
upvar $name name_to_use | Passage d'un tableau par pointeur à une fonction |
proc this_proc { sent_array } { upvar $sent_array array_to_use parray $array_to_use } this_proc toto
Commandes de contrôle
modifierConditions, boucles, contrôle de l'exécution
modifierif { condition } { #code à exécuter si la condition est vraie } elseif { condition2 } { # code à exécuter si la condition2 est vraie } else { # code à exécuter si aucune condition n'est vraie }
while { condition } { # code à exécuter tant que la condition est vraie }
switch valeur { value1 { #code à exécuter si valeur remplie la condition value1 } value2 { #code à exécuter si valeur remplie la condition value2 } default { #code à exécuter si aucune des conditions précédentes n'est vraie } }
Les options -exact -glob et -regexp permettent de choisir le type de règle de comparaison utilisé. Pour distinguer les options de switch de l'argument final de switch on utilise -- :
switch -exact -- $toto { 1 { puts 1 } }
Si l'on est en mode -exact de switch, on cherche la section de switch dont la valeur est strictement identique à l'argument de switch.
Si l'on est en mode -glob, alors * remplace n'importe quel caractère zéro ou plusieurs fois. Donc :
set toto test switch -glob -- $toto { t* { puts "Mode test" } default { puts "Autre chose" } }
Enfin en mode regexp, on utilise un mode de comparaison de type expression régulière.
set toto test switch -regexp -- $toto { [tT].* { puts "Mode test" } default { puts "Autre chose" } }
Un exemple plus complet :
set i 0 while { $i < 200 } { switch -exact -- $i { 0 { puts "Je ne vois pas de mouton" } 1 { puts "Whoua un mouton là" } 100 { puts "T'en a pas marre des moutons ?" puts "Tape Ctrl-c pour arrêter du plouc !" } default { puts "$i moutons" } } incr i } puts "J'ai une indigestion de mouton," puts "plus le mal de mer et la tête lourde" puts "avec une grosse envie de dormir. J'arrête."
for { # code d'initialisation } { condition } \ { # passage à l'état suivant (typ. incrémentation } { # code à exécuter }
Exemple
for { set i 1 } { $i < 100 } { incr i } { if { $i == 1 } { set pluriel "" } elseif { $i == 57 } { puts "Un mosellan" set pluriel "s" }else { set pluriel "s" } puts "$i mouton${pluriel}" }
Enfin, il ne faut pas oublier l'instruction foreach. Cette instruction permet de boucler sur les éléments d'une liste.
set l [list lundi mardi mercredi jeudi vendredi samedi dimanche] foreach jour $l { switch -regexp -- $jour { ^[lmmjvs].* { puts "Le $jour on bosse" } default { puts "Le $jour on bulle" } } }
Fonctions et procédures
modifierLa commande permettant de définir une procédure est proc. C'est une commande comme une autre qui prend 3 arguments :
proc nom_de_la_procedure { liste des arguments } { # code a exécuter quand la procédure est appelée. # Les valeurs des variables $liste $des et $arguments sont disponibles # On peut avoir accès aux variables défini au niveau 0 de l'exécution # avec l'instruction global # upvar permet de passer des variables par pointeur # on retourne une valeur avec : return 1 }
Exemple :
#!/usr/bin/tclsh set DEBUG 1 proc debug { message } { global DEBUG if $DEBUG { puts $message } } proc read_file { filename } { if { [catch { set fileid [open $filename] }] } { puts "Impossible d'ouvrir $filename" return "" } debug "Le fichier $filename est ouvert" set full_text [read $fileid] # Je vais renvoyer la liste des lignes du fichiers return [split $full_text "\n"] } set cour1_list [read_file "cour1.txt"] puts "$cour1_list"
Entrées/sorties et gestion des erreurs
modifierEntrées/sorties
modifierLes commandes sont les suivantes : ||open gets seek flush close read tell puts file
La commande open retourne un identifiant qui sera utilisé lors des appels aux autres commandes. Ex:
set f [open "toto.txt" "r"] => file4 set toto [read $f] => xxxxxx close $f
Ou encore :
set f [open "titi.txt" w] => file4 puts $f "Ecrit ce texte dans le fichier" # puts permet d'écrire dans un canal déterminé (défaut sortie standard) close $f
Les autres commandes utiles sont :
# lecture d'une ligne set x [gets $f] # read permet de lire un certain nombre d'octets read $f 100 # seek pour se positionner set f [open "database" "r"] seek $f 1024 read $f 100 Ici on lit les octets 1024 a 1123
Gestion des erreurs
modifierLa commande catch permet d'attraper les erreurs :
if [catch { n'importe quoi}] { puts "Vous avez dû taper une bêtise dans la commande appelée par catch" exit }
Tout cela est bien sûr très utilisé lors de l'ouverture d'un fichier en lecture ou en écriture et plus généralement dès que l'on communique avec l'extérieur.
catch { exec cp toto tutu }
La commande error permet elle de générer une erreur dans un code et d'y associer un message d'erreur.
Récapitulatif des commandes du Tcl
modifierCOMMANDES TRES UTILISEES
for incr list regsub close expr foreach llength append concat format load return array gets lrange proc switch file glob lappend lreplace puts break global lsearch set catch eval lindex lsort while exec if linsert open regexp source
MOINS USITEES
clock exit package split unknown after info pid rename string unset fblocked interp pkg_mkIndex subst update continue fconfigure join scan uplevel bgerror eof seek tclvars upvar error fileevent library pwd tell vwait filename history read socket time cd flush trace
Comme vous pouvez le remarquer, cela représente vraiment peu de commandes, ce qui explique la facilité d'apprentissage du tcl.
Pas toujours les mêmes
modifierLecture de fichiers
modifierL'objectif est d'écrire un programme tcl qui parcourra un fichier HTML et donnera la liste des noms entre les tags HTML h1 et /h1 et les variantes de ces tags h2 /h2 et h3 /h3. La liste sera imprimée sur la sortie standard. Il faut tester sur la page suivante : http://www.w3.org/TR/REC-html32.html Sauvegardez cette page dans /tmp sous le nom test.html, puis lancez votre programme dessus.
Lecture des arguments d'un programme
modifierVous souhaitez écrire un programme qui a des options d'appel en ligne de commande. En particulier :
-h[elp]
: affichage d'une aide ;-f[ile] nom_de_fichier
: fichier d'entrée ;-l[evel] [0-9]+
: niveau de recherche de 1 à ce que vous voulez.
Les arguments peuvent être passés sur la ligne de commande dans n'importe quel ordre. À la fin de l'initialisation de la fonction vous avez 3 drapeaux à 1 ou 0 indiquant si les options -help, -file ou -level ont été appelées. Le nom du fichier d'entrée et le niveau sont stockés dans les variables filename et level.
Lors de l'appel d'un programme, les variables suivantes sont disponibles :
- argc : nombre d'arguments sur la ligne de commande (stockée dans argv),
- argv : liste des arguments sur la ligne de commande, sans la commande,
- argv0 : nom de la commande,
- env : tableau contenant les variables d'environnement.
#!/usr/bin/tclsh puts "argc : $argc" puts "argv : $argv" set i 0 foreach arg $argv { puts "argument $i : $arg" incr i } puts "argv0 : $argv0"
Nous appelons cette commande args.tcl et la rendons exécutable puis testons :
args.tcl $ ./args.tcl argc : 0 argv : argv0 : ./args.tcl $ ./args.tcl -f test -level 3 argc : 4 argv : -f test -level 3 argument 0 : -f argument 1 : test argument 2 : -level argument 3 : 3 argv0 : ./args.tcl
Vous allez créer une machine à état. Le passage d'un état à un autre se fait lorsque l'on passe à l'argument suivant. L'état de base de cette machine est :
- check_args : attente d'un argument type -f, -l ou -h.
De cet état, vous allez sauter à l'état suivant lors du test de l'argument suivant, en fonction de la valeur de arg. Si arg est à -f*, alors vous sautez dans l'état is_file, et vous initialisez filename avec $arg. Au passage, vous initialisez le drapeau correspondant à la présence du nom de fichier sur la ligne de commande à 1. Il faut ensuite revenir à l'état check_arg.
Il vous reste juste à prévoir les états correspondants pour help et pour level et à les gérer de même.
Bon courage. Merci de ne pas oublier le guide à la fin de la visite. À votre bon cœur monsieur dame.