By Brent Welch
The Tcl (Tool Command Language) has gained a lot of recognition as a new scripting language and as an embeddable interpreter that is easy to add to your applications. As a scripting language, Tcl (pronounced ``tickle'') fills the niche also occupied by various Unix shell languages such as the Bourne shell, the C shell, and Perl. By using Tcl or these other shell languages, you can glue together existing Unix applications into a tool that is customized for your particular needs. What helps to distinguish Tcl are extensions not found in other shells, for example, an easy-to-use X window system toolkit.
The core Tcl language is quite simple. There are a number of predefined commands that provide basic programming capabilities such as variables, control flow, procedures, and error handling. There are also a set of predefined commands that provide access to Unix facilities, such as accessing the file system and running other Unix programs. There is just a small amount of syntax you need to know.
Adding a Tcl interpreter to your application provides even more flexibility to your users and the system integrators who use your application. Users can write short Tcl scripts that customize your application to their liking. System integrators can exercise sophisticated control over your application, for example, by bundling it with a set of Tcl-aware applications that all work together in concert.
Perhaps the real power of Tcl comes from the large and growing number of extensions that are available for it. A Tcl extension is a set of Tcl commands that are implemented in C and packaged into a library. The programmer can create a supershell that combines several extensions together to create a platform for rapidly constructing applications by scripting them in Tcl.
The most important extension is Tk, a toolkit for the X window
system. Using a Tcl/Tk shell (called wish
),
programmers can create a graphical user interface at the same
time they glue together their customized tool. Tk provides a
much higher-level interface when compared to other toolkits such
as Motif or the Windowing Korn Shell. It takes just a few Tcl
commands to define user interface widgets and compose them into a
working interface.
The syntax for a Tcl command is simple. The first word is the name of a command. The command name and the remaining arguments are separated by spaces. The command is terminated by a newline or a semicolon. The arguments are passed to the command implementation without interpretation, except for the substitutions described below.
The basic structure is augmented with two mechanisms:
grouping, used to get arguments that contain whitespace; and
substitution, a macro-like facility used with variables and
nested command invocations. There are two ways to group things.
Grouping with curly braces, { }
, prevents
substitutions from occurring on the things inside them, while
grouping with double quotes, " "
, allows
substitutions. Finally, there are also some backslash sequences
that can be used to quote special characters and to obtain
characters by specifying their character code.
The first line in Listing 1A
defines a Tcl variable, name
, as having the value
Brent Welch
. The curly braces used to group the
value into one argument are discarded before the set
command is invoked. The second command outputs a string to the
standard output. In this case double quotes are used so that
$name
will be substituted with the value of variable
name
. The substitution and grouping occurs before
puts
is invoked. Thus the basic function of the Tcl
interpreter is to parse a command to do groupings, then
substitutions, and finally invoke the command with the resulting
arguments.
The interpreter also does command substitution to allow for
nested commands. For example, suppose we want to get the current
date. We can do this by running the Unix date
command, as shown in Listing 1B.
The Tcl interpreter treats the string between the matching
square brackets as another Tcl command. It postpones processing
of the current command, invokes the nested command, and then
replaces the square brackets and everything between them with the
value returned by the nested command. In this case, the Tcl
exec
command returns whatever the program has
written to its standard output, which is something like
Mon Aug 8 17:01:53 PDT 1994
.
There is an implicit grouping done too, so there is no need to
group the results of the date
command even though it
contains spaces. In other words, it is not necessary to
quote a nested command, as in
set d "[exec date]"
A rule of thumb to help you in more complex cases is to remember that grouping decisions are made before substitutions. Grouping of characters into arguments is determined by whitespace, curly braces, and double quotes. Substitutions are triggered by the dollar sign for variable substitutions and square brackets for command substitutions.
ping
For our first extended example we will write a user interface
to the Unix ping
command, which sends a network
packet to a host and waits for an answer. It reports the time
taken or a failure if the host does not respond. Our first
version of the script (see Listing
2A) will be a bit limited, but we will address those
limitations in the second example.
The first line of the script causes it to be interpreted by
wish
, the Tcl/Tk shell. Of course, on your system
wish
might be installed in an alternate location.
The -f
is required for historical reasons. The
wish
shell was originally designed just to test the
Tk toolkit. Due to limitations of the Unix exec()
system call, this line must be fewer than 32 characters in
length, including the #!
and the -f
, so
be careful if you install a private copy of wish
in
some deeply nested directory. You will get confusing ``Command
not found'' errors when running the script.
This script creates several Tk widgets, one each on lines 3,
6, 7, 10, 11, 12, and 15. The example uses buttons
,
which have an associated Tcl command; a label
, which
is a read-only text widget; an entry
, which is a
one-line text-entry widget; frames
, which are
container widgets; and a text
widget, which is a
general-purpose multiline text widget.
If you examine Tcl commands that create the widgets you will see a common form. The command name indicates what kind of widget is being created. The first argument is the name of the widget. The remaining arguments are pairs of attributes and values for the widgets. Tk widgets have a lot of attributes so that you can control most of their appearance and behavior, but in most cases you only need to specify a few attributes and can leave the rest of the attributes in their default settings.
Tk uses a naming convention for its widgets that reflects their position in the hierarchy of windows. The main window of an application is named simply ``.'', and other widgets have a path name of components separated by periods. This pattern is analogous to the way Unix files are named, where ``/'' is the name of the root directory, and files have path names with components separated by slashes.
In this example there are three widgets that are children of
the main window: .buttons
, .f
, and
.log
. The pack
commands on lines 4, 10,
and 16 arrange these widgets in a vertical stack inside the main
window by specifying the -side top
packing
parameters.
There are two buttons that are children of the
.buttons
frame: .buttons.quit
and
.buttons.ping
. These are arranged in a horizontal
stack by the pack
command on line 8 by specifying
-side right
(see Figure
1). A similar thing is done on lines 10 through 13 with the
.f
frame that is used to hold the label,
.f.l
, and the entry, .f.host
. Line 10
contains two Tcl commands just to remind you that the semicolon
character functions as a command terminator.
I have found horizontal and vertical stacking of widgets within frames to be the most trouble-free way of using the Tk packer. There are other features of the packer that I have to skip for this introduction.
The interface consists of the Quit and Ping buttons, the entry widget used to name the host, and the text widget used to log the output.
The way that the Quit button functions is fairly
straightforward. When the user clicks on it, the associated Tcl
command, exit
, is invoked, which causes the program,
wish
, to terminate. Note that there is nothing at
the end of the script to tell it to keep going, either. This
action is implicit in the behavior of wish
, which
reads a Tcl script to configure the interface and then goes into
an event loop processing window events.
The Ping button invokes the Ping command, as shown on lines 18 through 24. Let's digress and talk about Tcl syntax again. Lines 18 through 24 form a single Tcl command because of the way grouping with curly braces works. It groups characters, including newlines and semicolons, until a matching brace is found. Grouping with double quotes works the same way, except that there is no nesting.
The Tcl proc
command (line 18) takes three
arguments: the procedure name, an argument list, and a command
body. In this case the argument list is empty, so we use curly
braces to group no characters to form the empty list. The curly
braces around the procedure body are placed carefully. The
opening brace at the end of line 18 causes the interpreter to
keep scanning characters until it gets to the matching brace, in
spite of the embedded newlines. These characters together form
the third argument to proc
. At this time they are
uninterpreted because of the curly braces used to group them.
The commands within Ping are interpreted later when the Ping
command is invoked. At that time the newlines in the command
body serve as command terminators and break the body up into
several commands. Finally, note that hostname
is a
local variable inside Ping and that it is not defined elsewhere,
even after Ping has been invoked.
The Ping command does three things. First, it gets the
current value of the entry widget with the .f.host
get
command. The name of the widget is a Tcl command that
operates on the widget. The .log
command is used to
insert the results of Ping into the text widget. The Unix
ping
command is run under the protection of the Tcl
catch
command in case it raises an error. Tcl
exec
will raise an error if the command is not found
or if the command writes to standard error.
The first argument to catch
is a Tcl command, and
the second is the name of a variable in which to place the result
(or error message). The catch
command returns 1 if
an error was raised, or 0, but we are ignoring the return value
in this case. I recommend using braces to group the Tcl command
argument to catch
because it will call the full Tcl
evaluator on that argument, at which time any substitutions will
occur. If you use double quotes, you will get two rounds of
substitutions, which can cause unexpected results.
There are some details not covered here, like the exact meanings of all the attributes for the widgets and the details of using text and entry widgets. There are Unix manual pages for each of the Tcl and Tk commands that describe all the details.
The main problem with this example is that we'd like to run
ping
in its mode that continuously queries the host,
and add a Stop button. To do so, we will need to split the
example into two scripts that communicate using the Tk
send
command. One script will be the control panel,
the other will be a helper script that runs ping
and
sends the output to the control panel for logging. Listing 2B shows the helper
script.
Line 3 tells the window manager to unmap the window. The main
window isn't needed by the helper, but we need to use
wish
to use the send
command.
Lines 4 and 5 access the command-line arguments. The
lindex
command is used to index into a list. The
argv
variable is the list of command-line arguments
for the script. The first argument is the name of the host to
ping
, and the second argument is the name of the Tcl
interpreter with which to communicate.
Line 6 opens a pipeline for reading. That is, the Unix
command ping -s $hostname
is forked (with
$hostname
suitably replaced), and the output of that
program is available to the script by reading from an I/O stream.
Line 7 uses the gets
command to do the reading. The
gets
command puts the next line of input into a
variable, line
, and returns the number of characters
read, not counting the newline that is discarded.
Line 8 uses the send
command to invoke a Tcl
command, namely Insert $line
, in another
interpreter. In this case we use the list
command
to properly construct the command for us, taking into account any
special characters that might be in the line. The
$line
is substituted before list
is
invoked and well before the command gets sent. The
list
command formats its arguments in such a way
that they will survive reparsing by the remote interpreter. Thus
the send
command gets two arguments, the name of the
other interpreter, and a safely packaged Tcl command.
We can leave the control panel script alone, except for
changing the Ping procedure and adding two more procedures. The
Insert
procedure inserts into the log, and the
Stop
procedure stops the helper script. The changes
to Ping and the two new procedures are shown in Listing 2C.
On line 21 the helper script is run. It is passed two
arguments: the host name and the result of a command that returns
the name of the Tcl interpreter. This name identifies the
interpreter to others so that they can send it Tcl commands. The
helper script is run in the background, and exec
returns its process ID.
You can dynamically change any Tk widget attribute that you
could specify when you created the widget. On lines 22 and 33
the behavior and appearance of .button.ping
is
changed. The button is alternately a Ping button and a Stop
button (see Figure 2).
The Ping and Stop procedures need to communicate the process
ID of the helper script, done with the global variable
pid
. By default, variables inside procedures are
local. The global
command makes them visible in the
global scope.
The Insert
procedure is there for convenience.
We could have arranged for pinghelper
to send all
the commands inside Insert
, but that would be
awkward. As the script evolves, the Insert
procedure will probably prove useful in other situations,
too.
Tcl and Tk, and a host of extensions, are available freely
over the Internet. The current archive site for Tcl is
ftp://www.sunlabs.com/pub/tcl
. Tcl and Tk are
distributed under a University of California at Berkeley
copyright that allows for use in commercial products with no
license fee. There is a newsgroup, comp.lang.tcl
,
for discussion of Tcl and its applications. Tcl and the Tk
toolkit were created by Professor John Ousterhout while he was at
U.C. Berkeley. I recommend his book, Tcl and the Tk
Toolkit, published by Addison-Wesley.