You are on page 1of 30

Bonus Chapter A

Tcl: Tool Command Language


In this chapter, we’ll take a look at an exciting development in the Linux world: an extensible, embeddable
programming language that is taking the community by storm. Tcl (pronounced tickle) has been
developed by John Ousterhout and others over a number of years and is now maintained by Scriptics
(http://www.scriptics.com). It has become a popular choice of programming language for a diverse range of
applications, ranging from fast prototype tools through extension languages to industrial control
applications. Tcl is also available on other platforms, including Microsoft Windows and Apple MacOS.

We’ll take a look at some of the key features of Tcl, including its extensibility and its use as the interactive
interface to new applications, and you’ll also see some of the extensions and applications that have been
created. Some of the things we’ll be covering in this chapter are

❑ Tcl commands and control structures


❑ Quoting and substitution
❑ Strings and lists in Tcl
❑ Input and output
❑ Tcl extensions and applications

A Tcl Overview
We begin our look at Tcl with the basics: how to write and execute Tcl programs and the features of the
language.

Our First Tcl Program


To see how Tcl programs are created and executed, let’s write a very simple “Hello World“ program,
hello.tcl. Here’s the source of such a program:

set s "Hello World"


puts $s

Tcl is an interpreted language, and Tcl programs are often referred to as scripts. In this respect, Tcl and the
UNIX shell have a lot in common. In fact, Tcl scripts need to be executed by a Tcl shell, called tclsh.
Let’s run our sample script using tclsh. Note that running a Tcl program this way requires only file read
permission.

$ tclsh hello.tcl
Hello World
$

As with other Linux command interpreters, we can run tclsh and it will prompt us for Tcl commands to
execute immediately. We could have just as well typed in the two lines directly:

$ tclsh
% set s "Hello World"
Hello World
% puts $s
Hello World

Notice that tclsh prompts with % and executes our commands as soon as they are typed. It keeps reading
lines until it gets a complete command. We can use the source command to make tclsh take commands
from a file. Let’s make Tcl read our program again:

% source hello.tcl
Hello World
% exit
$

The exit command causes the Tcl shell to end and returns us to the UNIX shell. We can turn our script
into a UNIX program by specifying which interpreter to use on the first line, just as we did for the UNIX
shell. Save the program in a file, hello2.tcl:

#!/usr/bin/tclsh

set s "Hello World - 2"


puts $s

Now, if we make this script executable, we can run it in the usual way. The location of the Tcl shell,
tclsh, may vary from system to system. In this case, it has been installed in /usr/bin.

Generally, you can use the whereis or which command to show where tclsh is
located within the search path.

$ chmod +x hello2.tcl
$ ./hello2.tcl
Hello World - 2
$

Tcl Commands
In general, all Tcl commands take the same form. They begin with a keyword and are followed by zero or
more arguments. A command is usually terminated by the end of the line, although you can use a
backslash (\) at the end of the line to extend a long command over multiple lines.

Quoting and variable substitution (to which we’ll come shortly) take place first. Then the resulting
command is executed. Some of these commands cause a result to be produced; others cause further
commands to be stored for later execution, such as when procedures are defined or loops are encountered.

Because of the fairly simple syntax, Tcl is easy to learn and simple to extend. However, it does mean that
some operations, such as assignment and calculations, can seem a little awkward at first.

You can place multiple Tcl commands on the same line if you separate them with semicolons.

In this chapter, when we look at the syntax of Tcl commands, we’ll use a notation like this:
command argument1 argument2 ?option1? ?option2? …

This means that the (fictitious) Tcl command, command, takes two mandatory arguments, plus some
optional arguments that may be used to change its behavior. You’ll find this type of syntax used in the
official Tcl/Tk documentation.

Variables and Values


Tcl deals essentially with strings and groups of strings. Most values can be dealt with as strings, and
where necessary, Tcl will automatically convert between types, for example to perform arithmetic on
numbers. To assign to a variable, creating the variable if necessary, we use the set command. You can
follow the rest of this section as a walk-through example in the Tcl shell.
$ tclsh
% set a 123
123
% set hello there
there
%

Variable names are case-sensitive and can be any length. Special characters (such as whitespace) in
variable names and values need to be quoted, which you do by enclosing the name or value in double
quotes. As in the UNIX shell, you need to take care with quoting. We’ll learn more about different quoting
methods in Tcl shortly.
% set "hello there" "fare well"
fare well
%

To examine the current value of a variable, use the set command again, but this time don’t give it a value
argument.
% set hello
there
% set "hello there"
fare well
%
When using Tcl interactively like this, the interpreter prints the result of each command. In the case of the
set command, we see the variable’s new value printed after an assignment, or the variable’s present
value if no new value is given. This is because the set command itself returns the current value of the
variable.

To use the value of a variable in another command, we prefix its name with a $, just as we do in the UNIX
shell. In our example program, we assigned a string to the variable s and then used it in the puts
command, which wrote it to the screen, by referring to it as $s. You can use braces to quote variable
names that contain spaces.

% puts $a
123
% puts [expr $a + $a]
246
% puts ${hello there}
fare well
%

To use the result of one command in another, we place the command in square brackets, as in the second
example above. This causes the Tcl interpreter to execute the command and return a result that is used in
its place. This is very like the way we use $(command) in the UNIX shell. In the same example, we use the
expr command, which evaluates a Tcl expression and returns the result.
We can remove a variable from the Tcl interpreter by using unset.

% unset a
% puts $a
can't read "a": no such variable

Quoting and Substitution


As it is in the UNIX shell, quoting is crucial in Tcl, and there are a number of different quoting and
substitution mechanisms in Tcl that you need to understand.

Variable Substitution
In a Tcl command, a variable is replaced with its value whenever its name appears preceded by a dollar
sign, $. This is the mechanism we would use to pass parameters to commands and store or use general
purpose values.

% set apple 5
5
% set orange $apple
5
%

Command Substitution
A command contained within square brackets, [], is executed and replaced with its result.

% set pear [expr $orange + 1]


6
%
Backslash Substitution
We use a backslash, \, to turn off the special meaning of the following character. We can use it to create
variables and values with unusual characters (say, dollar signs) in them.

A backslash at the end of a line is used as a continuation marker. (It negates the special meaning of the
newline character.)

% set fred \$apple


$apple
% set lemon [expr $pear \
+ 1]
7
%

String Quoting
We use double quotes to create strings containing white space and other special characters. However, as it
does in the Linux shell, this type of quoting allows substitutions to take place. Variables will be substituted
with their values when referenced inside double quoted strings.

% set joe "Our fruits are orange: $orange, and pear: $pear"
Our fruits are orange: 5, and pear: 6
%

Brace Quoting
Another form of quoting, using braces, {}, doesn’t allow variable substitutions and creates a verbatim
group of strings. Similar to the use of single quotes in the Linux shell, braces prevent substitution.
We can also use braces for storing procedure bodies. We want any variable and command substitutions to
be performed when the procedure is executed, not when it is defined.

% set moe {Our fruits are orange: $orange, and pear: $pear}
Our fruits are orange: $orange, and pear: $pear
%

The difference between joe and moe is clear. The variable substitutions in moe have been prevented by
the braces.

When a group surrounded by braces is evaluated, the outermost set of braces is effectively removed. If
further evaluated, the resulting group will have substitutions performed. Braces can be nested. For
example:

% set fred 1234


1234
% set cmd1 [puts $fred]
1234
% set cmd2 {[puts $fred]}
[puts $fred]
% set cmd1
% set cmd2
[puts $fred]
The variable cmd1 takes a null value, the result of printing the variable fred. The variable cmd2 takes the
value of the command itself, not the result. Here, the braces prevent the evaluation of puts and the
expansion of $fred.

This can all seem quite confusing, so let’s look at what the Tcl shell is actually doing when it encounters
different types of quoting.

Tcl Interpreter Substitution


Tcl always performs a single round of substitutions. When a command is executed, the line is broken up
into a series of “words” by separating on white spaces (except new lines, which are command separators).
Anything contained by two double quotes "...", two square brackets [...], or two braces {...} is
viewed as a single “word.” Each “word” is then processed by the interpreter as follows:

❑ If the “word” starts with a $, Tcl performs variable substitution.


❑ If the “word” starts with an open square bracket ([), Tcl performs “command
substitution,” invoking the Tcl interpreter again and processing this “word” as a new
Tcl script. The results replace the original “word.”
❑ If the “word” starts with an open brace ({), everything within the “word” (minus the
opening and closing braces) is left as is. This includes white space, $, and so on.
❑ If the “word” contains a backslash \, Tcl performs “backslash substitution.”

So to summarize:

Word Substitution Result


$a variable value of a
[commands] command result of interpreting commands
{the formatted none the formatted text
text}
\$word backslash $word

If the results contain further variable references or nested commands, these are not expanded. This
consistency means that the execution of a Tcl command is predictable. However, you do need to carefully
consider your choice of quoting method. Let’s try to make it clearer with an example or two.

% set age 35
35
% set str {I'm $age years old}
I'm $age years old
% set cmd {puts "$str"}
puts "$str"
%

These assignments set up two levels of indirection. The variable cmd refers to the variable str, which, in
turn, refers to the variable age, which has the value 35. If we wish to use cmd to get at the value of age,
we must arrange for additional substitutions.
We can gain additional rounds of substitutions using eval or explicitly by calling the subst command,
which is implemented in Tcl versions 7.4 and above. The eval command takes its arguments, gathers
them together as a single string, and then passes this string to the Tcl interpreter, which executes it.

% eval $cmd
I'm $age years old
%

The value of cmd—the command puts "$str"—is executed. The string, str, has the value I'm $age
years old. If we want to expand this string before performing the puts command, we need to use
subst. The subst command performs variable, command, and backslash substitutions on strings and
then returns the newly formed string. The difference is that the argument to eval must be a command,
which is executed. subst performs substitution but doesn’t evaluate the result.

% subst $cmd
puts "I'm $age years old"
%

Here, the subst command returns a valid command, which will output a string containing the current
value of age at the time it is evaluated.

% eval [subst $cmd]


I'm 35 years old
%

The control of substitution and execution makes Tcl a very powerful tool. Because the eval command can
execute an arbitrary string, Tcl programs can communicate and pass commands for execution to one
another. You can use this technique to develop Tcl debuggers in Tcl itself which control the execution of
other Tcl programs. You can also use it in Internet programming, where a server application may wish to
send a program to the client for execution, or may generate a Tcl script on the fly.

Comments
Tcl programs may contain comments. Comments act almost as if they were a Tcl command with the name
#, which has no effect. This means that, unlike some similar languages, we can continue across many lines
by ending each line with a trailing backslash. One peculiarity is that semicolons are not significant in
comments, so they can continue after one.

# This is a comment
set a 123 ; # This is a trailing comment, note the semicolon is needed to separate it
\
but the comment continues onto this line; and this is still comment

Calculation
Tcl provides a command, expr, to perform arithmetic calculations. This is because the Tcl interpreter itself
doesn’t contain mathematical functions; rather it relies on additional functionality provided by
commands. Although Tcl can perform limited arithmetic, such as that required for loop termination, the
bulk of calculation is carried out by expr.

The expr command takes one or more arguments and calculates a result. The arguments may be
operands, operators, and parentheses. Operands may be specified as numbers (integers or floating point),
logical expressions, strings that will be converted to numeric values (including substitutions), variable
references, and nested commands.

Examples are

% expr 5 + 5
10
% set a 5
5
% set b 6
6
% expr $a+$b
11
% expr 2*"$a.$b"
11.2
% expr 3*(1+[string length "hello"])
18
%

Valid operators for expr are listed below, in decreasing order of precedence. Comparison operators
produce 1 for true, 0 for false.

Operator Precedence Description


- + ~ ! Unary minus and plus, bitwise NOT, logical
NOT.
* / % Multiply, divide, remainder.
+ - Add, subtract.
<< >> Shift left, shift right (both signed).
< > <= >= Less than, greater than, less than or equal, greater
than or equal.
== != Equal, not equal.
& Bitwise AND.
^ Bitwise XOR.
| Bitwise OR.
&& Logical AND.
|| Logical OR.
cond?yes:no Ternary conditional, as in C. If cond is nonzero,
return the value of yes, else return the value of
no.

As an example of the last operator, consider the following:

% set k "foo"
foo
% set result [expr {($k == "foo") ? "pass" : "fail"}]
pass
%

Tcl also supports the following mathematical functions in expressions, each of which invokes the math
library function of the same name:

acos asin atan atan2


ceil cos cosh exp
floor fmod hypot log
log10 pow sin sinh
sqrt tan tanh

Because the construct,

set a [expr $a + 1]

occurs so often, the incr command was introduced. By default, it increments a variable’s value by 1. It
can also be given an explicit increment.

% set a 5
5
% incr a
6
% incr a 5
11
% set a
11
%

Control Structures
Tcl supports a wide range of control structures for program flow, including conditionals, loops, and
selections.

if
if expr1 ?then? body1
if expr1 ?then? body1 ?else? body2
if expr1 ?then? body1 elseif expr2 ?then? body2 ?elseif? ... ?else? ?bodyN?

The if command evaluates expr1 in the same way as expr. If the result is a Boolean true (a nonzero
numeric value, or a string with the value true or yes), body1 is executed and its value returned.
Otherwise, the process repeats with the next elseif clause if there is one (there may be zero). If none of
the expressions evaluate to a Boolean true, the else clause (if present) is executed and its result
returned. If no body is executed, the if command returns an empty string. A Boolean false would be
represented by numeric zero or the string values false or no.
The body sections need to be single items: a single “word” or a bracketed command. Note that the then
and else keywords are not necessary, but make things easier to read. It’s recommended practice to build
the construct like this:

if {expr1} {
body1
} else {
body2
}

The reason for the odd layout of braces, especially having the brace and else on the same line, is to make
sure the interpreter knows that there’s more coming. Without it, we have a complete if statement. Tcl
would execute it, assume the next line started with the command else, and give you a syntax error.

switch
switch ?options? string pattern body ?pattern body ...?
switch ?options? string {pattern body ?pattern body ...?}

The Tcl switch command is the direct analog of the Linux shell case statement and the C switch
(which works only on integer values). The given string is compared in turn with each of the patterns.
(See the later sections on matching.) When a match is found, the associated body is executed and its result
returned. A pattern of default acts as a catchall, matching all strings not matched by any other pattern.

Options control the matching technique to be used. These may be

switch option Description


-exact Match the string to the pattern exactly.
-glob Use glob (shell-style) matching (as in UNIX filename matching).
-regexp Use regular expression matching as in the UNIX egrep utility.
-- An option used to mark the end of options, in case a string starts with a
hyphen.

You can use the two different ways of specifying the patterns and bodies to gain fine control over
substitutions in the arguments to switch. Placing patterns and bodies in braces {} has two effects: It
prevents substitutions, and it allows you to spread the patterns and bodies over several lines without
having to quote the end of lines with backslashes.

Here’s an example we’ll meet again later that illustrates how switch can be used to process program
arguments:

foreach arg $argv {


switch -glob -- $arg {
-v {set VerboseFlag true}
-f* {set FileToRead [string range $arg 2 end]}
-h {puts stderr $Usage; exit 1}
default {error "bad argument: $arg\n$Usage"; exit 1}
}
}

for
for start test next body
The Tcl for statement is very similar to the for statement in C. The start, next, and body arguments
must be Tcl command strings, and test must yield a Boolean expression. The for command executes the
start string and then repeatedly evaluates the test expression. If it yields a Boolean true value, the
body string is executed. If a break command is encountered in the body, the loop is terminated
immediately. A continue command in the body causes the for loop to be restarted. Each time the loop
is completed, the next expression is evaluated.

% set n 5
5
% set result 1
1
% for {set i $n} {$i} {incr i -1} {
set result [expr $result * $i]
}
% set result
120

You may recognize this as calculating a factorial. We’ll see a little later how to create a Tcl procedure to
perform this function.

while
while test body

The while command acts in a similar way to the while statement in C. It repeatedly evaluates the test
string and executes the Tcl command body until test yields a Boolean false value. As with the for
command, a break or continue command causes the loop to be terminated or restarted, respectively. As
an example, this code emulates the Linux cat command on the file foo. See the section on input and
output for more about file handling.

% set fd [open "foo" "r"]


file3
% while {[gets $fd line] != -1} {
puts "$line"
}

<The file "foo" is displayed at this point>

% close $fd

Error Handling
When the Tcl interpreter encounters an error, it usually prints a message and halts. This is not always the
desired result from within a program, so Tcl provides a facility to raise and trap errors and to recover from
them.
error
error message ?info? ?code?

The error command generates an error and terminates execution unless the error is caught or trapped.
The message, message, is provided as an indication to the application as to what went wrong. It is
printed if the interpreter default error action is taken.

If present, the info argument is added to the global variable errorInfo. This variable accumulates
information about command nesting when errors occur. As each Tcl command unwinds, it adds to
errorInfo to provide a stack trace. Similarly, the code argument is intended to add machine-readable
information to the global variable errorCode. Refer to the Tcl documentation for further details.
catch
catch script ?varname?

The catch command evaluates the given script, trapping any errors that occur. If present, the variable
varname will be set to the return value of the script or any error message returned.

Here’s an example taken from the Tcl program, concord, that we’ll see later:

if [catch {set Input [open $FileToRead r]} res ] {


puts stderr "$res"

If the attempt to open the input file fails, the program can carry on, rather than stopping with an error.
catch is extremely useful in this regard, as there are many instances of programs that would not run
successfully without trapping the error in this fashion.

String Operations
As Tcl is essentially a string-based language interpreter, it’s no surprise that it provides a rich set of
commands for manipulating strings and string values.

The main string manipulation command is string. It takes one option and a number of arguments,
depending on the operation required.

string
string option arg ?arg? …

The options (and associated arguments) to string are given below. Where a string index is used or
returned, it is always base zero; that is, the first character is given an index of 0, the second an index of 1,
and so on.

first, last, compare


string first string1 string2
string last string1 string2
string compare string1 string2
first and last search for the appropriate occurrence of string1 in string2 and return the index into
string2 of the start of the match found, or –1 if string1 doesn't occur in string2.

% string first "o" "foobar"


1
% string last "o" "foobar"
2

compare performs a character-by-character comparison. Returns -1, 0, or 1 if string1 is less than, equal
to, or greater than string2, in the same way as strcmp in C.

% string compare "foo" "bar"


1

index
string index string num

index returns a single character from string. The character at index position num is returned, where the
first character is deemed to be index 0.
% string index "ABC" 1
B

length
string length string

length returns the length of string.

% string length "ABC"


3

match
string match pattern string

match returns 1 if string matches pattern, 0 otherwise. The matching performed is similar to that of
the Linux shell, where so-called glob matching is used in filename expansion.

% string match "*B*" "ABC"


1
% string match "*B*" "ZZZ"
0

range
string range string first last

range returns a substring of string from index position first (starting at zero) to last, which may be
the keyword end to specify the end of the string.

% string range "ABCDEF" 3 end


DEF
tolower, toupper
string tolower string
string toupper string

These arguments return a string made from the characters of string converted to the appropriate case.

% string tolower "ABC"


abc
% string toupper "abc"
ABC

trim, trimleft, trimright


string trim string ?chars?
string trimleft string ?chars?
string trimright string ?chars?

These arguments return a substring of string, but with certain characters from the set chars removed.
trim removes leading and trailing characters, while trimleft and trimright remove leading and
trailing characters, respectively. If chars is not specified, white space is removed.

% string trim "foobar" "for"


ba
% string trimleft "foobar" "for"
bar
% string trimright "foobar" "for"
fooba

wordstart, wordend
string wordstart string index
string wordend string index

These return the index in string of the first character of/after the word containing the position index.

% string wordstart "Tcl is Cool" 5


4
% string wordend "Tcl is Cool" 5
6

Glob Matching
In the string match and the switch -glob commands, a form of pattern matching is used. This
involves wild cards that will be familiar to you if you’ve used the Linux shell, as they are the same as those
used for the shell case statement and for matching filenames.

For a string to match a pattern, they must be identical, except for special sequences that may appear in the
pattern. These are
* Matches any sequence of characters.
? Matches any single character.
[…] Matches any character in a set.
\<char> Matches the otherwise special
character <char>; e.g. \* matches a
*.

regexp and regsub Matching


In the regexp and regsub commands that we’ll meet a little later and the -regexp option of the switch
command, patterns are used to match strings. These patterns may contain complex matches, specified by
regular expressions, which will be familiar to you if you’ve used the egrep utility or the ed/vi/emacs
editors.

For a string to match a regular expression, they must be identical, with the exception of special sequences
that may occur in the pattern specifier. These are

. Matches any character.


* Indicates zero or more matches of the previous item.
+ Indicates one or more matches of the previous item.
| Indicates a choice between two expressions.
[…] Matches any character in a set.
^ Matches the start of the string.
$ Matches the end of the string.
\<char> Matches the otherwise special character <char>; e.g.,
\* matches a *.

You can identify submatches in the regular expression by surrounding them in parentheses, (). You can
use these for extracting submatches in a match or for substitutions in regsub.
append
append varname ?value value …?

The append command adds the value arguments to the end of the current value of the variable
varname. This is more efficient than an equivalent assignment if the argument values are long—why copy
a string and throw the original away when you can just add to the original?
Given,

% set a "This is: "


This is:
% set b "a long string"
a long string

the following two Tcl fragments are equivalent (giving "This is: a long string"), but the second can
be more efficient:

% set a $a$b
% append a $b

regexp
regexp ?options? expression string ?match? ?submatch submatch …?

The regexp command uses regular expressions to look for a match in string. It returns 1 if it finds a
match, 0 otherwise. The expression is of a form given above for regexp matching. If the regular
expression is found in the string, the variable match is set to the part of the string that matches the
expression. The optional submatch variables are set to the parts of the string that match parenthesized
submatches in the expression. This example looks for a number starting with a 3:

% regexp {3[0-9]*} 3112


1
% regexp {3[0-9]*} 5112
0

these can also be written

% regexp 3\[0-9]* 3112


1
% regexp 3\[0-9]* 5112
0

Note that we have to use braces or backslashes to prevent Tcl from interpreting the square bracket as the
start of a command. Options that may be used to control the matching are

-nocase Causes the match to be case-insensitive.


-indices Causes the submatch variables to be set to
pairs of indices delimiting the match substrings.

regsub
regsub ?options? expression string subst varname

The regsub command matches string against a given regular expression and writes a new string to the
variable varname. The new string is a copy of the subst argument with substitutions carried out. These
are as follows:
❑ & or \0 is replaced by the matching string.
❑ \1, …, \9 is replaced by the first, …, ninth submatch (parenthesized subexpression).
This example changes a leading 3 to a 5:

% regsub {3([0-9]*)} 3112 {5\1} res


1
% set res
5112

Try It Out—String Matching and Substitution

Here’s an example of string matching and substitution:


% set date1 "Wednesday August 11 1999"
Wednesday August 11 1999
% set date2 "Saturday April 1 2000"
Saturday April 1 2000
% set weekend "(Saturday|Sunday)"
(Saturday|Sunday)
% regexp $weekend $date1
0
% regexp $weekend $date2 day
1
% puts $day
Saturday
% set parsedate "(\[A-Z]\[a-z]+) +(\[A-Z]\[a-z]+) +(\[0-9]+) +(\[0-9]+)"
([A-Z][a-z]+) +([A-Z][a-z]+) +([0-9]+) +([0-9]+)
% regexp $parsedate $date2 date day month dom year
1
% puts "$date breaks into $day, $month, $dom, $year"
Saturday April 1 2000 breaks into Saturday, April, 1, 2000
% regsub $parsedate $date2 {\4 \2 \3} newdate
1
% puts $newdate
2000 April 1
%

How It Works
Several calls to regexp are shown, matching dates to days in the weekend and extracting information
from a date. A substitution is used to reorder the format of the date. The variable parsedate is set to an
expression that describes a value date. It uses several key forms:

❑ [A-Z][a-z]+ matches a capitalized word of two or more letters, such as the day or
month name.
❑ <space>+ matches a sequence of one or more spaces, as occurs between date elements.
❑ [0-9]+ matches a decimal number, such as the date’s year number.

Note that when you use backslashes or square brackets in matches or substitutions, you need to protect
them against expansion when they’re inside double quotes. Alternatively, using braces prevents all
expansions.

Arrays
Arrays are special variables accessed with an index in parentheses following the array name. You can’t
manipulate arrays as an entity by using their name alone.

Tcl supports a form of array known as an associative array. This powerful concept is implemented by
allowing arrays with arbitrary indices; that is, arrays can use strings to address their elements. This allows
us to store information about general objects in an array, using the object itself as the array index. An
example will make things clearer.
When we’re dealing with associative arrays, indices and elements are often referred to
instead as keys and values.

% set myarray(0) 123


123
% puts $myarray
can't read "myarray": variable is array
% puts $myarray(0)
123
%

Array indices are not limited to numeric values, but it’s sensible to limit them to either numbers or a string
without spaces or special characters, as quoting and variable substitution can become difficult. A common
technique is to employ a variable to contain the value of the index and set the variable to the desired value.
This eliminates the difficulties with special characters.

% set myarray(orange) 1
1
% set myarray(apple) jim
jim
%

array
array option arrayname ?arg arg …?

Arrays may be manipulated by the array command, which takes a number of options. The options to
array (and associated arguments) are given here.

exists
array exists arrayname

This returns 1 if arrayname is an array, 0 otherwise.

get
array get arrayname ?pattern?

This returns a list of pairs of elements containing array indices and associated values. If the optional
pattern is given, it returns only the array elements whose indices match the (glob style) pattern.
names
array names arrayname ?pattern?

This returns a list of array index names, optionally only those that match pattern.

set
array set arrayname list

This sets the elements of the array arrayname. The list must be like the one returned by get, with a
series of indices and their values.

size
array size arrayname
This returns the size of an array.

% array exists myarray


1
% array size myarray
3
% array names myarray
orange 0 apple
% array get myarray
orange 1 0 123 apple jim
%

We can also perform a search of arrays element-by-element with array command options startsearch,
anymore, nextelement, and donesearch. See the Tcl documentation for further details.

Lists
In addition to strings and arrays, Tcl also has full support for lists. These are groups of Tcl objects,
numbers, strings, and other lists that can be grouped together and manipulated. Tcl allows lists to be
created, added to, converted to and from strings, and used for controlling loops. We’ll outline a number of
list manipulation commands below.

The syntax for Tcl lists is the brace group. In fact, we’ve already seen lists in action with the bodies for
loops.

% set mylist {bob {1 2 3 {4 5}} fred}


bob {1 2 3 {4 5}} fred

This assignment has created a list of three elements. The second element is itself a list of four elements.
Notice that Tcl displays braces around lists only when necessary, to show the correct structure. As with
arrays and strings, list elements can be accessed by position with an index, starting from zero.

If you have a need for an array, but will only be using numeric indices, you should consider using a list
instead. They can often prove to be more efficient than the more general arrays.
list
list ?arg arg arg …?

We can create lists programmatically in a number of ways, but perhaps the easiest is with the list
command. list takes all of its arguments and creates a list, each element of which is a copy of an
argument. If no arguments are given, list produces an empty list.

We need to add braces and backslashes as necessary, so that the original arguments may be extracted
using lindex (see below) and the list can be evaluated by eval (if the first argument is the name of a
command).

% set elist [list puts "Hello World"]


puts {Hello World}
% eval $elist
Hello World

split
split string ?delimiters?
The split command is used to create a list from a string. By default, split creates a list containing the
distinct words (delimited by white space) in string. The optional delimiters argument is a list of
characters used to determine the places to split the string. If the delimiters list is empty, rather than
omitted altogether, all characters in the string are individually included as list elements.

% split "hello there jim"


hello there jim
% split "hi there everyone" {e}
{hi th} r { } v ryon {}

Notice that the delimiters don’t appear in the list, but that empty elements will be created if there are
adjacent delimiters in the string.

When you’re using split, you should take care with backslash substitutions: \$ will be converted to a $,
not a \ and a $. In other words, to get \ included as an element in a list, you have to use \\. To include the
literal $foo, you need to use \$foo.

join
join list ?delimiter?

The join command is the opposite of split. It creates a string by recursively joining together elements
of a list. It uses the optional delimiter string (if there is one) to separate elements. It defaults to a space.

% join {1 2 fred {bill bob} 37} ","


1,2,fred,bill bob,37
%
concat
concat ?arg arg arg …?

The concat command takes several arguments, treats them as lists, and concatenates them. If you supply
no arguments, you’ll get an empty result, not an error. When it joins lists, concat will effectively strip off
one level of braces so that top-level members of list arguments become top-level members of the resulting
list.

Arguments have leading and trailing spaces removed. Each list element is separated by a single space.

% concat {1 2} {3 {4 5} 6} 7 8
1 2 3 {4 5} 6 7 8

lappend
lappend listvar ?arg arg arg …?

We can use the lappend command to add further elements to a list. It’s more efficient than creating a new
list with, for example, concat.

Each of the arguments to lappend is added as a list element to the existing list listvar. Each element
added is separated by a single space. Note that, unlike concat, no level of braces is removed; each
argument is added as is.

% set alist {1 2 3 {4 5}}


1 2 3 {4 5}
% lappend alist 6 {7 8}
1 2 3 {4 5} 6 {7 8}
%
lindex
lindex list index

We use the lindex command to retrieve an element at position index from a list. The list elements are
numbered starting from zero. If the requested element is not in the list, an empty string is returned. We
can use the keyword end in place of an index to refer to the last element in the list.

% set alist {1 2 3 {4 5}}


1 2 3 {4 5}
% lindex $alist 3
4 5
%
linsert
linsert list index arg ?arg arg …?

We can use the linsert command to insert elements into a list. The new list created by linsert
comprises list, with the arg arguments inserted just after the element referred to by index. If index is
zero (or negative), the new elements are placed at the beginning of the new list. If the index is greater than
or equal to the length of the list, or is the keyword end, the new elements are added at the end of the list.
Notice that the original list is not itself affected by the linsert command.

% set alist {{4 5}}


{4 5}
% linsert $alist 0 1 2 3
1 2 3 {4 5}
% linsert [linsert $alist 0 1 2 3] end {6 7 8}
1 2 3 {4 5} {6 7 8}
%

llength
llength list

We can obtain the length of a list by the llength command, which returns a string containing a decimal
number corresponding to the length of the list list. An empty list returns a length of zero.

% llength hello
1
% llength "1 2 3"
3
% llength {a b {c d}}
3

Note that Tcl will try to interpret many different arguments as lists automatically, in which case quoted
and unquoted strings and brace groups are all treated as lists. Each “word” within a string is interpreted
as an individual element.

lrange
lrange list first last

A consecutive subset of a list may be extracted by the lrange command. The elements in positions first
through last in the list list are extracted, and a new list with just those elements is returned.

% set alist {1 2 3 {4 5} 6 7 8}
1 2 3 {4 5} 6 7 8
% lrange $alist 2 4
3 {4 5} 6
%
lreplace
lreplace list first last ?arg arg arg …?

We can replace elements in a list by the lreplace command. The elements in positions first through
last in the list list are replaced with the given arguments. Elements are deleted and new ones inserted.
The element in position first must exist, although the number of elements to be deleted and the number
to be inserted need not be the same. We can use the keyword end for first or last to specify the last
element in the list.

% set alist {1 2 3 {4 5} 6 7 8}
1 2 3 {4 5} 6 7 8
% lreplace $alist 2 end bill {bob joe}
1 2 bill {bob joe}
%

lsearch
lsearch ?option? list pattern

We can use lsearch to search a list for elements that match a pattern. The options are
-exact, -glob (the default), and -regexp, which determine the type of matching to be undertaken. See
the section on strings above for more details of Tcl matching.

The given list is searched for elements that match the specified pattern. The index of the first
matching element is returned, or -1 if no such element is found.

% lsearch {apple pear banana orange} b*


2

lsort
lsort ?option? list

We can use the lsort command to sort a list according to a number of criteria. The default action is for
the elements of the list to be sorted into ascending alphabetical order by ASCII collating sequence, which
is the order of alphanumeric characters, plus punctuation. As far as the computer’s concerned, it’s the
numerical order of the byte values of the characters. For example,

% lsort {apple pear banana orange}


apple banana orange pear
The options to lsort that change the type of sort performed include the following:

lsort option Description


-ascii Normal string comparison, the default.
-integer Elements are converted to integers and compared.
-real Elements are converted to floating point and compared.
-increasing Sorts in ascending order.
-decreasing Sorts in descending order.
-command Use command as a sort function. This must be a
procedure taking two list elements as arguments and
returning an integer less than zero, zero, or greater than
zero depending on whether the first element is to be
considered smaller, equal, or larger than the second.

foreach
foreach varname list body

We can use foreach to separately evaluate or use each element in a list. The command assigns to
varname each element of the list argument in turn and executes body. Here’s an example:

% foreach i {1 2 3 bill bob joe} {


puts "i is $i "
}
i is 1
i is 2
i is 3
i is bill
i is bob
i is joe
%

This is a simplification of the foreach command; in the general case, foreach can take a list of variable
names and a number of lists. In this case, the lists are traversed at the same time. Each time around the
loop, each of the variables is assigned for the next element in the corresponding list.

% foreach {a b} {1 2 3 4 5 6} {puts "a is $a, b is $b"}


a is 1, b is 2
a is 3, b is 4
a is 5, b is 6
% foreach a {1 2 3} b {4 5 6} {puts "a is $a, b is $b"}
a is 1, b is 4
a is 2, b is 5
a is 3, b is 6
%

A break or continue command in a foreach body has the same effect as in a for command. If a
break command is encountered in the body, the loop is terminated immediately. A continue command
in the body skips the remainder of the loop for the present iteration.
A foreach loop in conjunction with array names makes for a useful way to scan an array:

% set myarray(orange) 1
1
% set myarray(apple) 2
2
% set myarray(banana) 3
3
% foreach fruit [array names myarray] { puts "fruit is $fruit" }
fruit is orange
fruit is apple
fruit is banana
%

Procedures
Procedures in Tcl are similar to those of other languages, but with some interesting differences.
Procedures are introduced to the Tcl interpreter with the proc command. We must define all procedures
with proc before they can be called.

proc name args body


The proc command defines the procedure name. It stores the body (a list of executable commands) to be
run when the procedure is called. Arguments passed to the procedure when it is executed are substituted
for the list of variables, args.

Procedures exit when the last command in the body is executed. The value of a procedure is the value of
the last command executed. We can prematurely terminate procedures with the return command.

return ?option? ?string?

The optional value string is returned as the value of the enclosing procedure. If string is omitted, an
empty string is returned. We can specify the action taken when a procedure needs to return an error by
one of several options to return. See the Tcl documentation for details.

Try It Out—Procedures

Here’s a simple procedure for calculating a factorial:


% proc fact {n} {
set result 1
while {$n > 1} {
set result [expr $result * $n]
set n [expr $n - 1]
}
return $result
}
% fact 7
5040

How It Works
The procedure fact builds up the required factorial by iteration. Note that the procedure uses a local
variable: result. As we don’t need to declare variables before they are first used in Tcl, all new variables
encountered in Tcl procedures are assumed to be local; that is, new variables created inside a Tcl
procedure don’t exist outside of the procedure.

upvar
upvar ?level? oldname newname

To gain access to a global variable, we must use the command upvar, which allows access to variables at a
higher level in the call stack. At its simplest, it allows a procedure to use a variable declared in an
enclosing scope (possibly the procedure that called it), or a global variable.

The upvar command creates a new variable, newname, within the procedure. Accesses and assignments
to it are equivalent to using the variable oldname, which exists outside the procedure. We can use the
level argument to determine precisely which stack frame the oldname variable is from.

The most common case is simply to provide access to global variables with

upvar variable variable

Here is an example of the proper use of upvar:

% set result 1
1
% proc badset2 {} {
set result 2
}
% badset2
2
% set result
1
% proc goodset2 {} {
upvar result result
set result 2
}
% goodset2
2
% set result
2
%

Input and Output


Tcl provides a sophisticated system for input and output. In this section, we’ll cover some of the basic
facilities. Refer to the Tcl documentation for further details.
open
open filename ?access? ?permissions?

The open command establishes a connection to a file or a command pipeline. It combines the functionality
of fopen and popen in C.

open returns a channel identifier (the Tcl equivalent of a file stream) that can be used in future calls to
other input and output commands like gets, puts, and close. Standard streams are automatically
available to Tcl programs as stdin, stdout, and stderr.

If present, the access argument determines how the file is to be opened. It’s a string value and takes the
same values as the modes used by fopen in C:

access modes Description


r Opens filename for reading. The file must exist. This is the default.
r+ Opens filename for reading and writing. The file must exist.
w Opens filename for writing. The file will be created or truncated.
w+ Opens filename for writing and reading. The file will be created or
truncated.
a Opens filename for writing. New data will be written at the end of the file.
a+ Opens filename for reading and writing. The initial file position is set to
the end of the file.

Alternatively, we can set the access modes using a Tcl list of POSIX flags, including RDONLY, WRONLY,
RDWR, APPEND, CREAT, EXCL, NOCTTY, NONBLOCK, and TRUNC.

The permissions argument, if present, is an integer value (default octal 666) used to set the initial
permissions on any file created by the call to open.

If the filename argument has a pipe symbol (|) as its first character, it’s treated as a command pipeline.
The Linux command is executed and its output made available for reading via the returned channel
identifier.
close
close channelID

The close command closes the channel given by channelID, which must have been obtained from a
previous call to open (or in Tcl 7.5 or later, socket). The channel is the equivalent of a C file descriptor.
read
read ?-nonewline? channel ?bytes?

The read command reads all of the input available (or, if bytes is set, a specified number of bytes) on a
channel identifier.

If the -nonewline option is specified, read doesn’t return the last character in the input if it’s a newline.
The read command translates end of line sequences into single newlines.

gets
gets channel ?varname?

The gets command reads the first line of input and stores it in a variable varname if this has been
specified. Each subsequent use of gets on a channel results in the next line of data being read, stopping
when an end of file character is found. The value returned by gets is the number of characters read into
varname, -1 on error, or the string itself if varname is not specified.

% set myfile [open "|date" r]


file3
% gets $myfile fred
28
% close $myfile
% set fred
Wed Aug 11 15:03:35 BST 1999

puts
puts ?-nonewline? ?channel? string

The puts command writes output to a channel (defaults to the standard output). The string is written to
the channel followed by a newline, unless the option -nonewline is specified. New lines are translated to
end of line sequences automatically.

Note that Tcl performs internal buffering, so output from puts may not appear in an output file
immediately. We can force output with the flush command. Refer to the Tcl documentation for
information beyond this simple example:

% open "fred.txt" w
file3
% puts file3 "Hello World"
% close file3
% open "fred.txt" r
file3
% gets file3
Hello World
% close file3
%
format
format formatstring ?arg arg arg …?
The format command is the Tcl equivalent of the C library function sprintf and shares many
conversion specifiers. In fact, sprintf is used in the implementation of format.

The format command generates a formatted string. Refer to the Tcl manual page (man format) or the C
library documentation for printf/sprintf for details of conversion specifiers.

scan
scan string formatstring var ?var var …?

The scan command is the Tcl equivalent of the C library function sscanf. The variables specified as
arguments are set to values extracted from string, according to conversion specifiers given in
formatstring. As with format above, the implementation of scan is very similar to the ANSI C
sscanf, and readers are directed to the C library documentation for further details of conversion
specifiers.

file
file option name ?arg arg … ?

File manipulations are carried out in Tcl by the file command. It accepts a number of options for
changing and inspecting file characteristics. Options used by the file command follow.

atime, mtime
file atime name
file mtime name

These return a string describing the last access time/modified time of the file’s name.

dirname
file dirname name

This returns the directory part of the file’s name, that is, all the characters before the last / character.

% file dirname "/bin/fred.txt"


/bin

exists, executable
file executable name
file exists name

These return 1 if the file exists/is executable, 0 otherwise.

% file exists "fred.txt"


1
% file executable "fred.txt"
0
extension, rootname
file extension name
file rootname name

These return the file extension (part of name after last dot)/file root name (part up to last dot).

% file extension "fred.txt"


.txt
isdirectory, isfile
file isdirectory name
file isfile name

These return 1 if name refers to a directory/regular file, 0 otherwise.

owned
file owned name

This returns 1 if the file is owned by the current user, 0 otherwise.

readable, writeable
file readable name
file writeable name

These return 1 if the file is readable/writeable, 0 otherwise.

size
file size name

This returns the file size, in bytes.

Depending on the operating system that you’re using, you have other options available. These include
options to retrieve the result of a stat or lstat system call in a Tcl array. Refer to the system-specific Tcl
documentation for details.

A Tcl Program
Here’s a sample program to demonstrate some Tcl features. It generates a list of the frequencies of word
usage in a text file. It might form part of a concordance.

Try It Out—A Concordance Program

1. Type in the listing, concord.tcl, below. We begin by specifying the shell to run under and
initializing some variables.
#!/usr/bin/tclsh
set VerboseFlag false
set FileToRead "-"
set Usage "Usage: concord \[-v] \[-f<filename>]"

2. Remembering that argv is the program arguments array, parse the command line arguments.
Set FileToRead and give help.

foreach arg $argv {


switch -glob -- $arg {
-v {set VerboseFlag true}
-f* {set FileToRead [string range $arg 2 end]}
-h {puts stderr $Usage; exit 1}
default {error "bad argument: $arg\n$Usage"; exit 1}
}
}
3. Set the default input source to the standard input. If a file has been specified, open it “safely.”

set Input stdin

if {$FileToRead != "-"} {
if [catch {set Input [open $FileToRead r]} res ] {
puts stderr "$res"
exit 1
}
}

4. Initialize the word and line counters. Then read each line in the input, split the line according to
punctuation, and increment a concordance array.

set NumberOfLines 0
set NumberOfWords 0

while {[gets $Input line] >= 0} {


incr NumberOfLines
set words [split $line " \t.,\{\}\(\)\[\]\;\""]
foreach word $words {
if {[info exists concord("$word")]} {
incr concord("$word")
} else {
set concord("$word") 1
}
incr NumberOfWords
}
}

5. Output a summary, then all the words found, then each word accompanied by its count.

puts stdout [format "File contained %d/%d words/lines\n" \


$NumberOfWords $NumberOfLines]

puts stdout [array names concord]

foreach word [array names concord] {


puts "$word: $concord($word)"
}
Let’s try this program out on a text file, such as its own source code. When we run the program with the
commands,

$ chmod +x concord.tcl
$ ./concord.tcl -fconcord.tcl

we get output like this (edited for brevity):

File contained 404/48 words/lines

{"$Input"} {"if"} {"exists"} {"%d/%d"} {"$word:"} {"--"} {"error"} {"$words"}


{"$FileToRead"} {"info"} {"set"} {"$argv"} {"contained"} {"NumberOfWords"} {"range"}
{"argument:"} {"!="} {"bad"} {"VerboseFlag"} {"Input"} {"catch"} {"cannot"}
{"$concord"} {"words"} {"File"} {""} {"0"} {"else"} {"while"} {"-v"} {"word"}
{"split"} {"-h"} {"-f<filename>"} {"line"} {"concord"} {"format"} {"1"} {"switch"}
{"exit"} {"stdin"} {"stdout"} {"\"} {"stderr"} {"$NumberOfLines"} {"default"} {"$arg"}
{"\t"} {"$Usage"} {"#!/usr/bin/tclsh"} {"2"} {"open"} {"r"} {"names"} {"-f*"}
{"string"} {"$word"} {"$arg\n$Usage"}
...
"names": 2
"-f*": 1
"string": 1
"$word": 4
"$arg\n$Usage": 1
"NumberOfLines": 2
"arg": 1
"$line": 1
"incr": 3
"puts": 5
"Usage": 1
"array": 2
"Usage:": 1
"FileToRead": 2
"end": 1
"foreach": 3
"oops": 1
">=": 1
"gets": 1
"false": 1
"-": 2
"true": 1
"$NumberOfWords": 1
"-glob": 1
"words/lines\n": 1

How It Works
For demonstration purposes, the program prints out the list of words that it found before the frequency
listing. The order of the words in the array is undefined; it depends on the Tcl implementation of
associative arrays. If we had wanted the output sorted, we could have copied the array elements into a list
and used lsort, or we could have piped the output through the UNIX sort program.

The program works by splitting input lines into individual words, excluding punctuation, and using these
words as indices to an associative array. This array, concord, is used to collect the word frequencies. We
used a quoted string as an index to the array to prevent possible problems with special characters
occurring in the words.
Network Support
With Tcl 7.5 and later, Tcl has direct support for networking, where previously third-party add-ons were
required. The flexibility and concise nature of Tcl combined with a network programming paradigm gives
Tcl an exceptionally wide range of uses. In fact, there are even World Wide Web browsers written entirely
in Tcl. The key command is socket.

socket host port

By default, the socket command opens a SOCK_STREAM connection to the specified computer, host, and
service number, port. It returns a channel identifier that can be used in the normal Tcl input/output
commands.

Try It Out—Socketssocket
To demonstrate the use of sockets with Tcl, let’s revisit a small example from Chapter 14 to compare.
Here’s a complete program, socket.c, in Tcl to display the time of day from a networked computer.

To get this to work properly, replace the hostname tilde with an appropriate system
name on your internal network or, alternatively, use localhost.

#!/usr/bin/tclsh

set sockid [socket tilde 13]


gets $sockid date
puts "Tilde says the date is: $date"
How It Works
Tcl (from Version 7.5) has built-in support for Linux networking. This program uses the socket
command to create a communication path to the host tilde, port 13, which is the daytime service. It
reads a string from the socket connection and prints it.

Compare this three-line solution with the program from Chapter 15!

Using gets here reveals a potential pitfall. An unscrupulous system administrator


might configure the daytime service to return an extremely long string that could give
your machine problems. These kinds of antics have been known to occur on the
Internet and are known as Denial of Service attacks.

Creating a New Tcl


One of the design objectives of the Tcl language was that it should be embeddable, so that we can include
scripts written in Tcl in other command line programs, and even in C. Indeed, that is how Tcl got started,
and why it is called Tool Command Language. And, in this respect, it is very similar to Perl.

It’s quite easy to incorporate Tcl into your own command line programs by writing one simple function
that initializes the Tcl interpreter. You can link your own functions with the Tcl library to create your own
Tcl interpreter that is tailored to your specific needs.

The precise mechanism for this is beyond the scope of this chapter. In fact, many Tcl programmers never
have to create their own interpreters. They find that the basic Tcl system, together with Tcl extensions
provided by third parties, satisfies all their needs.

Tcl Extensions
Over the years, there have been a large number of extensions to Tcl. These most often take the form of
versions of Tcl with additional functionality to serve a particular purpose. We’ll cover just a few of them
here.

expect
The expect program is a Tcl interpreter extended with additional functions aimed particularly at
automatic control of interactive programs. We can use it for testing software, because we can write
expect scripts to send commands to a program, wait for responses, and then react to those responses. It
has built-in capabilities for timeouts and error recovery. It can run Tcl functions on receipt of a number of
different events.

expect was created by Don Libes of NIST (National Institute of Standards and
Technology) shortly after the release of Tcl on the Internet. It is the first major program
built around Tcl.

The extension gets its name from the two principal functions it adds. These are send, for sending
interactive input to a program under test, and expect, for arranging for actions to be taken on receipt of
different responses, or lack of them.
[incr Tcl]
This extension, the name of which is a pun on C++, adds object-oriented features to Tcl.

TclX
TclX is “extended Tcl.” It’s a commonly used extension in the Linux environment, as it contains many
additional functions that mirror the UNIX system calls. TclX programs enjoy greater low-level access to
the operating system at the expense of a certain amount of portability.

Graphics
Many Tcl extensions have been developed to support graphics and additional operations.

Tk
A major Tcl extension, also developed by John Ousterhout, is Tk (for Tool Kit). It bestows on Tcl the ability
to create and manipulate graphical user interface objects. Using Tk makes it easy for us to write
sophisticated graphical programs.

Initially developed for The X Window System, Tk (and of course Tcl) now comes in versions for Microsoft
Windows and Apple MacOS, so a Tcl/Tk program has the potential to run unchanged on several different
hardware platforms.

We’ll cover the subject of programming with Tk in Chapter B.

Summary
In this chapter, we’ve taken a look at the Tool Command Language, Tcl. We’ve seen that it’s an extensive
and extensible interpreted programming language with support for many high-level programming
language features, such as associative arrays and lists.

We’ve briefly covered the fact that Tcl has been extended in many ways to create new interpreters tailored
for specific application areas.

In the next chapter we will take a look at Tk, the graphical toolkit for Tcl that has resulted in many
applications being developed and distributed among the Linux community.

You might also like