TCL
TCL
Welcome to the Tcl tutorial. We wrote it with the goal of helping you to learn Tcl. It is aimed at those
who have some knowledge of programming, although you certainly don't have to be an expert. The
tutorial is intended as a companion to the Tcl manual pages which provide a reference for all Tcl
commands.
It is divided into brief sections covering different aspects of the language. Depending on what system
you are on, you can always look up the reference documentation for commands that you are curious
about. On Unix for example, man while would bring up the man page for the while command.
Each section is accompanied by relevant examples showing you how to put to use the material covered.
Additional Resources
The Tcl community is an exceedingly friendly one. It's polite to try and figure things out yourself, but if
you're struggling, we're more than willing to help. Here are some good places to get help:
The comp.lang.tcl newsgroup. Accessible via a newsreader, or Google Groups.
The Wiki has a great deal of useful code, examples and discussions of the finer points of Tcl usage.
If you need help right away, there is often someone on the #tcl channel on irc.freenode.net who can
help you out, but please don't be impatient if no one can help you instantly - if you need that level of
support, consider hiring a consultant.
There are several recommended books for those who wish to gain more in-depth knowledge of Tcl.
Clif Flynt, the original author of this tutorial is also the author of Tcl/Tk: A Developer's Guide. Other
popular books: Practical Programming in Tcl and Tk.
Credits
Thanks first and foremost to Clif Flynt for making his material available under a BSD license. The
following people also contributed:
Neil Madden
Arjen Markus
David N. Welton
Of course, we also welcome comments and suggestions about how it could be improved - or if it's great
the way it is, we don't mind a bit of thanks, either!
Running Tcl
When you have installed Tcl, the program you will then call to utilize it is tclsh. For instance, if you
write some code to a file "hello.tcl", and you want to execute it, you would do it like so: tclsh hello.tcl.
Depending on the version of Tcl installed, and the operating system distribution you use, the tclsh
program may be a link to the real executable, which may be named tclsh8.6 or tclsh86.exe on Microsoft
Windows.
The tclsh is a simple command-line interactive interpreter. You can either start it with a script on the
command line, in which case it runs the script to completion and then exits, or you can start it without
any arguments, in which case you will be presented with an interactive prompt, usually using a %
symbol to prompt for input. In interactive mode, you can type in commands, which Tcl will then
execute and display the result, or any error messages that result. To exit the interpreter, type exit and
press Return. Playing around with the interactive interpreter is a great way to learn how to use Tcl.
Most Tcl commands will produce a helpful error message explaining how they are used if you just type
in the command with no arguments. You can get a list of all the commands that your interpreter knows
about by typing info commands.
The tclsh executable is just one way of starting a Tcl interpreter. Another common executable, which
may be installed on your system, is the wish, or WIndowing SHell. This is a version of Tcl that
automatically loads the Tk extension for building graphical user interfaces (GUIs). This tutorial does
not cover Tk, and so we will not use the wish interpreter here. Other options are also available,
providing more functional environments for developing and debugging code than that provided by the
standard tclsh. One very popular choice is the TkCon enhanced interpreter, written by Jeff Hobbs. The
Eclipse IDE offers good Tcl support, in the form of the DLTK extension, and the Tcl'ers Wiki offers a
list of IDEs with Tcl support and a comprehensive catalogue of Tcl source code editors. Don't panic,
though! If you don't know how to use a sophisticated development environment, it is still very easy to
write Tcl code by hand in a simple text editor (such as Notepad).
Simple Text Output
The traditional starting place for a tutorial is the classic "Hello, World" program. Once you can print
out a string, you're well on your way to using Tcl for fun and profit!
The command to output a string in Tcl is the puts command.
A single unit of text after the puts command will be printed to the standard output device. The default
behavior is to print a newline character ("return") appropriate for the system after printing the text.
If the string has more than one word, you must enclose the string in double quotes or braces ({}). A set
of words enclosed in quotes or braces is treated as a single unit, while words separated by whitespace
are treated as multiple arguments to the command. Quotes and braces can both be used to group several
words into a single unit. However, they actually behave differently. In the next lesson you'll start to
learn some of the differences between their behaviors. Note that in Tcl, single quotes are not
significant, as they are in other programming languages such as C, Perl and Python.
Many commands in Tcl (including puts) can accept multiple arguments. If a string is not enclosed in
quotes or braces, the Tcl interpreter will consider each word in the string as a separate argument, and
pass each individually to the puts command. The puts command will try to evaluate the words as
optional arguments. This will probably result in an error.
A command in Tcl is a list of words terminated by a newline or semicolon. Tcl comments are a # at the
beginning of the line, or after the command is closed with a ; semicolon.
Example
puts "Hello, World - In quotes" ;# This is a comment after the command.
# This is a comment at beginning of a line
puts {Hello, World - In Braces}
puts {Bad comment syntax example} # *Error* - there is no semicolon!
puts "This is line 1"; puts "this is line 2"
puts "Hello, World; - With a semicolon inside the quotes"
# Words don't need to be quoted unless they contain white space:
puts HelloWorld
Assigning values to variables
In Tcl, everything may be represented as a string, although internally it may be represented as a list,
integer, double, or other type, in order to make the language fast.
The assignment command in Tcl is set.
When set is called with two arguments, as in:
set fruit Cauliflower
it places the second argument (Cauliflower) in the memory space referenced by the first argument
(fruit). Set always returns the contents of the variable named in the first argument. Thus, when set is
called with two arguments, it places the second argument in the memory space referenced by the first
argument and then returns the second argument. In the above example, for instance, it would return
"Cauliflower", without the quotes.
The first argument to a set command can be either a single word, like fruit or pi , or it can be a member
of an array. Arrays will be discussed in greater detail later, for the time being just remember that many
data can be collected under a single variable name, and an individual datum can be accessed by its
index within that array. Indexing into an array in Tcl is handled by putting the index within parentheses
after the name of the variable.
Set can also be invoked with only one argument. When called with just one argument, it will return the
contents of that argument.
Here's a summary of the set command.
set varName ?value?
If value is specified, then the contents of the variable varName are set equal to value .
Grouping words within double quotes allows substitutions to occur within the quotations - or, in fancier
terms, "interpolation". The substituted group is then evaluated as a single argument. Thus, in the
command:
puts "The current stock value is $varName"
the current contents of varName are substituted for $varName, and then the entire string is printed to
the output device, just like the example above.
In general, the backslash (\) disables substitution for the single character immediately following the
backslash. Any character immediately following the backslash will stand without substitution.
However, there are specific "Backslash Sequence" strings which are replaced by specific values during
the substitution phase. The following backslash strings will be substituted as shown below.
String Output
Hex Value
\a
Audible Bell 0x07
\b
Backspace
0x08
\f
Form Feed (clear screen)
0x0c
\n
New Line
0x0a
\r
Carriage Return
0x0d
\t
Tab 0x09
\v
Vertical Tab 0x0b
\0dd Octal Value d is a digit from 0-7
\uHHHH
H is a hex digit 0-9,A-F,a-f. This represents a 16-bit Unicode character.
\xHH....
Hex Value
H is a hex digit 0-9,A-F,a-f. Note that the \x substitution "keeps going" as
long as it has hex digits, and only uses the last two, meaning that \xaa and \xaaaa are equal, and that
\xaaAnd anyway will "eat" the A of "And". Using the \u notation is probably a better idea.
The final exception is the backslash at the end of a line of text. This causes the interpreter to ignore the
newline, and treat the text as a single line of text. The interpreter will insert a blank space at the
location of the ending backslash.
Example
set Z Albany
set Z_LABEL "The Capitol of New York is: "
puts "$Z_LABEL $Z" ;# Prints the value of Z
puts "$Z_LABEL \$Z" ;# Prints a literal $Z instead of the value of Z
puts "\nBen Franklin is on the \$100.00 bill"
set a 100.00
puts "Washington is not on the $a bill" ;# This is not what you want
puts "Lincoln is not on the $$a bill"
;# This is OK
puts "Hamilton is not on the \$a bill" ;# This is not what you want
puts "Ben Franklin is on the \$$a bill" ;# But, this is OK
puts "\n................. examples of escape strings"
puts "Tab\tTab\tTab"
Performance tip: enclosing the arguments to expr in curly braces will result in faster code. So do expr
{$i * 10} instead of simply expr $i * 10
WARNING: You should always use braces when evaluating expressions that may contain user input, to
avoid possible security breaches. The expr command performs its own round of substitutions on
variables and commands, so you should use braces to prevent the Tcl interpreter doing this as well
(leading to double substitution). To illustrate the danger, consider this interactive session:
% set userinput {[puts DANGER!]}
[puts DANGER!]
% expr $userinput == 1
DANGER!
0
% expr {$userinput == 1}
0
In the first example, the code contained in the user-supplied input is evaluated, whereas in the second
the braces prevent this potential danger. As a general rule, always surround expressions with braces,
whether using expr directly or some other command that takes an expression (such as if or while).
OPERANDS
A Tcl expression consists of a combination of operands, operators, and parentheses. White space may
be used between operands, operators and parentheses; it is ignored by the expression processor. Where
possible, operands are interpreted as integer values. Integer values may be specified in decimal (the
normal case), in octal (if the first character of the operand is 0), or in hexadecimal (if the first two
characters of the operand are 0x).
Note that the octal and hexadecimal conversion takes place differently in the expr command than in the
Tcl substitution phase. In the substitution phase, a \x32 would be converted to an ascii "2", while expr
would convert 0x32 to a decimal 50.
If an operand does not have one of the integer formats given above, then it is treated as a floating-point
number, if that is possible. Floating-point numbers may be specified in any of the ways accepted by an
ANSI-compliant C compiler. For example, all of the following are valid floating-point numbers:
2.1
3.
6E4
7.91e+16
.000001
If no numeric interpretation is possible, then an operand is left as a string (and only a limited set of
operators may be applied to it).
Note however, that it does not support numbers of the following forms:
2,1 - a decimal comma, instead of a decimal point
2,100 - a thousands separator
It is possible to deal with numbers in that form, but you will have to convert these "strings" to numbers
in the standard form first.
Beware of leading zeros: 0700 is not interpreted as the decimal number 700 (seven hundred), but as the
octal number 700 = 7*8*8 = 448 (decimal).
Worse, if the number contains a digit 8 or 9 an error results:
% expr {0900+1}
expected integer but got "0900" (looks like invalid octal number)
Octal numbers are in fact a relic of the past, when such number formats were much more common.
Operands may be specified in any of the following ways:
As an numeric value, either integer or floating-point.
As a Tcl variable, using standard $ notation. The variable's value will be used as the operand.
OPERATORS
The valid operators are listed below, grouped in decreasing order of precedence:
-+~!
Unary minus, unary plus, bit-wise NOT, logical NOT. None of these operators may be applied to
string operands, and bit-wise NOT may be applied only to integers.
**
Exponentiation (works on both floating-point numbers and integers)
*/%
Multiply, divide, remainder. None of these operators may be applied to string operands, and
remainder may be applied only to integers. The remainder will always have the same sign as the divisor
and an absolute value smaller than the divisor.
+Add and subtract. Valid for any numeric operands.
<< >>
Left and right (bit) shift. Valid for integer operands only.
< > <= >=
Relational operators: less, greater, less than or equal, and greater than or equal. Each operator
produces 1 if the condition is true, 0 otherwise. These operators may be applied to numeric operands as
well as strings, in which case string comparison is used.
eq ne in ni
compare two strings for equality (eq) or inequality (ne). and two operators for checking if a string is
contained in a list (in) or not (ni). These operators all return 1 (true) or 0 (false). Using these operators
ensures that the operands are regarded exclusively as strings (and lists), not as possible numbers:
% expr { "9" == "9.0"}
1
% expr { "9" eq "9.0"}
0
&
Bit-wise AND. Valid for integer operands only.
^
Bit-wise exclusive OR. Valid for integer operands only.
|
Bit-wise OR. Valid for integer operands only.
&&
Logical AND. Produces a 1 result if both operands are non-zero, 0 otherwise. Valid for numeric
operands only (integers or floating-point).
||
Logical OR. Produces a 0 result if both operands are zero, 1 otherwise. Valid for numeric operands
only (integers or floating-point).
x?y:z
If-then-else. If x evaluates to non-zero, then the result is the value of y. Otherwise the result is the
value of z. The x operand must have a numeric value.
% set x 1
% expr { $x>0? ($x+1) : ($x-1) }
2
MATH FUNCTIONS
Tcl supports the following mathematical functions in expressions:
abs
atan2
cosh
floor
isqrt
min
sin
tan
acos
bool
double
fmod
log
pow
sinh
tanh
asin
atan
ceil
cos
entier
exp
hypot
int
log10
max
rand
round
sqrt
srand
wide
Besides these functions, you can also apply commands within an expression. For instance:
% set x 1
% set w "Abcdef"
% expr { [string length $w]-2*$x }
4
TYPE CONVERSIONS
Tcl supports the following functions to convert from one representation of a number to another:
double int wide entier
double() converts a number to a floating-point number.
int() converts a number to an ordinary integer number (by truncating the decimal part).
wide() converts a number to a so-called wide integer number (these numbers have a larger range).
entier() coerces a number to an integer of appropriate size to hold it without truncation. This might
return the same as int() or wide() or an integer of arbitrary size (in Tcl 8.5 and above).
The next lesson explains the various types of numbers in more detail.
Example
set X 100
set Y 256
set Z [expr {$Y + $X}]
set Z_LABEL "$Y plus $X is "
puts "$Z_LABEL $Z"
puts "The square root of $Y is [expr { sqrt($Y) }]\n"
puts "Because of the precedence rules \"5 + -3 * 4\" is: [expr {-3 * 4 + 5}]"
puts "Because of the parentheses
\"(5 + -3) * 4\" is: [expr {(5 + -3) * 4}]"
set A 3
set B 4
puts "The hypotenuse of a triangle: [expr {hypot($A,$B)}]"
#
# The trigonometric functions work with radians ...
#
set pi6 [expr {3.1415926/6.0}]
puts "The sine and cosine of pi/6: [expr {sin($pi6)}] [expr {cos($pi6)}]"
#
# Working with arrays
#
set a(1) 10
set a(2) 7
set a(3) 17
set b 2
puts "Sum: [expr {$a(1)+$a($b)}]"
Computers and numbers
mbers with a limited precision: floating-point numbers are not our mathematical real numbers.
Somewhat unexpectedly, 1/10 also gives problems. 1.2/0.1 results in 11.999999999999998, not 12.
That is an example of a very nasty aspect of most computers and programming languages today: they
do not work with ordinary decimal fractions, but with binary fractions. So, 0.5 can be represented
exactly, but 0.1 can not.
Some practical consequences
The fact that floating-point numbers are not ordinary decimal or real numbers and the actual way
if expr1 ?then? body1 elseif expr2 ?then? body2 elseif ... ?else? ?bodyN?
The words then and else are optional, although generally then is left out and else is used.
The test expression following if should return a value that can be interpreted as representing "true" or
"false":
False True
a numeric value
0
all others
yes/no no
yes
true/false
false true
If the test expression returns a string "yes"/"no" or "true"/"false", the case of the return is not checked.
True/FALSE or YeS/nO are legitimate returns.
If the test expression evaluates to True, then body1 will be executed.
If the test expression evaluates to False, then the word after body1 will be examined. If the next word is
elseif, then the next test expression will be tested as a condition. If the next word is else then the final
body will be evaluated as a command.
The test expression following the word if is evaluated in the same manner as in the expr command.
The test expression following if may be enclosed within quotes, or braces. If it is enclosed within
braces, it will be evaluated within the if command, and if enclosed within quotes it will be evaluated
during the substitution phase, and then another round of substitutions will be done within the if
command.
Note: This extra round can cause unexpected trouble - avoid it.
Example
set x 1
if {$x == 2} {puts "$x is 2"} else {puts "$x is not 2"}
if {$x != 1} {
puts "$x is != 1"
} else {
puts "$x is 1"
}
if $x==1 {puts "GOT 1"}
#
# Be careful, this is just an example
# Usually you should avoid such constructs,
# it is less than clear what is going on and it can be dangerous
#
set y x
if "$$y != 1" {
Note that you can use braces to group the body argument when using the switch or if commands. This
is because these commands pass their body argument to the Tcl interpreter for evaluation. This
evaluation includes a pass of substitutions just as it does for code not within a command body
argument.
Example
set x "ONE"
set y 1
set z ONE
# This is probably the easiest and cleanest form of the command
# to remember:
switch $x {
"$z" {
set y1 [expr {$y+1}]
puts "MATCH \$z. $y + $z is $y1"
}
ONE {
set y1 [expr {$y+1}]
puts "MATCH ONE. $y + one is $y1"
}
TWO {
set y1 [expr {$y+2}]
puts "MATCH TWO. $y + two is $y1"
}
THREE {
set y1 [expr {$y+3}]
puts "MATCH THREE. $y + three is $y1"
}
default {
puts "$x is NOT A MATCH"
}
}
switch $x "$z" {
set y1 [expr {$y+1}]
puts "MATCH \$z. $y + $z is $y1"
} ONE {
set y1 [expr {$y+1}]
puts "MATCH ONE. $y + one is $y1"
} TWO {
set y1 [expr {$y+2}]
puts "MATCH TWO. $y + two is $y1"
} THREE {
set y1 [expr {$y+3}]
puts "MATCH THREE. $y + three is $y1"
} default {
# The next example shows the difference between ".." and {...}
# How many times does the following loop run? Why does it not
# print on each pass?
set x 0
while "$x < 5" {
set x [expr {$x + 1}]
if {$x > 7} break
if "$x > 3" continue
puts "x is $x"
}
puts "exited second loop with X equal to $x"
Within the body code, the commands break and continue may be used just as they are used with the
while command. When a break is encountered, the loop exits immediately. When a continue is
encountered, evaluation of the body ceases, and the test is re-evaluated.
Because incrementing the iteration variable is so common, Tcl has a special command for this:
incr varName ?increment?
This command adds the value in the second argument to the variable named in the first argument. If no
value is given for the second argument, it defaults to 1.
Example
for {set i 0} {$i < 10} {incr i} {
puts "I inside first loop: $i"
}
for {set i 3} {$i < 2} {incr i} {
puts "I inside second loop: $i"
}
puts "Start"
set i 0
while {$i < 10} {
puts "I inside third loop: $i"
incr i
puts "I after incr: $i"
}
set i 0
incr i
# This is equivalent to:
set i [expr {$i + 1}]
the procedure name is called, it then runs the code contained in body.
Args is a list of arguments which will be passed to name. When name is invoked, local variables with
these names will be created, and the values to be passed to name will be copied to the local variables.
The value that the body of a proc returns can be defined with the return command. The return command
will return its argument to the calling program. If there is no return, then body will return to the caller
when the last of its commands has been executed. The return value of the last command becomes the
return value of the procedure.
Example
proc sum {arg1 arg2} {
set x [expr {$arg1 + $arg2}];
return $x
}
puts " The sum of 2 + 3 is: [sum 2 3]\n\n"
proc for {a b c} {
puts "The for command has been replaced by a puts";
puts "The arguments were: $a\n$b\n$c\n"
}
for {set i 1} {$i < 10} {incr I}
present when example is called. The second argument can be left out, and in that case it will default to
an empty string. By declaring args as the last argument, example can take a variable number of
arguments.
Note that if there is a variable other than args after a variable with a default, then the default will never
be used. For example, if you declare a proc such as: proc function { a {b 1} c} {...}, you will always
have to call it with 3 arguments.
Tcl assigns values to a proc's variables in the order that they are listed in the command. If you provide 2
arguments when you call function they will be assigned to a and b, and Tcl will generate an error
because c is undefined.
You can, however, declare other arguments that may not have values as coming after an argument with
a default value. For example, this is valid:
proc example {required {default1 a} {default2 b} args} {...}
In this case, example requires one argument, which will be assigned to the variable required. If there
are two arguments, the second arg will be assigned to default1. If there are 3 arguments, the first will be
assigned to required, the second to default1, and the third to default2. If example is called with more
than 3 arguments, all the arguments after the third will be assigned to args.
Example
proc example {first {second ""} args} {
if {$second eq ""} {
puts "There is only one argument and it is: $first"
return 1
} else {
if {$args eq ""} {
puts "There are two arguments - $first and $second"
return 2
} else {
puts "There are many arguments - $first and $second and $args"
return "many"
}
}
}
set count1 [example ONE]
set count2 [example ONE TWO]
set count3 [example ONE TWO THREE ]
set count4 [example ONE TWO THREE FOUR]
puts "The example was called with $count1, $count2, $count3, and $count4 Arguments"
- packages and namespaces), and at the topmost level, the global scope.
The scope in which a variable will be evaluated can be changed with the global and upvar commands.
The global command will cause a variable in a local scope (inside a procedure) to refer to the global
variable of that name.
The upvar command is similar. It "ties" the name of a variable in the current scope to a variable in a
different scope. This is commonly used to simulate pass-by-reference to procs.
You might also encounter the variable command in others' Tcl code. It is part of the namespace system
and is discussed in detail in that chapter.
Normally, Tcl uses a type of "garbage collection" called reference counting in order to automatically
clean up variables when they are not used anymore, such as when they go "out of scope" at the end of a
procedure, so that you don't have to keep track of them yourself. It is also possible to explicitly unset
them with the aptly named unset command.
The syntax for upvar is:
upvar ?level? otherVar1 myVar1 ?otherVar2 myVar2? ... ?otherVarN myVarN?
The upvar command causes myVar1 to become a reference to otherVar1, and myVar2 to become a
reference to otherVar2, etc. The otherVar variable is declared to be at level relative to the current
procedure. By default level is 1, the next level up.
If a number is used for the level, then level references that many levels up the stack from the current
level.
If the level number is preceded by a # symbol, then it references that many levels down from the global
scope. If level is #0, then the reference is to a variable at the global level.
If you are using upvar with anything except #0 or 1, you are most likely asking for trouble, unless you
really know what you're doing.
You should avoid using global variables if possible. If you have a lot of globals, you should reconsider
the design of your program.
Note that since there is only one global space it is surprisingly easy to have name conflicts if you are
importing other peoples code and aren't careful. It is recommended that you start global variables with
an identifiable prefix to help avoid unexpected conflicts.
Example
proc SetPositive {variable value } {
upvar $variable myvar
if {$value < 0} {
set myvar [expr {-$value}]
} else {
set myvar $value
}
return $myvar
}
SetPositive x 5
SetPositive y -5
puts "X : $x Y: $y\n"
proc two {y} {
upvar 1 $y z
;# tie the calling value to variable z
upvar 2 x a
;# Tie variable x two levels up to a
puts "two: Z: $z A: $a"
;# Output the values, just to confirm
set z 1
;# Set z, the passed variable to 1;
set a 2
;# Set x, two layers up to 2;
}
proc one {y} {
upvar $y z
puts "one: Z: $z"
two z
}
one y
puts "\nX: $x Y: $y"
existence x
puts "We celebrate on the [lindex $y 1]'th day of the [lindex $y 0]'th month\n"
set z [list puts "arg 2 is $y" ]
puts "A command resembles: $z\n"
set i 0
foreach j $x {
puts "$j is item number $i in list x"
incr i
}
#
# Match the first substring with lowercase letters only
#
set result [regexp {[a-z]+} $sample match]
puts "Result: $result match: $match"
#
# Match the first two words, the first one allows uppercase
set result [regexp {([A-Za-z]+) +([a-z]+)} $sample match sub1 sub2 ]
puts "Result: $result Match: $match 1: $sub1 2: $sub2"
#
# Replace a word
#
regsub "way" $sample "lawsuit" sample2
puts "New: $sample2"
#
# Use the -all option to count the number of "words"
#
puts "Number of words: [regexp -all {[^ ]+} $sample]"
/}\
{/dev/wd0f
{/dev/wd0h
{/dev/wd0g
Associative Arrays.
Languages like C, BASIC, FORTRAN and Java support arrays in which the index value is an integer.
Tcl, like most scripting languages (Perl, Python, PHP, etc...) supports associative arrays (also known as
"hash tables") in which the index value is a string.
The syntax for an associative array is to put the index within parentheses:
set name(first) "Mary"
set name(last) "Poppins"
puts "Full name: $name(first) $name(last)"
There are several array commands aside from simply accessing and creating arrays which will be
discussed in this and the next lesson.
set name(ID) 0
addname Mary Poppins
addname Uriah Heep
addname Rene Descartes
addname Leonardo "da Vinci"
#
# Check the contents of our database
# The parray command is a quick way to
# print it
#
parray name
#
# Some array commands
#
array set array1 [list {123} {Abigail Aardvark} \
{234} {Bob Baboon} \
{345} {Cathy Coyote} \
{456} {Daniel Dog} ]
puts "Array1 has [array size array1] entries\n"
puts "Array1 has the following entries: \n [array names array1] \n"
puts "ID Number 123 belongs to $array1(123)\n"
if {[array exist array1]} {
puts "array1 is an array"
} else {
puts "array1 is not an array"
}
if {[array exist array2]} {
puts "array2 is an array"
} else {
puts "array2 is not an array"
}
proc existence {variable} {
upvar $variable testVar
if { [info exists testVar] } {
puts "$variable Exists"
} else {
puts "$variable Does Not Exist"
}
}
# Create an array
for {set i 0} {$i < 5} {incr i} { set a($i) test }
puts "\ntesting unsetting a member of an array"
existence a(0)
puts "a0 has been unset"
unset a(0)
existence a(0)
puts "\ntesting unsetting several members of an array, with an error"
existence a(3)
existence a(4)
catch {unset a(3) a(0) a(4)}
puts "\nAfter attempting to delete a(3), a(0) and a(4)"
existence a(3)
existence a(4)
puts "\nUnset all the array's elements"
existence a
array unset a *
puts "\ntesting unsetting an array"
existence a
puts "a has been unset"
unset a
existence a
report fictional_name
puts "Historical characters:"
report historical_name
#
puts "Number of clients: [dict size $clients]"
dict for {id info} $clients {
puts "Client $id:"
dict with info {
puts " Name: $forenames $surname"
}
}
What happens in this example is:
We fill a dictionary, called clients, with the information we have on two clients. The dictionary has
two keys, "1" and "2" and the value for each of these keys is itself a (nested) dictionary again with
two keys: "forenames" and "surname". The dict set command accepts a list of key names which act as a
path through the dictionary. The last argument to the command is the value that we want to set. You can
supply as many key arguments to the dict set command as you want, leading to arbitrarily complicated
nested data structures. Be careful though! Flat data structure designs are usually better than nested for
most problems.
The dict for command then loops through each key and value pair in the dictionary (at the outer-most
level only). dict for is essentially a version of foreach that is specialised for dictionaries. We could also
have written this line as:
foreach {id info} $clients { ... }
This takes advantage of the fact that, in Tcl, every dictionary is also a valid Tcl list, consisting of a
sequence of name and value pairs representing the contents of the dictionary. The dict for command is
preferred when working with dictionaries, however, as it is both more efficient, and makes it clear to
readers of the code that we are dealing with a dictionary and not just a list.
To get at the actual values in the dictionary that is stored with the client IDs we use the dict with
command. This command takes the dictionary and unpacks it into a set of local variables in the current
scope. For instance, in our example, the "info" variable on each iteration of the outer loop will contain
a dictionary with two keys: "forenames" and "surname". The dict with command unpacks these keys
into local variables with the same name as the key and with the associated value from the dictionary.
This allows us to use a more convenient syntax when accessing the values, instead of having to use dict
get everywhere. A related command is the dict update command, that allows you to specify exactly
which keys you want to convert into variables. Be aware that any changes you make to these variables
will be copied back into the dictionary when the dict with command finishes.
The order in which elements of a dictionary are returned during a dict for loop is defined to be the
chronological order in which keys were added to the dictionary. If you need to access the keys in some
other order, then it is advisable to explicitly sort the keys first. For example, to retrieve all elements of a
dictionary in alphabetical order, based on the key, we can use the lsort command:
foreach name [lsort [dict keys $mydata]] {
puts "Data on \"$name\": [dict get $mydata $name]"
}
Example
In this example, we convert the simple database of the previous lessons to work with dictionaries
instead of arrays.
#
# The example of the previous lesson revisited - using dicts.
#
proc addname {dbVar first last} {
upvar 1 $dbVar db
# Create a new ID (stored in the name array too for easy access)
dict incr db ID
set id [dict get $db ID]
# Create the new record
dict set db $id first $first
dict set db $id last $last
}
proc report {db} {
# Loop over the last names: make a map from last name to ID
dict for {id name} $db {
# Create a temporary dictionary mapping from
# last name to ID, for reverse lookup
if {$id eq "ID"} { continue }
set last
[dict get $name last]
dict set tmp $last $id
}
#
# Now we can easily print the names in the order we want!
#
foreach last [lsort [dict keys $tmp]] {
set id [dict get $tmp $last]
puts " [dict get $db $id first] $last"
}
}
#
# Initialise the array and add a few names
#
dict set fictional_name ID 0
dict set historical_name ID 0
addname fictional_name Mary Poppins
addname fictional_name Uriah Heep
addname fictional_name Frodo Baggins
}
close $infile
puts "Number of lines: $number"
#
# Also report it in an external file
#
set outfile [open "report.out" w]
puts $outfile "Number of lines: $number"
close $outfile
letters, like a "d" for directories and "f" for ordinary files as well as letters and keywords indicating the
user's permissions ("r" for files/directories that can be read for instance).
-Marks the end of switches. This allows the use of "-" in a pattern without confusing the glob
parser.
pattern follows the same matching rules as the string match globbing rules with these exceptions:
{a,b,...} Matches any of the strings a,b, etc.
A "." at the beginning of a filename must match a "." in the filename. The "." is only a wildcard if
it is not the first character in a name.
All "/" must match exactly.
If the first two characters in pattern are ~/, then the ~ is replaced by the value of the HOME
environment variable.
If the first character in pattern is a ~, followed by a login id, then the ~loginid is replaced by the
path of loginid's home directory.
Note that the filenames that match pattern are returned in an arbitrary order (that is, do not expect
them to be sorted in alphabetical order, for instance).
file atime name
Returns the number of seconds since some system-dependent start date, also known as the "epoch"
(frequently 1/1/1970) when the file name was last accessed. Generates an error if the file doesn't exist,
or the access time cannot be queried.
file copy ?-force? name target
Copy the file/directory name to a new file target (or to an existing directory with that name)
The switch -force allows you to overwrite existing files.
file delete ?-force? name
Delete the file/directory name.
The switch -force allows you to delete non-empty directories.
file dirname name
Returns the directory portion of a path/filename string. If name contains no slashes, file dirname
returns a ".". If the last "/" in name is also the first character, it returns a "/".
file executable name
Returns 1 if file name is executable by the current user, otherwise returns 0.
file exists name
Returns 1 if the file name exists, and the user has search access in all the directories leading to the
file. Otherwise, 0 is returned.
file extension name
Returns the file extension.
file isdirectory name
Returns 1 if file name is a directory, otherwise returns 0.
file isfile name
Returns 1 if file name is a regular file, otherwise returns 0.
file lstat name varName
This returns the same information returned by the system call lstat. The results are placed in the
associative array varName. The indexes in varName are:
atime.......time of last access
You can also use the fconfigure command to make a connection (channel) unbuffered.
As already mentioned, expect extension to Tcl provides a much better interface to other programs,
which in particular handles the buffering problem.
[NOTE: add good reference to expect]
If one of the commands in an open |cmd fails the open does not return an error. However, attempting
to read input from the file descriptor with gets $file will return an empty string. Using the gets $file
input construct will return a character count of -1.
Tcl does not expand file names like the UNIX/Linux shells do. So:
exec ls *.tcl
will fail - there is most probably no file with the literal name "*.tcl".
If you need such an expansion, you should use the glob command:
eval exec ls [glob *.tcl]
or, from Tcl 8.5 onwards:
exec ls {*}[glob *.tcl]
where the {*} prefix is used to force the list to become individual arguments.
If one of the commands in an exec call fails to execute, the exec will return an error, and the error
output will include the last line describing the error.
The exec treats any output to standard error to be an indication that the external program failed. This
is simply a conservative assumption: many programs behave that way and they are sloppy in setting
return codes.
Some programs however write to standard error without intending this as an indication of an error.
You can guard against this from upsetting your script by using the catch command:
if { [catch { exec ls *.tcl } msg] } {
puts "Something seems to have gone wrong but we will ignore it"
}
To inspect the return code from a program and the possible reason for failure, you can use the global
errorInfo variable:
if { [catch { exec ls *.tcl } msg] } {
puts "Something seems to have gone wrong:"
puts "Information about it: $::errorInfo"
}
Example
#
# Write a Tcl script to get a platform-independent program:
#
# Create a unique (mostly) file name for a Tcl program
set TMPDIR "/tmp"
if { [info exists ::env(TMP)] } {
set TMPDIR $::env(TMP)
}
set tempFileName "$TMPDIR/invert_[pid].tcl"
# Open the output file, and
# write the program to it
set outfl [open $tempFileName w]
puts $outfl {
set len [gets stdin line]
if {$len < 5} {exit -1}
for {set i [expr {$len-1}]} {$i >= 0} {incr i -1} {
append l2 [string range $line $i $i]
}
puts $l2
exit 0
}
# Flush and close the file
flush $outfl
close $outfl
#
# Run the new Tcl script:
#
# Open a pipe to the program (for both reading and writing: r+)
#
set io [open "|[info nameofexecutable] $tempFileName" r+]
#
# send a string to the new program
# *MUST FLUSH*
puts $io "This will come back backwards."
flush $io
# Get the reply, and display it.
set len [gets $io line]
puts "To reverse: 'This will come back backwards.'"
puts "Reversed is: $line"
puts "The line is $len characters long"
# Run the program with input defined in an exec call
}
localproc
like: "8.4".
pid
Returns the process id of the current process.
Example
puts "This is how many commands have been executed: [info cmdcount]"
puts "Now *THIS* many commands have been executed: [info cmdcount]"
puts "\nThis interpreter is revision level: [info tclversion]"
puts "This interpreter is at patch level: [info patchlevel]"
puts "The process id for this program is [pid]"
proc factorial {val} {
puts "Current level: [info level] - val: $val"
set lvl [info level]
if {$lvl == $val} {
return $val
}
return [expr {($val-$lvl) * [factorial $val]}]
}
set count1 [info cmdcount]
set fact [factorial 3]
set count2 [info cmdcount]
puts "The factorial of 3 is $fact"
puts "Before calling the factorial proc, $count1 commands had been executed"
puts "After calling the factorial proc, $count2 commands had been executed"
puts "It took [expr $count2-$count1] commands to calculate this factorial"
#
# Use [info script] to determine where the other files of interest
# reside
#
set sysdir [file dirname [info script]]
source [file join $sysdir "utils.tcl"]
Modularization source
The source command will load a file and execute it. This allows a program to be broken up into
multiple files, with each file defining procedures and variables for a particular area of functionality. For
instance, you might have a file called database.tcl that contains all the procedures for dealing with a
database, or a file called gui.tcl that handles creating a graphical user interface with Tk. The main script
can then simply include each file using the source command. More powerful techniques for program
modularization are discussed in the next lesson on packages.
This command can be used to:
provide commands. From this information it generates an appropriate pkgIndex.tcl file in the directory.
Once a package index has been created, the next step is to move the package to somewhere that Tcl can
find it. The tcl_pkgPath and auto_path global variables contain a list of directories that Tcl searches for
packages. The package index and all the files that implement the package should be installed into a
subdirectory of one of these directories. Alternatively, the auto_path variable can be extended at runtime to tell Tcl of new places to look for packages.
package require ?-exact? name ?version?
Loads the package identified by name. If the -exact switch is given along with a version number then
only that exact package version will be accepted. If a version number is given, without the -exact
switch then any version equal to or greater than that version (but with the same major version number)
will be accepted. If no version is specified then any version will be loaded. If a matching package can
be found then it is loaded and the command returns the actual version number; otherwise it generates
an error.
package provide name ?version?
If a version is given this command tells Tcl that this version of the package indicated by name is
loaded. If a different version of the same package has already been loaded then an error is generated. If
the version argument is omitted, then the command returns the version number that is currently loaded,
or the empty string if the package has not been loaded.
pkg_mkIndex ?-direct? ?-lazy? ?-load pkgPat? ?-verbose? dir ?pattern pattern ...?
Creates a pkgIndex.tcl file for a package or set of packages. The command works by loading the files
matching the patterns in the directory, dir and seeing what new packages and commands appear. The
command is able to handle both Tcl script files and binary libraries (not discussed here).
Namespaces
One problem that can occur when using packages, and particularly when using code written by others
is that of name collision. This happens when two pieces of code try to define a procedure or variable
with the same name. In Tcl when this occurs the old procedure or variable is simply overwritten. This is
sometimes a useful feature, but more often it is the cause of bugs if the two definitions are not
compatible. To solve this problem, Tcl provides a namespace command to allow commands and
variables to be partitioned into separate areas, called namespaces. Each namespace can contain
commands and variables which are local to that namespace and cannot be overwritten by commands or
variables in other namespaces. When a command in a namespace is invoked it can see all the other
commands and variables in its namespace, as well as those in the global namespace. Namespaces can
also contain other namespaces. This allows a hierarchy of namespaces to be created in a similar way to
a file system hierarchy, or the Tk widget hierarchy. Each namespace itself has a name which is visible
in its parent namespace. Items in a namespace can be accessed by creating a path to the item. This is
done by joining the names of the items with ::. For instance, to access the variable bar in the namespace
foo, you could use the path foo::bar. This kind of path is called a relative path because Tcl will try to
follow the path relative to the current namespace. If that fails, and the path represents a command, then
Tcl will also look relative to the global namespace. You can make a path fully-qualified by describing
its exact position in the hierachy from the global namespace, which is named ::. For instance, if our foo
namespace was a child of the global namespace, then the fully-qualified name of bar would be
::foo::bar. It is usually a good idea to use fully-qualified names when referring to any item outside of
the current namespace to avoid surprises.
A namespace can export some or all of the command names it contains. These commands can then be
imported into another namespace. This in effect creates a local command in the new namespace which
when invoked calls the original command in the original namespace. This is a useful technique for
creating short-cuts to frequently used commands from other namespaces. In general, a namespace
should be careful about exporting commands with the same name as any built-in Tcl command or with
a commonly used name.
Some of the most important commands to use when dealing with namespaces are:
namespace eval path script
This command evaluates the script in the namespace specified by path. If the namespace doesn't exist
then it is created. The namespace becomes the current namespace while the script is executing, and any
unqualified names will be resolved relative to that namespace. Returns the result of the last command
in script.
namespace delete ?namespace namespace ...?
Deletes each namespace specified, along with all variables, commands and child namespaces it
contains.
namespace current
Returns the fully qualified path of the current namespace.
namespace export ?-clear? ?pattern pattern ...?
Adds any commands matching one of the patterns to the list of commands exported by the current
namespace. If the -clear switch is given then the export list is cleared before adding any new
commands. If no arguments are given, returns the currently exported command names. Each pattern is
a glob-style pattern such as *, [a-z]*, or *foo*.
namespace import ?-force? ?pattern pattern ...?
Imports all commands matching any of the patterns into the current namespace. Each pattern is a
glob-style pattern such as foo::*, or foo::bar.
Using namespace with packages
William Duquette has an excellent guide to using namespaces and packages at
http://www.wjduquette.com/tcl/namespaces.html. In general, a package should provide a namespace as
a child of the global namespace and put all of its commands and variables inside that namespace. A
package shouldn't put commands or variables into the global namespace by default. It is also good style
to give your package and the namespace it provides the same name, to avoid confusion.
Example
This example creates a package which provides a stack data structure.
# Register the package
package provide tutstack 1.0
package require Tcl
8.5
# Create the namespace
namespace eval ::tutstack {
# Export commands
namespace export create destroy push pop peek empty
# Set up state
variable stack
variable id 0
}
# Create a new stack
proc ::tutstack::create {} {
variable stack
variable id
set token "stack[incr id]"
set stack($token) [list]
return $token
}
# Destroy a stack
proc ::tutstack::destroy {token} {
variable stack
unset stack($token)
}
# Push an element onto a stack
proc ::tutstack::push {token elem} {
variable stack
lappend stack($token) $elem
}
# Check if stack is empty
proc ::tutstack::empty {token} {
variable stack
set num [llength $stack($token)]
return [expr {$num == 0}]
}
# See what is on top of the stack without removing it
proc ::tutstack::peek {token} {
variable stack
if {[empty $token]} {
error "stack empty"
}
return [lindex $stack($token) end]
}
# Remove an element from the top of the stack
proc ::tutstack::pop {token} {
variable stack
As well as providing a nicer syntax for accessing functionality in a namespace, ensemble commands
also help to clearly distinguish the public interface of a package from the private implementation
details, as only exported commands will be registered as sub-commands and the ensemble will enforce
this distinction. Readers who are familiar with object-oriented programming (OOP) will realise that the
namespace and ensemble mechanisms provide many of the same encapsulation advantages. Indeed,
many OO extensions for Tcl build on top of the powerful namespace mechanism.
commands. These commands become difficult to use, however if you need to put braces in the
command, as was done in the previous lesson.
The example from the previous lesson is re-implemented in the example code using lappend.
The completeness of a command can be checked with info complete. Info complete can also be used in
an interactive program to determine if the line being typed in is a complete command, or the user just
entered a newline to format the command better.
info complete string
If string has no unmatched brackets, braces or parentheses, then a value of 1 is returned, else 0 is
returned.
Example
set cmd "OK"
eval puts $cmd
set cmd "puts" ; lappend cmd {Also OK}; eval $cmd
set cmd "NOT OK"
eval puts $cmd
eval [format {%s "%s"} puts "Even This Works"]
set cmd "And even this can be made to work"
eval [format {%s "%s"} puts $cmd ]
set tmpFileNum 0;
set cmd {proc tempFileName }
lappend cmd ""
lappend cmd "global num; incr num; return \"/tmp/TMP.[pid].\$num\""
eval $cmd
puts "\nThis is the body of the proc definition:"
puts "[info body tempFileName]\n"
set cmd {puts "This is Cool!}
if {[info complete $cmd]} {
eval $cmd
} else {
puts "INCOMPLETE COMMAND: $cmd"
}
set a arrayname
set b index
set c newvalue
eval [format "set %s(%s) %s" $a $b $c]
puts "Index: $b of $a was set to: $arrayname(index)"
proc a {} {
b
}
proc b {} {
c
}
proc c {} {
d
}
proc d {} {
some_command
}
a
Produces the following output:
invalid command name "some_command"
while executing
"some_command"
(procedure "d" line 2)
invoked from within
"d"
(procedure "c" line 2)
invoked from within
"c"
(procedure "b" line 2)
invoked from within
"b"
(procedure "a" line 2)
invoked from within
"a"
(file "errors.tcl" line 16)
This actually occurs when any exception condition occurs, including break and continue. The break and
continue commands normally occur within a loop of some sort, and the loop command catches the
exception and processes it properly, meaning that it either stops executing the loop, or continues on to
the next instance of the loop without executing the rest of the loop body.
It is possible to "catch" errors and exceptions with the catch command, which runs some code, and
catches any errors that code happens to generate. The programmer can then decide what to do about
those errors and act accordingly, instead of having the whole application come to a halt.
For example, if an open call returns an error, the user could be prompted to provide another file name.
A Tcl proc can also generate an error status condition. This can be done by specifying an error return
with an option to the return command, or by using the error command. In either case, a message will be
placed in errorInfo, and the proc will generate an error.
catch errorproc
puts "after bad proc call: ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
set errorInfo "";
catch {errorproc 0}
puts "after proc call with no error: ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
catch {errorproc 2}
puts "after error generated in proc: ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
proc returnErr { x } {
return -code error -errorinfo "Return Generates This" -errorcode "-999"
}
catch {returnErr 2}
puts "after proc that uses return to generate an error: ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
proc withError {x} {
set x $a
}
catch {withError 2}
puts "after proc with an error: ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
catch {open [file join no_such_directory no_such_file] r}
puts "after an error call to a nonexistent file:"
puts "ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
remove, which has the general form: trace remove type name opList command
Which are for adding traces, retrieving information about traces, and removing traces, respectively.
Traces can be added to three kinds of "things":
variable - Traces added to variables are called when some event occurs to the variable, such as being
written to or read.
command - Traces added to commands are executed whenever the named command is renamed or
deleted.
execution - Traces on "execution" are called whenever the named command is run.
Traces on variables are invoked on four separate conditions - when a variable is accessed or modified
via the array command, when the variable is read or written, or when it's unset. For instance, to set a
trace on a variable so that when it's written to, the value doesn't change, you could do this:
proc vartrace {oldval varname element op} {
upvar $varname localvar
set localvar $oldval
}
set tracedvar 1
trace add variable tracedvar write [list vartrace $tracedvar]
set tracedvar 2
puts "tracedvar is $tracedvar"
In the above example, we create a proc that takes four arguments. We supply the first, the old value of
the variable, because write traces are triggered after the variable's value has already been changed, so
we need to preserve the original value ourselves. The other three arguments are the variable's name, the
element name if the variable is an array (which it isn't in our example), and the operation to trace - in
this case, write. When the trace is called, we simply set the variable's value back to its old value. We
could also do something like generate an error, thus warning people that this variable shouldn't be
written to. Infact, this would probably be better. If someone else is attempting to understand your
program, they could become quite confused when they find that a simple set command no longer
functions!
The command and execution traces are intended for expert users - perhaps those writing debuggers for
Tcl in Tcl itself - and are therefore not covered in this tutorial, see the trace man page for further
information.
Example
proc traceproc {variableName arrayElement operation} {
set op(write) Write
set op(unset) Unset
set op(read) Read
set level [info level]
incr level -1
if {$level > 0} {
useful.
The number of command line arguments to a Tcl script is passed as the global variable argc . The name
of a Tcl script is passed to the script as the global variable argv0 , and the rest of the command line
arguments are passed as a list in argv. The name of the executable that runs the script, such as tclsh is
given by the command info nameofexecutable
Another method of passing information to a script is with environment variables. For instance, suppose
you are writing a program in which a user provides some sort of comment to go into a record. It would
be friendly to allow the user to edit their comments in their favorite editor. If the user has defined an
EDITOR environment variable, then you can invoke that editor for them to use.
Environment variables are available to Tcl scripts in a global associative array env . The index into env
is the name of the environment variable. The command puts "$env(PATH)" would print the contents of
the PATH environment variable.
Example
puts "There are $argc arguments to this script"
puts "The name of this script is $argv0"
if {$argc > 0} {puts "The other arguments are: $argv" }
puts "You have these environment variables set:"
foreach index [array names env] {
puts "$index: $env($index)"
}
Timing scripts
The simplest method of making a script run faster is to buy a faster processor. Unfortunately, this isn't
always an option. You may need to optimize your script to run faster. This is difficult if you can't
measure the time it takes to run the portion of the script that you are trying to optimize.
The time command is the solution to this problem. time will measure the length of time that it takes to
execute a script. You can then modify the script, rerun time and see how much you improved it.
After you've run the example, play with the size of the loop counters in timetst1 and timetst2. If you
make the inner loop counter 5 or less, it may take longer to execute timetst2 than it takes for timetst1.
This is because it takes time to calculate and assign the variable k, and if the inner loop is too small,
then the gain in not doing the multiply inside the loop is lost in the time it takes to do the outside the
loop calculation.
time script ?count?
Returns the number of milliseconds it took to execute script. If count is specified, it will run the
script count times, and average the result. The time is elapsed time, not CPU time.
Example
proc timetst1 {lst} {
-blocking . . . Determines whether or not the task will block when data cannot be moved on a
channel. (i.e. If no data is available on a read, or the buffer is full on a write).
-buffersize . . . The number of bytes that will be buffered before data is sent, or can be buffered
before being read when data is received. The value must be an integer between 10 and 1000000.
-translation . . . Sets how Tcl will terminate a line when it is output. By default, the lines are
terminated with the newline, carriage return, or newline/carriage return that is appropriate to the system
on which the interpreter is running.
This can be configured to be:
auto . . . Translates newline, carriage return, or newline/carriage return as an end of line marker.
Outputs the correct line termination for the current platform.
binary . . Treats newlines as end of line markers. Does not add any line termination to lines
being output.
cr . . . . Treats carriage returns as the end of line marker (and translates them to newline
internally). Output lines are terminated with a carriage return. This is the Macintosh standard.
crlf . . . Treats cr/lf pairs as the end of line marker, and terminates output lines with a carriage
return/linefeed combination. This is the Windows standard, and should also be used for all line-oriented
network protocols.
lf . . . . Treats linefeeds as the end of line marker, and terminates output lines with a linefeed.
This is the Unix standard.
The example is similar to the lesson 40 example with a client and server socket in the same script. It
shows a server channel being configured to be non-blocking, and using the default buffering style data is not made availalble to the script until a newline is present, or the buffer has filled.
When the first write:
puts -nonewline $sock "A Test Line"
is done, the fileevent triggers the read, but the gets can't read characters because there is no newline.
The gets returns a -1, and fblocked returns a 1. When a bare newline is sent, the data in the input buffer
will become available, and the gets returns 18, and fblocked returns 0.
Example
proc serverOpen {channel addr port} {
puts "channel: $channel - from Address: $addr Port: $port"
puts "The default state for blocking is: [fconfigure $channel -blocking]"
puts "The default buffer size is: [fconfigure $channel -buffersize ]"
# Set this channel to be non-blocking.
fconfigure $channel -blocking 0
set bl [fconfigure $channel -blocking]
puts "After fconfigure the state for blocking is: $bl"
# Change the buffer size to be smaller
fconfigure $channel -buffersize 12
puts "After Fconfigure buffer size is: [fconfigure $channel -buffersize ]\n"
Child interpreters
For most applications, a single interpreter and subroutines are quite sufficient. However, if you are
building a client-server system (for example) you may need to have several interpreters talking to
different clients, and maintaining their state. You can do this with state variables, naming conventions,
or swapping state to and from disk, but that gets messy.
The interp command creates new child interpreters within an existing interpreter. The child interpreters
can have their own sets of variables, commands and open files, or they can be given access to items in
the parent interpreter.
If the child is created with the -safe option, it will not be able to access the file system, or otherwise
damage your system. This feature allows a script to evaluate code from an unknown (and untrusted)
source.
The names of child interpreters are a hierarchical list. If interpreter foo is a child of interpreter bar, then
it can be accessed from the toplevel interpreter as {bar foo}.
The primary interpreter (what you get when you type tclsh) is the empty list {}.
The interp command has several subcommands and options. A critical subset is:
interp create -safe name
Creates a new interpreter and returns the name. If the -safe option is used, the new interpreter will be
unable to access certain dangerous system facilities.
interp delete name
Deletes the named child interpreter.
#
# Alias that procedure to a proc in $i1
interp alias $i1 rtnName {} rtnName
puts ""
# This is an error. The alias causes the evaluation
# to happen in the {} interpreter, where name is
# not defined.
puts "firstChild reports [interp eval $i1 rtnName]"