Tcl/Tk : Tcl

Anne Possoz

Table of Contents


Tcl/Tk : Tcl

1. Mécanismes fondamentaux

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.

2. Syntaxe

2.1. Commandes

command arg1 arg2 ?arg3? ... ?args?

2.2. Evaluation d'une commande

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.

2.3. Variables

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.

2.4. Substitution des variables:$

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.

2.5. Substitution des commandes:[]

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.

2.6. Substitution \ [backslash]

Le caractère \ est utilisé:

2.7. Empêcher la substitution [quoting and braces]

Pour éviter que Tcl ne donne une signification particulière à un caractère donné, il existe encore deux autres possibilités.

2.7.1. "..."

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
2.7.2. {...}

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).

2.8. Commentaires

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

2.9. Expressions mathématiques

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.

2.10. Difficultés des débutants

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]

2.11. Exercices

A ce stade, invoquez tclsh et entraînez-vous à bien comprendre les mécanismes de substitution.

3. Quelques commandes de base

3.1. set

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

3.2. unset

unset varName ?varName2 varName3 ...?

La commande unset détruit la ou les variables.

unset a

3.3. append

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à

3.4. incr

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

3.5. puts

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.

3.6. format

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

3.7. glob

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

3.8. info

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).

4. Listes, arrays et variables d'environnement

Outre les variables, Tcl a aussi la notion de liste et de vecteur [array].

4.1. Listes

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

4.2. Arrays

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.

4.3. Cas particulier: les variables d'environnement

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

5. Structures de contrôle

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!

5.1. if ... elseif ... else

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.

5.2. while

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
}

5.3. for

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}

5.4. foreach

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)"
}

5.5. break et continue

Dans les boucles while, for et foreach:

5.6. switch

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.

6. Procédure et champ d'application [scope]

6.1. Procédure

Une procédure est définie de la manière suivante:

proc name params {corps
...
}
proc p2 {a {b 7} {c abcd}} {corps} return returnvalue

6.2. Champ d'application [scope]

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.

6.2.1. global

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.

6.2.2. upvar

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 ...?
upvar #0 varName varName

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
6.2.3. uplevel

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]}
6.2.4. info level

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?

7. Chaînes de caractères et leur traitement

7.1. string

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.

7.2. strings et expressions

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"}

7.3. append

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

7.4. scan

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

8. Regular expressions

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 ...?
set env(DISPLAY) ltisun12:0.0 regexp {([^:]*):} $env(DISPLAY) match host => 1 puts "$match et $host" => ltisun12: et ltisun12

La commande regsub permet d'éditer une string sur la base du pattern matching.

9. Tcl dans l'environnement Unix

9.1. exec

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

9.2. file

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.

9.3. Les fichiers et I/O

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

10. D'autres commandes

10.1. source

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.

10.2. eval

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!

10.3. Gestion d'erreurs:catch et error

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?
proc test {} { error "essai d'erreur avec la commande error" puts "Après l'erreur (on ne devrait jamais venir ici)" } test => essai d'erreur avec la commande error puts stderr $errorInfo => essai d'erreur avec la commande error => while executing => "error "essai d'erreur avec la commande error"" => (procedure "test" line 2) => invoked from within => "test"