Tcl est un langage de commandes qui ne manipule que des chaînes de caractères [string-based command language]. Ce langage n'a que quelques constructions fondamentales et est donc facile à apprendre.
Mécanisme de base: chaînes de caractères et substitutions.
command arg1 arg2 ?arg3? ... ?args?
L'évaluation d'une commande se fait en deux temps: l'interprétation [parsing] puis l'exécution.
Lors de l'interprétation, Tcl applique les règles décrites plus bas pour séparer les mots et appliquer les substitutions si nécessaire. A ce stade, aucune signification n'est attribuée à aucun mot.
Lors de l'exécution, Tcl traite le premier mot comme la commande, vérifiant si elle est définie. Si tel est bien le cas, Tcl invoque cette commande, passant tous les mots qui suivent comme arguments de la commande. C'est la commande qui est libre d'interpréter les arguments comme elle le veut.
Une variable est constituée de lettres, chiffres et/ou "souligné". Exemple: var_3, le_fichier. tclsh et wish ont un certains nombre de variables prédéfinies.
Il s'agit des variables précédées du $. Dans ce cas, Tcl remplace l'argument par la valeur de la variable précédée du $.
set var_1 20 => 20 set var_2 $var_1 => 20
Ces substitutions peuvent avoir lieu n'importe ou dans un mot. Exemple: new_$var_1 correspond à new_20. Ici l'exemple choisi ne pose pas d'ambiguïté car la substitution a lieu à la fin du mot. Si il y a ambiguïté, on inclura la partie à substituer entre {}. Exemple: ${var_1}_new correspond à 20_new alors que $var_1_new n'existe pas.
set kg 6 => 6 set pounds [expr $kg*2.2046] => 13.2276
Il s'agit de commandes et arguments entourés de crochets [command arg1 arg2...]. Lorsque l'interpréteur de Tcl rencontre des crochets, il remplace la chaîne entre crochets par le résultat de la commande correspondante. Comme dans le cas des variables, cette substitution peut avoir lieu n'importe où dans un mot.
Le caractère \ est utilisé:
Pour éviter que Tcl ne donne une signification particulière à un caractère donné, il existe encore deux autres possibilités.
Les guillemets suppriment l'interprétation des séparateurs de mots et de commandes (espace, tabulations, point-virgule et retour à la ligne) mais préservent les substitutions de variables ($) et de commandes ([]) ainsi que du \.. Pour inclure un " dans une chaîne, utiliser \"
set message "Abonnement revue: 45\$/an\nAbonnement journal: 27\$/an" => Abonnement revue: 45$/an => Abonnement journal: 27$/an
Les accolades suppriment toute interprétation et/ou substitution.
set message {Abonnement revue: 45$/an Abonnement journal: 27$/an} => Abonnement revue: 45$/an => Abonnement journal: 27$/an
Les accolades sont surtout utiles pour différer les évaluations (voir par exemple plus bas la commande if).
Si le premier caractère non blanc d'une ligne est un #. Ce caractère et tout ce qui suit sur la ligne sera considéré comme un commentaire. Pour ajouter un commentaire en fin de ligne, utiliser ;# (le ; terminant la commande, la suite sera aussi considérée comme commentaire).
# Ceci est un commentaire set temp 21 ;# On definit ici la valeur de la température => 21
Comme toutes les variables sont des chaînes de caractères, il existe une commande spéciale pour les expressions mathématiques: expr.
expr 7.2/3 => 2.4 set len [expr [string length Hello] + 7] => 12 set pi [expr 2*asin(1.0)] => 3.14159
Par défaut, la précision des flottants est de 6 digits. Cette précision peut être modifiée en affectant une valeur à la variable tcl_precision.
expr 1./3.0 => 0.333333 set tcl_precision 17 => 17 expr 1./3.0 => 0.33333333333333331
La liste des opérateurs arithmétiques est reprise dans une table.
Il existe aussi une liste des fonctions mathématiques faisant partie de Tcl.
Les difficultés des nouveaux utilisateurs de Tcl seront évitées s'ils ont une bonne compréhension des mécanismes de substitution: quand elles ont lieu ou non.
Pour rappel:
Problème classique :
exec rm [glob *.o]
La commande rm essaye d'effacer une liste de fichiers qui correspond pour elle à une seul paramètre, c'est-à-dire comme si c'était une seul fichier (par exemple: rm "a.o b.o c.o"). Or dans le cas présent on souhaite une seconde interpétation et c'est ce rôle que remplit la commande eval:
eval exec rm [glob *.o]
A ce stade, invoquez tclsh et entraînez-vous à bien comprendre les mécanismes de substitution.
set varName ?value?
La commande set permet d'attribuer une valeur à une variable, de modifier cette valeur et, en mode interactif, d'en connaître la valeur.
set a {Il y a 4 ans} ;# Ou set a "Il y a 4 ans" => Il y a 4 ans set a => Il y a 4 ans set a 12.6 => 12.6
unset varName ?varName2 varName3 ...?
La commande unset détruit la ou les variables.
unset a
append varName value ?value2 ...?
La commande append ajoute la ou les valeurs à la variable existante; "append a $b" équivaut à "set a $a$b".
set x Ici => Ici append x " et là" => Ici et là
incr varName ?increment?
La commande incr incrémente le contenu de la variable de la valeur de l'incrément. Si l'incrément est omis, il vaut 1. La valeur de la variable et de l'incrément doivent être des chaînes de caractères correspondant à des nombres entiers. Cette commande est utile pour les boucles
set x 23 incr x 22 => 55
puts ?fileId? string
La commande puts est utilisée pour imprimer la chaîne string dans le fichier dont l'identificateur est fileId; par défaut, fileId correspond à stdout. Puts reconnaît par défaut stdin, stdout et stderr.
format formatString ?arg arg ...?
La commande format permet de formater l'impression comme sprintf.
set a 24.236784; set b secondes puts [format "a = %5.3f %s" $a $b] => a = 24.237 secondes
glob [-nocomplain] pattern ?pattern?
La commande glob renvoie la liste de tous les fichiers du répertoire courant dont le nom correspond au pattern (de type csh).
glob *.c => amigastu.c dldmem.c dlimage.c dosstuff.c
Ne pas oublier que le résultat de glob est une liste. Ceci pourra par exemple être utilisé pour manipuler un ensemble de fichiers:
foreach file [glob *.c] { puts $file # On peut ici manipuler les fichiers à souhait } => amigastu.c => dldmem.c => dlimage.c => dosstuff.c
info exists varName
La commande info exists permet de savoir si une variable est définie; elle renvoie 1 si la variable existe déjà et 0 dans le cas contraire.
set a 22; set b Température info exists a => 1 info exists x => 0 # Initialisons a, si ce n'est déjà fait if [info exists a] {set a 0}
Plus largement, la commande info permet un grand nombre d'opérations. Deux d'entre elles peuvent être aussi très utiles: info vars (donne la liste des variables actuellement définies) et info procs (donne la liste des procédures actuellement définies).
Outre les variables, Tcl a aussi la notion de liste et de vecteur [array].
Une liste est en ensemble de chaînes de caractères séparées par des blancs.
set a {a b c} => a b c
Pour des cas plus complexes, la commande liste sera bien utile. Pour bien comprendre, on peut comparer:
set my_list [list $a 3 4 a abc] => {a b c} 3 4 a abc set mylist "$a 3 4 a abc" => a b c 3 4 a abc set my_list {$a 3 4 a abc} => $a 3 4 a abc
Les listes sont utilisées pour des ensembles qui peuvent ainsi être passés comme un seul argument à une procédure. Tcl fournit différentes commandes pour la gestion des listes.
Par exemple, si on veut connaître le nombre d'éléments dans une liste:
llength $my_list => 5
Une array est une variable avec des indices qui sont des chaînes de caractères. Comme l'indice n'est pas entier, on parle souvent de associative array.
Les arrays se manipulent comme des variables simples.
set a(color) blue => blue set color_now $a(color) => blue
Comme pour les listes, Tcl fournit la commande array avec différentes options pour la gestion des arrays. La commande la plus utilisée est probablement array names qui retourne la liste des indices de l'array.
set a(size) 25 => 25 puts $a(size) => 25 array names a => size color
Si le nom d'une array est une variable, inclure le nom de la variable entre {} et passer par une interprétation de commande pour accéder au contenu:
set var a => a set ${var}(color) green => green puts ${var}(color) => a(color) puts [set ${var}(color)] => green set name color => color puts ${var}($name) => a(color) puts [set ${var}($name)] => green
Les arrays sont utiles pour regrouper les variables qui devront être passées ensemble à une procédure; elles augmentent aussi la clarté du code. Attention toutefois: une variable et une array ne peuvent avoir le même nom alors que les procédures et les variables peuvent avoir les mêmes noms.
Les variables d'environnement sont toutes dans l'array env.
array names env => LD_LIBRARY_PATH MANPATH HOME XUSERFILESEARCHPATH (...) PATH TZ USER EDITOR info exists env(DISPLAY) ;# On teste si la variable DISPLAY a bien été définie. => 1 puts $env(DISPLAY) => slsun2.epfl.ch:0.0
Dans le langage Tcl, les structures de contrôle, de même que les procédures et les expressions, sont interprétées comme des commandes ordinaires.
Les noms des structures de contrôle sont similaires à ceux du C et du C++. Dans les exemples qui suivront, on ajoutera souvent des accolades par pur question de style et de clarté. Toutefois, certaines de ces accolades sont importantes pour une interprétation différée!
if {test1} {corps1 ... } elseif {test2} {corps2 ... } else {corps3 ... }
test1 est évalué comme une expression booléenne: si sa valeur est non nulle, corps1 est exécuté comme un script Tcl et retourne sa valeur, sinon on passe à test2 et ainsi de suite.
while {test} {corps ... }
Aussi longtemps que le test est vrai, corps sera exécuté.
Attention ici à l'utilisation des accolades: souvent le test doit être inclus dans des accolades car sinon on peut avoir des boucles infinies. Exemple:
set i 10 while $i>0 {!!! Erreur classique puts $i incr i -1 }
Cette boucle sera infinie car $i sera évalué avant d'être passé à la commande while et vaudra donc toujours 10. L'écriture correcte est celle qui suit.:
set i 10 while {$i>0} { puts $i incr i -1 }
for {init} {test} {reinit} {corps ... }
Après avoir exécuté init, test est évalué; si test est vrai, corps est exécuté puis reinit; test est alors à nouveau évalué et ainsi de suite. Par exemple,
for {set i 0} {$i < 10} {incr i} {puts $i}
foreach varName list {corps ... }
Pour chaque élément de list (en respectant l'ordre), varName prend la valeur de cet élément et corps est exécuté. Par exemple,
foreach i [array names arr] { puts stderr "$i $arr($i)" }
Dans les boucles while, for et foreach:
switch ?options? string { pattern1 {corps1} pattern2 {corps2} ... default {corps def} }
La commande switch compare string à différents pattern et, en cas de correspondance (matching) exécute le corps correspondant; si le string ne correspond à aucun des patterns, default est exécuté, à condition qu'il se trouve en dernière position!
Les options possibles sont:
Si l'un des corps est remplacé par - (le caractère tiret, mais sans espaces autour si on met des accolades) switch exécutera le corps du pattern qui suit.
Une procédure est définie de la manière suivante:
proc name params {corps ... }
Les noms des procédures sont tous définis à un niveau global. Par contre, les variables ont un champ d'application local qui dépend du niveau de profondeur de la procédure. Les variables à l'extérieur des procédures sont globales.
Dans une procédure, pour dire qu'une variable est globale, on utilise la commandeglobal.
global varName1 ?varName2 ...?
Ainsi, dans la procédure en cours, et uniquement pendant que celle-ci est exécutée, varName1 et varName2 seront référencées au niveau global.
L'utilisation des arrays simplifie l'écriture lorsqu'on souhaite que plusieurs variables soient globales.
Pour passer une variable à une procédure, par référence plutôt que par valeur, on utilise la commande upvar. C'est principalement le cas pour les arrays. La commande upvar associe une variable locale avec une variable d'un autre scope.
upvar ?level? otherVar localVar ?otherVar2 localVar2 ...?
Exemple d'une procédure proche de incrmais qui évite une erreur si la variable n'a pas été prédéfinie. Ici on l'initialise par défaut à zéro. Cette nouvelle procédure incr permet aussi les flottants.
proc incr {varName {amount 1} } { upvar $varName var if [info exists var] { set var [expr $var + $amount] } else { set var $amount } return $var } set a 123.56 puts $a => 123.56 incr a 100 puts $a => 223.56
La commande uplevel est un croisement entre upvar et eval. Comme eval, elle évalue ses arguments comme s'il s'agissait d'un script mais, comme upvar, dans un champ d'application différent.
uplevel ?level? arg ?arg ...?
Tous les arguments sont concaténés comme s'ils avaient été passés à concat; le résultat est ensuite évalué dans le contexte spécifié par level et uplevel retourne la résultat de cette évaluation.
Par exemple, la commande suivante incrémentera la valeur de la variable x du scope appelant:
uplevel {set x [expr $x +1]}
La commande info level permet de savoir à quel niveau on se trouve dans le stack d'appel de Tcl. Elle est surtout utile pour debugger.
info level ?number?
Les chaînes de caractères étant la base de Tcl, il est normal qu'il existe plusieurs commandes permettant des les manipuler. La commande string a la syntaxe générale suivante:
string operation stringvalue ?otherargs ...?
C'est à dire que le premier argument détermine ce que fait string et que le second argument est la chaîne de caractères. D'autres arguments peuvent être nécessaires, dépendant de l'opération à effectuer. Certains arguments correspondent à des indices dans la chaîne; le premier indice d'une chaîne est zéro; end correspond à l'indice du dernier caractère.
string range abcd 1 end => bcd
Les options de la commande string sont nombreuses.
Comparer des chaînes de caractères est possible en utilisant les opérateurs de comparaison. Toutefois, il faut être prudent à plusieurs niveaux. Tout d'abord, il faut mettre la chaîne de caractères entre " " de façon à ce que l'évaluateur de l'expression puisse l'identifier à une information de type string. Ensuite il faut mettre le tout entre accolades de façon à préserver les quotations qui sinon seraient otées par l'interpréteur.
if {$x == "toto"}
Mais ceci n'est pas toujours suffisant car l'évaluateur convertit d'abord la chaîne en nombre, si c'est possible, puis la reconvertit lorsqu'il remarque qu'il s'agit d'une comparaison de chaînes. Ainsi, des situations surprenantes peuvent se produire dans le cas des nombres octal ou hexadécimal.
if {"0xa" == "10"} {puts stdout Attention!} => Attention!
Ainsi, la seule solution parfaitement sûre est d'utiliser string compare. Elle est aussi un peu plus rapide.
if {[string compare $a $b] == 0} {puts stdout "Ces chaînes sont égales"}
La commande append ajoute le ou les arguments à la fin de la variable mentionnée. Si cette variables n'était pas définie, elle est considérée comme vide.
set xyz toto append xyz a bc d => totoabcd
La commande scan est équivalente à sscanf en C. Elle extrait d'une chaîne de caractères les valeurs suivant un format donné et les redistribue dans les variables correspondantes.
scan string format varName ?varName2 ...?
Le format de scan est presque celui de la commande format; %u n'existe pas. On peut aussi accéder à un ensemble de caractères en utilisant les crochets.
scan abcABC {%[a-z]} result puts $result => abc
Les expressions régulières sont la façon la plus puissante d'exprimer des patterns. Un pattern est une séquence de caractères littéraux, de caractères de match, de clauses de répétition, de clause alternative ou de subpattern groupés entre parenthèses. La syntaxe des expressions régulières est résumée dans un tableau.
Quelques exemples:
[Hh]ello: match Hello ou hello .. : toute suite de 2 carcatères quelconques [a-d] : tous les caractères compris entre a et d (inclus) [^a-zA-Z] : tous les caractères sauf les lettres majuscules ou minuscules
La commande regexp fournit un accès direct pour matcher les expressions régulières.
regexp ?switches? pattern string ?match sub1 sub2 ...?
La commande regsub permet d'éditer une string sur la base du pattern matching.
La commande exec est utilisée pour exécuter des programmes unix à partir de scripts Tcl (Tcl fork un process pour exécuter le programme unix dans un sous-process).
set d [exec date] => Wed Mar 15 14:59:28 MET 1995
La commande exec supporte toute la syntaxe des redirections et pipeline pour les I/O.
set n [exec sort < /etc/passwd | uniq | wc -l 2> /dev/null] => 107
La commande file a plusieurs options qui permettent de vérifier l'état de fichiers dans l'environnement unix tels que leur existence, les droits d'autorisation, etc.
Les commandes concernant les I/O de fichiers sont résumée dans un tableau.
L'ouverture de fichier pour input/output se fait à l'aide de la commande open.
open what ?access? ?permissions?
Exemple:
set fileId [open /tmp/toto w 600] puts $fileId "Hello, toto!" close $fileId
Un autre exemple plus prudent (voir catch plus loin):
if [catch {open /tmp/data r} fileId] { puts stderr "Cannot open /tmp/data : $fileId" } else { # Read what you want close fileId }
Enfin un exemple avec pipe:
set input [open "|sort /etc/passwd" r] set contents [split [read $input] \n] close $input
source filename
La commande source permet d'exécuter un fichier comme un script Tcl. Le résultat de source sera le résultat de la dernière commande de filename.
La commande eval est utilisée pour réévaluer une chaîne de caractères comme une commande.
set res {expr $a/2} => expr $a/2 set a 25.2 eval $res => 12.6 set a 2000 eval $res => 1000
Cette commande peut être très utile dans certains cas et très perturbante dans d'autres. En effet, il faut être attentif aux substitutions, quoting, etc. Toutefois, si des commandes sont construites dynamiquement, il faudra utiliser eval pour les interpréter et les exécuter. Il sera souvent judicieux de passer par une liste pour éviter de nombreux problèmes. Cette approche résout le problème des $ et des espaces.
set string "Hello, world!" set cmd [list puts stdout $string] => puts stdout {Hello, world!} eval $cmd => Hello, world!
Certaines commande peuvent générer des erreurs (par exemple si on essaye d'ouvrir un fichier qui n'existe pas, ou auquel on n'a pas accès, etc.). Pour éviter que Tcl ne déclenche effectivement une erreur et n'arrête l'exécution, on peut utiliser la commande catch:
catch command ?resultVar?
La façon propre d'écrire le code d'ouverture d'un fichier serait alors:
if [catch {open /tmp/tst r} result] { puts stderr "Erreur de open: $result" } else { puts "L'ouverture a bien fonctionné et le fileid de /tmp/tst est $result" }
Pour faciliter le débogage, Tcl possède une variable d'erreur, errorInfo, qui est définie par l'interpréteur. En cas d'erreur, cette variable contient la trace de l'état de l'exécution au moment de l'erreur. Le contenu de cette variable peut être imprimé en cas d'erreur. Un exemple:
if [catch {command1; command2} result] { global errorInfo puts stderr $result puts stderr " --- Tcl TRACE --- " puts stderr $errorInfo } else { puts stderr "No error" }
Si le concepteur souhaite interrompre une script et remplir la variable errorInfo pour informer l'utilisateur de l'erreur, il peut faire appel à la commande error.
error message ?info? ?code?