Document
Document
Introduction
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.
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:
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
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 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).
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.
Example
If you look at the example code, you'll notice that in the set command the
first argument is typed with only its name, but in the putsstatement the
argument is preceded with a $.
The dollar sign tells Tcl to use the value of the variable - in this case X or Y.
Example
set Y 1.24
puts $X
puts $Y
puts "..............................."
This lesson is the first of three which discuss the way Tcl handles
substitution during command evaluation.
puts $varName
the contents of the proper variable are substituted for $varName, and then the
command is executed. Assuming we have set varName to "Hello World", the
sequence would look like this: puts $varName ⇒ puts"Hello World", which is
then executed and prints out Hello World.
A command within square brackets ([]) is replaced with the result of the
execution of that command. (This will be explained more fully in the lesson
"Results of a Command - Math 101.")
Words within double quotes or braces are grouped into a single argument.
However, double quotes and braces cause different behavior during the
substitution phase. In this lesson, we will concentrate on the behavior of
double quotes during the substitution phase.
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:
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.
\b Backspace 0x08
Form Feed
\f 0x0c
(clear screen)
\t Tab 0x09
H is a hex digit
\uHHHH 0-9,A-F,a-f. This
represents a 16-
String Output Hex Value
bit Unicode
character.
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: "
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
In the last lesson you saw that grouping words with double quotes allows
substitutions to occur within the double quotes. By contrast, grouping words
within double braces disables substitution within the braces. Characters
within braces are passed to a command exactly as written. The only
"Backslash Sequence" that is processed within braces is the backslash at the
end of a line. This is still a line continuation character.
Note that braces have this effect only when they are used for grouping (i.e.
at the beginning and end of a sequence of words). If a string is already
grouped, either with quotes or braces, and braces occur in the middle of the
grouped string (i.e. "foo{bar"), then the braces are treated as regular
characters with no special meaning. If the string is grouped with quotes,
substitutions will occur within the quoted string, even between the braces.
Example
set Z Albany
set Z_LABEL "The Capitol of New York is: "
puts "\n................. examples of differences between \" and \{"
puts "$Z_LABEL $Z"
puts {$Z_LABEL $Z}
As the Tcl interpreter reads in a line it replaces all the $variables with their
values. If a portion of the string is grouped with square brackets, then the
string within the square brackets is evaluated as a command by the
interpreter, and the result of the command replaces the square bracketed
string.
The parser scans the entire command, and sees that there is a
command substitution to perform: readsensor [selectsensor] ,
which is sent to the interpreter for evaluation.
The parser once again finds a command to be evaluated and
substituted, selectsensor
The fictitious selectsensor command is evaluated, and it
presumably returns a sensor to read.
At this point, readsensor has a sensor to read, and the
readsensor command is evaluated.
Finally, the value of readsensor is passed on back to
the puts command, which prints the output to the screen.
Example
set x abc
puts "A simple substitution: $x\n"
expr takes all of its arguments ("2 + 2" for example) and evaluates the
result as a Tcl "expression" (rather than a normal command), and returns
the value. The operators permitted in Tcl expressions include all the
standard math functions, logical operators, bitwise operators, as well as
math functions like rand(), sqrt(), cosh()and so on. Expressions almost
always yield numeric results (integer or floating-point values).
Performance tip: enclosing the arguments toexpr in curly braces will result
in faster code. So do expr {$i * 10} instead of simply expr $i * 10
Note that the octal and hexadecimal conversion takes place differently in
the exprcommand 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
Note however, that it does not support numbers of the following forms:
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).
Octal numbers are in fact a relic of the past, when such number
formats were much more common.
OPERATORS
-+~!
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:
&
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
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
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 "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)}]"
Important note: I used Tcl 8.4.1 for all examples. In Tcl 8.5 the results will
hopefully be more intuitive, as a result of adding so-called big integers.
Nevertheless, the general theme remains the same.
Now consider the following example, it is almost the same, with the
exception of a decimal dot:
The reason is simple, well if you know more about the background
of computer arithmetic:
In the first example we multiplied two integer numbers, or
short integers. While we are used to these numbers ranging
from-infinity to +infinity, computers can not deal with that
range (at least not that easily). So, instead, computers deal
with a subset of the actual mathematical integer numbers.
They deal with numbers from -231 to 231-1 (in general) - that
is, with numbers from -2147483648 to 2147483647.
Numbers outside that range can not be dealt with that easily.
This is also true of the results of a computation (or the
intermediate results of a computation, even if the final result
does fit).
Tcl's strategy
Tcl uses a simple but efficient strategy to decide what kind of numbers to
use for the computations:
If you add, subtract, multiply and divide two integer numbers,
then the result is an integer. If the result fits within the range
you have the exact answer. If not, you end up with something
that appears to be completely wrong. (Note: not too long ago,
floating-point computations were much more time-consuming
than integer computations. And most computers do not warn
about integer results outside the range, because that is too
time-consuming too: a computer typically uses lots of such
operations, most of which do fit into the designated range.)
If you add, subtract, multiply and divide an integer number
and a floating-point number, then the integer number is first
converted to a floating-point number with the same value and
then the computation is done, resulting in a floating-point
number.
# Compute 1.0e+300/1.0-300
% puts [expr {1.0e300/1.0e-300}]
floating-point value too large to represent
Now some of the mysteries you can find yourself involved in. Run the
following scripts:
#
# Division
#
puts "1/2 is [expr {1/2}]"
puts "-1/2 is [expr {-1/2}]"
puts "1/2 is [expr {1./2}]"
puts "1/3 is [expr {1./3}]"
puts "1/3 is [expr {double(1)/3}]"
The first two computations have the surprising result: 0 and -1. That is
because the result is an integer number and the mathematically exact
results 1/2 and -1/2 are not.
a = q * b + r
0 <= |r| < |b|
r has the same sign as q
While many of the above computations give the result you would expect,
note however the last decimals, the last two do not give exactly 5 and 12!
This is because computers can only deal with numbers 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.
The fact that floating-point numbers are not ordinary decimal or real
numbers and the actual way computers deal with floating-point numbers,
has a number of consequences:
#
# The wrong way
#
set number [expr {int(1.2/0.1)}] ;# Force an integer -
;# accidentally number = 11
for { set i 0 } { $i <= $number } { incr i } {
set x [expr {$i*0.1}]
... create label $x
}
#
# A right way - note the limit
#
set x 0.0
set delta 0.1
while { $x < 1.2+0.5*$delta } {
#
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.
yes/no no yes
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 exprcommand.
Note: This extra round can cause unexpected trouble - avoid it.
Example
set x 1
if {$x != 1} {
puts "$x is != 1"
} else {
puts "$x is 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" {
puts "$$y is != 1"
} else {
puts "$$y is 1"
}
#
# A dangerous example: due to the extra round of substitution,
# the script stops
#
set y {[exit]}
if "$$y != 1" {
puts "$$y is != 1"
} else {
puts "$$y is 1"
}
It's a good idea to use the switch command when you want to match a
variable against several possible values, and don't want to do a long series
of if... elseif ... elseifstatements.
- or -
String is the string that you wish to test, and pattern1, pattern2, etc are the
patterns that the string will be compared to. If stringmatches a pattern,
then the code within the body associated with that pattern will be executed.
The return value of the body will be returned as the return value of the switch
statement. Only one pattern will be matched.
If the last pattern argument is the string default, that pattern will match any
string. This guarantees that some set of code will be executed no matter
what the contents of string are.
If there is no default argument, and none of the patterns match string, then
the switchcommand will return an empty string.
If you use the brace version of this command, there will be no substitutions
done on the patterns. The body of the command, however, will be parsed
and evaluated just like any other command, so there will be a pass of
substitutions done on that, just as will be done in the first syntax. The
advantage of the second form is that you can write multiple line commands
more readably with the brackets.
Note that you can use braces to group the body argument when using
the switch or ifcommands. 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 bodyargument.
Example
set x "ONE"
set y 1
set z ONE
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 does not match any of these choices"
}
switch $x "ONE" "puts ONE=1" "TWO" "puts TWO=2" "default" "puts NO_MATCH"
switch $x \
"ONE" "puts ONE=1" \
"TWO" "puts TWO=2" \
"default" "puts NO_MATCH";
Tcl includes two commands for looping, the while and for commands. Like
the ifstatement, they evaluate their test the same way that the expr does.
In this lesson we discuss the while command, and in the next lesson,
the for command. In most circumstances where one of these commands can
be used, the other can be used as well.
A continue statement within body will stop the execution of the code and the
test will be re-evaluated. A break within body will break out of the while loop,
and execution will continue with the next line of code after body
Look at the two loops in the example. If it weren't for the break command in
the second loop, it would loop forever.
Example
set x 1
# 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"
}
During evaluation of the for command, the start code is evaluated once,
before any other arguments are evaluated. After the start code has been
evaluated, the test is evaluated. If the test evaluates to true, then
the body is evaluated, and finally, the nextargument is evaluated. After
evaluating the next argument, the interpreter loops back to the test, and
repeats the process. If the testevaluates as false, then the loop will exit
immediately.
Since you commonly do not want the Tcl interpreter's substitution phase to
change variables to their current values before passing control to
the for command, it is common to group the arguments with curly braces.
When braces are used for grouping, the newline is not treated as the end of
a Tcl command. This makes it simpler to write multiple line commands.
However, the opening brace must be on the line with the for command, or
the Tcl interpreter will treat the close of the next brace as the end of the
command, and you will get an error. This is different than other languages
like C or Perl, where it doesn't matter where you place your braces.
Within the body code, the commands breakand continue may be used just as
they are used with the while command. When a breakis encountered, the
loop exits immediately. When a continue is encountered, evaluation of
the body ceases, and the test is re-evaluated.
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
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}]
When proc is evaluated, it creates a new command with name name that
takes arguments args. When the procedure name is called, it then runs the
code contained in body.
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 for {a b c} {
puts "The for command has been replaced by a puts";
puts "The arguments were: $a\n$b\n$c\n"
}
A proc can be defined with a set number of required arguments (as was
done with sum in the previous lesson, or it can have a variable number of
arguments. An argument can also be defined to have a default value.
Variables can be defined with a default value by placing the variable name
and the default within braces within args. For example:
Since there are default arguments for the band c variables, you could call the
procedure one of three ways: justdoit 10, which would set a to 10, and
leave b set to its default 1, and c at -1. justdoit 10 20 would likewise set b to
20, and leave C to its default. Or call it with all three parameters set to avoid
any defaults.
A proc will accept a variable number of arguments if the last declared
argument is the word args. If the last argument to a proc argument list
is args, then any arguments that aren't already assigned to previous
variables will be assigned to args.
The example procedure below is defined with three arguments. At least one
argument *must* be 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, examplecan take a variable number of
arguments.
Note that if there is a variable other than argsafter 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:
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
puts "The example was called with $count1, $count2, $count3, and $count4
Arguments"
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 variablecommand 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.
If a number is used for the level, then level references that many levels up
the stack from the current 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
SetPositive x 5
SetPositive y -5
set x 1
set y 2
for {set i 0} {$i < 5} {incr i} {
set a($i) $i;
}
The list is the basic Tcl data structure. A list is simply an ordered collection
of stuff; numbers, words, strings, or other lists. Even commands in Tcl are
just lists in which the first list entry is the name of a proc, and subsequent
members of the list are the arguments to the proc.
Returns the index'th item from the list. Note: lists start from 0,
not 1, so the first item is at index 0, the second item is at
index 1, and so on.
llength list
The items in list can be iterated through using the foreach command:
The foreach command will execute the body code one time for
each list item in list. On each pass, varname will contain the
value of the next list item.
In reality, the above form of foreach is the simple form, but the command is
quite powerful. It will allow you to take more than one variable at a time
from the list:foreach {a b} $listofpairs { ... }. You can even take a variable
at a time from multiple lists! For
example:foreach a $listOfA b $listOfB { ... }
Examples
set i 0
foreach j $x {
puts "$j is item number $i in list x"
incr i
}
Returns a new list with the new list elements inserted just
before the index th element of listName. Each element argument
will become a separate element of the new list. If index is less
than or equal to zero, then the new elements are inserted at
the beginning of the list. If index has the value end , or if it is
greater than or equal to the number of elements in the list,
then the new elements are appended to the list.
lreplace listName first last ?arg1 ... argn?
Lists in Tcl are the right data structure to use when you have an arbitrary
number of things, and you'd like to access them according to their order in
the list. In C, you would use an array. In Tcl, arrays are associated arrays -
hash tables, as you'll see in the coming sections. If you want to have a
collection of things, and refer to the Nth thing (give me the 10th element in
this group of numbers), or go through them in order via foreach.
Take a look at the example code, and pay special attention to the way that
sets of characters are grouped into single list elements.
Example
Searches list for an entry that matches pattern, and returns the
index for the first match, or a -1 if there is no match. By
default, lsearch uses "glob" patterns for matching. See the
section on globbing.
lsort list
Example
*
Matches any quantity of any character
?
Matches one occurrence of any character
\X
The backslash escapes a special character in globbing just the
way it does in Tcl substitutions. Using the backslash lets you
use glob to match a * or ?.
[...]
Matches one occurrence of any character within the brackets.
A range of characters can be matched by using a range
between the brackets. For example, [a-z] will match any lower
case letter.
There is also a glob command that you will see in later sections that uses
glob pattern matching in directories, and returns a list of the matching files.
Example
# Matches
string match f* foo
# Matches
string match f?? foo
# Doesn't match
string match f foo
Example
There are 6 string subcommands that do pattern and string matching. These
are relatively fast operations, certainly faster than regular expressions, albeit
less powerful.
Returns the index of the character in string1 that starts the first
match tostring2, or -1 if there is no match.
string last string1 string2
Returns the index of the character in string1 that starts the last
match to string2, or -1 if there is no match.
string wordend string index
Returns the index of the character just after the last one in the
word which contains the index'th character of string. A word is
any contiguous set of letters, numbers or underscore
characters, or a single other character.
string wordstart string index
Example
if {$first != 0} {
puts "$path is a relative path"
} else {
puts "$path is an absolute path"
}
# If "/" is not the last character in $path, report the last word.
# else, remove the last "/", and find the next to last "/", and
# report the last word.
incr last
if {$last != [string length $path]} {
set name [string range $path $last end]
puts "The file referenced in $path is $name"
} else {
incr last -2;
set tmp [string range $path 0 $last]
set last [string last "/" $tmp]
incr last;
set name [string range $tmp $last end]
puts "The final directory in $path is $name"
}
Example
There are also two explicit commands for parsing regular expressions.
^
Matches the beginning of a string
$
Matches the end of a string
.
Matches any single character
*
Matches any count (0-n) of the previous character
+
Matches any count, but at least 1 of the previous character
[...]
Matches any character of a set of characters
[^...]
Matches any character *NOT* a member of the set of
characters following the ^.
(...)
Groups a set of characters into a subSpec.
Regular expressions are similar to the globbing that was discussed in lessons
16 and 18. The main difference is in the way that sets of matched characters
are handled. In globbing the only way to select sets of unknown text is
the * symbol. This matches to any quantity of any character.
The + symbol behaves roughly the same as the *, except that it requires at
least one character to match. For example, [a-c]+would match a, abc, or
aabcabc, but not an empty string.
Regsub will copy the contents of the string to a new variable, substituting the
characters that match exp with the characters insubSpec. If subSpec contains
a & or \0, then those characters will be replaced by the characters that
matched exp. If the number following a backslash is 1-9, then that backslash
sequence will be replaced by the appropriate portion of exp that is enclosed
within parentheses.
Note that the exp argument to regexp or regsub is processed by the Tcl
substitution pass. Therefore quite often the expression is enclosed in braces
to prevent any special processing by Tcl.
Example
#
# 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]"
[-+]?[0-9]*\.?[0-9]*
[-+]?([0-9])*\.?([0-9]*)
No period: [-+]?[0-9]+
A period without digits before it: [-+]?\.[0-9]+
Digits before a period, and possibly digits after it: [-+]?[0-
9]+\.[0-9]*
(^|[ \t])([-+]?([0-9]+|\.[0-9]+|[0-9]+\.[0-9]*))($|[^+-.])
Or:
(^|[ \t])([-+]?(\d+|\.\d+|\d+\.\d*))($|[^+-.])
#
# Or simply only the recognised number (x's as placeholders, the
# last can be left out
#
regexp {.....} $line x x number
Tip: To identify these substrings: just count the opening parentheses from
left to right.
You can use this technique to see if a word occurs twice in the
same line of text:
#
if { $balance < 0 } {
puts "Parentheses unbalanced!"
}
}
if { $balance != 0 } {
puts "Parentheses unbalanced!"
}
The phase at which a character has meaning affects how many escapes are
necessary to match the character you wish to match. An escape can be
either enclosing the phrase in braces, or placing a backslash before the
escaped character.
Note: You can copy the code and run it in tclsh or wish to see the effects.
Example
#
# Examine an overview of UNIX/Linux disks
#
set list1 [list \
{/dev/wd0a 17086 10958 5272 68% /}\
{/dev/wd0f 179824 127798 48428 73% /news}\
{/dev/wd0h 1249244 967818 218962 82% /usr}\
{/dev/wd0g 98190 32836 60444 35% /var}]
#
# Extracting a hexadecimal value ...
#
set line {Interrupt Vector? [32(0x20)]}
regexp "\[^\t]+\t\\\[\[0-9]+\\(0x(\[0-9a-fA-F]+)\\)]" $line match hexval
puts "Hex Default is: 0x$hexval"
#
# Matching the special characters as if they were ordinary
#
set str2 "abc^def"
regexp "\[^a-f]*def" $str2 match
puts "using \[^a-f] the match is: $match"
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:
There are several array commands aside from simply accessing and creating
arrays which will be discussed in this and the next lesson.
This method makes it simpler to share data between many procs that are
working together, and doesn't pollute the global namespace as badly as
using separate globals for all shared data items.
Another common use for arrays is to store tables of data. In the example
below we use an array to store a simple database of names.
Example
# Create a new ID (stored in the name array too for easy access)
incr name(ID)
set id $name(ID)
#
# Initialise the array and add a few names
#
global name
set name(ID) 0
#
# 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 the following entries: \n [array names array1] \n"
# Create an array
for {set i 0} {$i < 5} {incr i} { set a($i) test }
#
# Get names and values directly
#
foreach {name value} [array get mydata] {
puts "Data on \"$name\": $value"
}
While arrays are great as a storage facility for some purposes, they
are a bit tricky when you pass them to a procedure: they are
actually collections of variables. This will not work:
print12 $array
The reason is very simple: an array does not have a value. Instead
the above code should be:
print12 array
So, instead of passing a "value" for the array, you pass the name.
This gets aliased (via the upvar command) to a local variable (that
behaves the as original array). You can make changes to the
original array in this way too.
Example
#
# The example of the previous lesson revisited - to get a
# more general "database"
#
# Create a new ID (stored in the name array too for easy access)
incr name(ID)
set id $name(ID)
# Loop over the last names: make a map from last name to ID
#
# Store in a temporary array:
# an "inverse" map of last name to ID)
#
set last $name($n)
set tmp($last) $id
}
#
# Now we can easily print the names in the order we want!
#
foreach last [lsort [array names tmp]] {
set id $tmp($last)
puts " $name($id,first) $name($id,last)"
}
}
#
# Initialise the array and add a few names
#
set fictional_name(ID) 0
set historical_name(ID) 0
#
# Some simple reporting
#
puts "Fictional characters:"
report fictional_name
puts "Historical characters:"
report historical_name
Tcl arrays are collections of variables, rather than values. This has
advantages in some situations (e.g., you can use variable traces on them),
but also has a number of drawbacks:
set array(foo,2) 10
set array(bar,3) 11
In Tcl 8.5 the dict command has been introduced. This provides efficient
access to key-value pairs, just like arrays, but dictionaries are pure values.
This means that you can pass them to a procedure just as a list or a string,
without the need for dict. Tcl dictionaries are therefore much more like Tcl
lists, except that they represent a mapping from keys to values, rather than
an ordered sequence.
Unlike arrays, you can nest dictionaries, so that the value for a particular
key consists of another dictionary. That way you can elegantly build
complicated data structures, such as hierarchical databases. You can also
combine dictionaries with other Tcl data structures. For instance, you can
build a list of dictionaries that themselves contain lists.
#
# Create a dictionary:
# Two clients, known by their client number,
# with forenames, surname
#
dict set clients 1 forenames Joe
dict set clients 1 surname Schmoe
dict set clients 2 forenames Anne
dict set clients 2 surname Other
#
# Print a table
#
puts "Number of clients: [dict size $clients]"
dict for {id info} $clients {
puts "Client $id:"
dict with info {
puts " Name: $forenames $surname"
}
}
Example
#
# The example of the previous lesson revisited - using dicts.
#
# Create a new ID (stored in the name array too for easy access)
dict incr db ID
set id [dict get $db ID]
# 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
#
# Some simple reporting
#
puts "Fictional characters:"
report $fictional_name
puts "Historical characters:"
report $historical_name
These methods can also be used for communicating over sockets and pipes.
It is even possible, via the so-called virtual file system to use files stored in
memory rather than on disk. Tcl provides an almost uniform interface to
these very different resources, so that in general you do not need to concern
yourself with the details.
close fileID
Closes a file previously opened with open, and flushes any
remaining output.
gets fileID ?varName?
tell fileID
Returns the position of the access pointer in fileID as a decimal
string.
flush fileID
eof fileID
The file I/O is buffered. The output may not be sent out when
you expect it to be sent. Files will all be closed and flushed
when your program exits normally, but may only be closed
(not flushed) if the program is terminated in an unexpected
manner.
There are a finite number of open file slots available. If you
expect the program to run in a manner that will cause it to
open several files, remember to close the files when you are
done with them.
An empty line is indistinguishable from an EOF with the
command:
set string [gets filename]
Example
#
# Count the number of lines in a text file
#
set infile [open "myfile.txt" r]
set number 0
#
# gets with two arguments returns the length of the line,
# -1 if the end of the file is found
#
while { [gets $infile line] >= 0 } {
incr number
}
close $infile
#
# Also report it in an external file
#
set outfile [open "report.out" w]
puts $outfile "Number of lines: $number"
close $outfile
There are two commands that provide information about the file
system, glob and file.
While retrieving information about what files are present and what
properties they have is usually a highly platform-dependent matter, Tcl
provides an interface that hides almost all details that are specific to the
platform (but are irrelevant to the programmer).
#
# On Windows the name becomes "..\myfile.out"
#
set newname [file nativename [file join ".." "myfile.out"]]
Retrieving all the files with extension ".tcl" in the current directory:
Returns a string giving the type of file name, which will be one
of:
file...................................Normal file
directory........................Directory
characterSpecial.......Character oriented device
blockSpecial.............. Block oriented device
fifo...................................Named pipe
link..................................Symbolic link
socket...........................Named socket
Note: The overview given above does not cover all the details of the various
subcommands, nor does it list all subcommands. Please check the man
pages for these.
Example
#
# Report all the files and subdirectories in the current directory
# For files: show the size
# For directories: show that they _are_ directories
#
set dirs [glob -nocomplain -type d *]
if { [llength $dirs] > 0 } {
puts "Directories:"
foreach d [lsort $dirs] {
puts " $d"
}
} else {
puts "(no subdirectories)"
}
So far the lessons have dealt with programming within the Tcl
interpreter. However, Tcl is also useful as a scripting language to tie
other packages or programs together. To accomplish this function,
Tcl has two ways to start another program:
The open call is the same call that is used to open a file. If the first character
in the file name argument is a "pipe" symbol (|), then open will treat the rest
of the argument as a program name, and will run that program with
the standard input or output connected to a file descriptor. This "pipe"
connection can be used to read the output from that other program or to
write fresh input data to it or both.
If the "pipe" is opened for both reading and writing you must be aware that
the pipes are buffered. The output from a puts command will be saved in an
I/O buffer until the buffer is full, or until you execute a flush command to
force it to be transmitted to the other program. The output of this other
program will not be available to a read or gets until its output buffer is filled
up or flushed explicitly.
(Note: as this is internal to this other program, there is no way that your Tcl
script can influence that. The other program simply must cooperate. Well,
that is not entirely true: the expect extension actually works around this
limitation by exploiting deep system features.)
The exec call is similar to invoking a program (or a set of programs piped
together) from the prompt in an interactive shell or a DOS-box or in a
UNIX/Linux shell script. It supports several styles of output redirection, or it
can return the output of the other program(s) as the return value of
the execcall.
switches are:
-keepnewline
The first program in the pipe will read input from fileName.
<@ fileID
The first program in the pipe will read input from the Tcl
descriptor fileID.fileID is the value returned from
an open... "r" command.
<< value
The first program in the pipe will read value as its input.
> fileName
The standard error from all the programs in the pipe will be
sent to fileName. Any previous contents of fileName will be lost.
2>> fileName
The standard error from all the programs in the pipe will be
appended to fileName.
>@ fileID
The output from the last program in the pipe will be written
to fileID. fileID is the value returned from
an open ... "w"command.
If you are familiar with shell programming, there are a few differences to be
aware of when you are writing Tcl scripts that use the exec and open calls.
You don't need the quotes that you would put around
arguments to escape them from the shell expanding them. In
the example, the argument to the sed command is not put in
quotes. If it were put in quotes, the quotes would be passed
to sed, instead of being stripped off (as the shell does),
and sed would report an error.
If you use the open |cmd "r+" construct, you must follow each
puts with a flush to force Tcl to send the command from its
buffer to the program. The output from the program itself may
be buffered in its output buffer.
exec ls *.tcl
will fail - there is most probably no file with the literal name
"*.tcl".
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"
puts $outfl {
set len [gets stdin line]
if {$len < 5} {exit -1}
#
# 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
# Clean up
file delete $tempFileName
The info command allows a Tcl program to obtain information from the Tcl
interpreter. The next three lessons cover aspects of the info command.
(Other commands allowing introspection involve: traces, namespaces,
commands scheduled for later execution via the after command and so on.)
This lesson covers the info subcommands that return information about
which procs, variables, or commands are currently in existence in this
instance of the interpreter. By using these subcommands you can determine
if a variable or proc exists before you try to access it.
The code below shows how to use the infoexists command to make an incr
that will never return a no such variable error, since it checks to be certain
that the variable exists before incrementing it:
Almost all these commands take a pattern that follow the string match rules.
If patternis not provided, a list of all items is returned (as if the pattern was
"*").
set a 100
safeIncr a
puts "After calling SafeIncr with a variable with a value of 100: $a"
safeIncr b -3
puts "After calling safeIncr with a non existent variable by -3: $b"
set b 100
safeIncr b -3
puts "After calling safeIncr with a variable whose value is 100 by -3: $b"
proc localproc {} {
global argv
set loc1 1
set loc2 2
puts "\nLocal variables accessible in this proc are: [lsort [info
locals]]"
puts "\nVariables accessible from this proc are: [lsort [info
vars]]"
puts "\nGlobal variables visible from this proc are: [lsort [info
globals]]"
}
localproc
The info tclversion and info patchlevel can be used to find out if the revision
level of the interpreter running your code has the support for features you
are using. If you know that certain features are not available in certain
revisions of the interpreter, you can define your own procs to handle this, or
just exit the program with an error message.
The info cmdcount and info level can be used while optimizing a Tcl script to
find out how many levels and commands were necessary to accomplish a
function.
Note that the pid command is not part of the info command, but a command
in its own right.
(Note: There are several other subcommands that can be useful at times)
info cmdcount
Returns the total number of commands that have been
executed by this interpreter.
info level ?number?
info patchlevel
info tclversion
puts "This is how many commands have been executed: [info cmdcount]"
puts "Now *THIS* many commands have been executed: [info cmdcount]"
#
# Use [info script] to determine where the other files of interest
# reside
#
set sysdir [file dirname [info script]]
source [file join $sysdir "utils.tcl"]
Information about procs – info
Example
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.
source fileName
Reads the script in fileName and executes it. If the script
executes successfully, source returns the value of the last
statement in the script.
If there is an error in the script, source will return that error.
If there is a return (other than within a procdefinition)
then source will return immediately, without executing the
remainder of the script.
If fileName starts with a tilde (~) then$env(HOME) will substituted
for the tilde, as is done in the file command.
Example
sourcedata.tcl:
sourcemain.tcl:
Using packages
It is good style to start every script you create with a set of package
require statements to load any packages required. This serves two purposes:
making sure that any missing requirements are identified as soon as
possible; and, clearly documenting the dependencies that your code has. Tcl
and Tk are both made available as packages and it is a good idea to
explicitly require them in your scripts even if they are already loaded as this
makes your scripts more portable and documents the version requirements
of your script.
Creating a package
The next step is to create a pkgIndex.tcl file. This file tells Tcl how to load
your package. In essence the index file is simply a Tcl file which is loaded
into the interpreter when Tcl searches for packages. It should use
the package ifneeded command register a script which will load the package
when it is required. The pkgIndex.tcl file is evaluated globally in the
interpreter when Tcl first searches for any package. For this reason it is very
bad style for an index script to do anything other than tell Tcl how to load a
package; index scripts should not define procs, require packages, or perform
any other action which may affect the state of the interpreter.
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_pathvariable can be extended at
run-time to tell Tcl of new places to look for packages.
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 pathrelative 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 foonamespace 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.
Example
# Set up state
variable stack
variable id 0
}
# Destroy a stack
proc ::tutstack::destroy {token} {
variable stack
unset stack($token)
}
if {[empty $token]} {
error "stack empty"
}
tutstack::destroy $stack
Ensembles
One difference between Tcl and most other compilers is that Tcl will
allow an executing program to create new commands and execute
them while running.
The eval command will evaluate a list of strings as though they were
commands typed at the % prompt or sourced from a file.
The eval command normally returns the final value of the commands being
evaluated. If the commands being evaluated throw an error (for example, if
there is a syntax error in one of the strings), then eval will will throw an
error.
Note that either concat or list may be used to create the command string,
but that these two commands will create slightly different command strings.
Example
#
# Define a proc using lists
#
eval $cmd
}
For instance
eval puts OK
As long as you keep track of how the arguments you present to eval will be
grouped, you can use many methods of creating the strings for eval,
including the string commands and format.
Example
set tmpFileNum 0;
For instance
eval puts OK
Example
set tmpFileNum 0;
set cmd {proc tempFileName }
lappend cmd ""
lappend cmd "global num; incr num; return \"/tmp/TMP.[pid].\$num\""
eval $cmd
The Tcl interpreter does only one substitution pass during command
evaluation. Some situations, such as placing the name of a variable
in a variable, require two passes through the substitution phase. In
this case, the subst command is useful.
Example
set a "alpha"
set b a
set num 0;
set cmd "proc tempFileName {} "
set cmd [format "%s {global num; incr num;" $cmd]
set cmd [format {%s return "/tmp/TMP.%s.$num"} $cmd [pid] ]
set cmd [format "%s }" $cmd ]
eval $cmd
These are:
cd ?dirName?
Changes the current directory to dirName(if dirName is given,
or to the $HOMEdirectory if dirName is not given. If dirNameis
a tilde (~, cd changes the working directory to the users home
directory. If dirName starts with a tilde, then the rest of the
characters are treated as a login id, andcd changes the working
directory to that user's $HOME.
pwd
Returns the current directory.
Example
proc a {} {
b
}
proc b {} {
c
}
proc c {} {
d
}
proc d {} {
some_command
}
a
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 returncommand, or by using
the error command. In either case, a message will be placed in errorInfo,
and the proc will generate an error.
The next value specifies the return status. code must be one of:
These allow you to write procedures that behave like the built
in commands break, error, and continue.
-errorinfo info
info will be the first string in the errorInfo variable.
-errorcode errorcode
value
Example
catch errorproc
puts "after bad proc call: 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"
catch {withError 2}
puts "after proc with an error: ErrorCode: $errorCode"
puts "ERRORINFO:\n$errorInfo\n"
add, which has the general form: trace addtype ops ?args?
info, which has the general form: traceinfo type name
remove, which has the general
form: traceremove 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":
set tracedvar 1
trace add variable tracedvar write [list vartrace $tracedvar]
set tracedvar 2
puts "tracedvar is $tracedvar"
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
set i2 "testvalue"
For instance, a script that extracts a particular value from a file could be
written so that it prompts for a file name, reads the file name, and then
extracts the data. Or, it could be written to loop through as many files as are
in the command line, and extract the data from each file, and print the file
name and data.
The second method of writing the program can easily be used from other
scripts. This makes it more useful.
Example
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.
Example
If a socket channel is opened as a server, then the tcl program will 'listen' on
that channel for another task to attempt to connect with it. When this
happens, a new channel is created for that link (server-> new client), and
the tcl program continues to listen for connections on the original port
number. In this way, a single Tcl server could be talking to several clients
simultaneously.
When a channel exists, a handler can be defined that will be invoked when
the channel is available for reading or writing. This handler is defined with
the fileeventcommand. When a tcl procedure does a getsor puts to a
blocking device, and the device isn't ready for I/O, the program will block
until the device is ready. This may be a long while if the other end of the I/O
channel has gone off line. Using the fileevent command, the program only
accesses an I/O channel when it is ready to move data.
Look at the example, and you'll see the socket command being used as both
client and server, and the fileevent and vwaitcommands being used to
control the I/O between the client and server.
Note in particular the flush commands being used. Just as a channel that is
opened as a pipe to a command doesn't send data until either a flush is
invoked, or a buffer is filled, the socket based channels don't automatically
send data.
Examples
set connected 0
# catch {socket -server serverOpen 33000} server
set server [socket -server serverOpen 33000]
The clock command provides access to the time and date functions
in Tcl. Depending on the subcommands invoked, it can acquire the
current time, or convert between different representations of time
and date.
clock seconds
The clock seconds command returns the time in seconds
since the epoch. The date of the epoch varies for different
operating systems, thus this value is useful for comparison
purposes, or as an input to theclock format command.
clock format clockValue ?-gmt boolean? ?-format string?
The format subcommand formats a clockvalue (as returned
by clock secondsinto a human readable string.
The -format option controls what format the return will be in.
The contents of the string argument to format has similar
contents as the format statement (as discussed in lesson 19,
33 and 34). In addition, there are several more %*descriptors
that can be used to describe the output.
These include:
time
A time of day in one of the formats shown below. Meridian
may be AM, or PM, or a capitalization variant. If it is not
specified, then the hour (hh) is interpreted as a 24 hour clock.
Zone may be a three letter description of a time zone, EST,
PDT, etc.
mm/dd/yy
mm/dd
monthname dd, yy
monthname dd
dd monthname yy
dd monthname
day, dd monthname yy
Example
puts "The book and movie versions of '2001, A Space Oddysey' had a"
puts "discrepancy of [expr {$bookSeconds - $movieSeconds}] seconds in how"
puts "soon we would have sentient computers like the HAL 9000"
More channel I/O - fblocked & fconfigure
The previous lessons have shown how to use channels with files and
blocking sockets. Tcl also supports non-blocking reads and writes,
and allows you to configure the sizes of the I/O buffers, and how
lines are terminated.
A non-blocking read or write means that instead of a gets call waiting until
data is available, it will return immediately. If there was data available, it
will be read, and if no data is available, the gets call will return a 0 length.
If you have several channels that must be checked for input, you can use
the fileeventcommand to trigger reads on the channels, and then use
the fblocked command to determine when all the data is read.
The fblocked and fconfigure commands provide more control over the
behavior of a channel.
The fblocked command checks whether a channel has returned all available
input. It is useful when you are working with a channel that has been set to
non-blocking mode and you need to determine if there should be data
available, or if the channel has been closed from the other end.
The fconfigure command has many options that allow you to query or fine
tune the behavior of a channel including whether the channel is blocking or
non-blocking, the buffer size, the end of line character, etc.
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.
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
if {$len < 0} {
if {$blocked} {
puts "Input is blocked"
} else {
puts "The socket was closed - closing my end"
close $channel;
}
} else {
puts "Read $len characters: $line"
puts $channel "This is a return"
flush $channel;
}
incr didRead;
}
after 120 update; # This kicks MS-Windows machines for this application
set didRead 0
puts -nonewline $sock "A Test Line"
flush $sock;
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 primary interpreter (what you get when you type tclsh) is the empty
list {}.
The example below shows two child interpreters being created under the
primary interpreter {}. Each of these interpreters is given a
variable name which contains the name of the interpreter.
Note that the alias command causes the procedure to be evaluated in the
interpreter in which the procedure was defined, not the interpreter in which
it was evaluated. If you need a procedure to exist within an interpreter, you
must interp eval a proc command within that interpreter. If you want an
interpreter to be able to call back to the primary interpreter (or other
interpreter) you can use the interp alias command.
Example
#
# Alias that procedure to a proc in $i1
interp alias $i1 rtnName {} rtnName
puts ""