
Tcl with its X Window toolkit adds command scripts and
user interfaces to both standalone tools and applications
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.
Tcl Basics
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.
A Tcl/Tk Front End to 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. 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.
GUI Made Simple
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.
Improving our Front End
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.
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.
Where to Get Tcl/Tk
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.
|