Vous êtes sur la page 1sur 37

Tcl for Web Nerds

Tcl for Web Nerds

by Hal Abelson, Philip Greenspun, and Lydia Sandon

Preface
1.Introduction
2.Strings
3.Lists
4.Pattern matching
5.Arrays
6.Numbers and Arithmetic
7.Control structure
8.Procedures
9.File commands
10.eval: building Tcl commands with Tcl and feeding them back to Tcl
11.exec: building Unix commands with Tcl and feeding them to Unix
12.Further Study

Preface

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

The last thing that the world needs is another tutorial book on the Tcl programming language. So here's ours...
When we sat down to plan a class at MIT on Web service design and implementation, our first thought was to give the
students our favorite commercial Tcl book: Brent Welch's Practical Programming in Tcl and Tk. At 630 pages, nobody can
accuse Prentice-Hall of shortchanging the reader on information. Sadly, however, for the Web nerd most of this information is
useless. Two-thirds of the book is devoted to Tk, a toolkit for constructing graphical user interfaces. On the Internet, the user
interface is a Web browser. If you're programming a Web service, all you care about is using Tcl to produce some HTML that gets
sent to the client.
Another problem with using Welch's book for our class is that he is unable to assume that his readers are primarily interested
in building Web applications. So his examples have to be extremely generic.
A final and crushing problem with the book is that it isn't available on the Web. Even if our students can afford to pony up
$40 or $50 for a 630-page book, they probably can't afford the physical therapy and orthopedic surgery they'd require after
lugging it around from class to class.
What about Tcl for Web Nerds then? We hope that a professional programmer or MIT student can breeze through it in one
evening. By the end of the evening, that person should have learned Tcl, learned a little something about the Web, and not have
been bored.
It is available on the Web at a permanent URL. If you don't like it, the authors will happily refund your purchase price. :-)

Introduction

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

If you're an MIT student reading this tutorial, you have a special advantage in learning Tcl, because you've read Structure and
Interpretation of Computer Programs (Abelson and Sussman 1996; MIT Press). We're not going to try to redeliver the
knowledge in SICP packaged up in Tcl syntax. However, we are going to try to show how some of the big ideas from SICP can be
accomplished even in the comparatively impoverished Tcl language.
One of the strengths of Lisp, the programming language used to illustrate the principles in SICP, is that it has very simple
syntax:

(procedure-name arg1 arg2 arg3)

e.g.,

(set! ten-factorial (factorial 10))


(set! checking-account-balance (+ 25 34 86))
(print "If you're so smart, why aren't you rich like Bill Gates?")

will set ten-factorial to the result of evaluating the factorial procedure with an argument of 10, then compute a graduate
student's checking account balance, then print out some career advice to a computer science graduate student.
These examples are written in the Scheme dialect of Lisp, which is distributed from
http://www-swiss.ai.mit.edu/scheme-home.html.
How would you accomplish the same goals in Tcl? Tcl syntax is

procedure_name arg1 arg2 arg3


procedure_name arg1 [subprocedure subarg1 subarg2]

This is like Lisp, except that procedures invoked at top level need not be surrounded by delimeters. Procedures invoked from
within other procedure calls must be surrounded by [] instead of ():

set ten_factorial [factorial 10]


set checking_account_balance [expr 25 + 34 + 86]
puts "If you're so smart, why aren't you rich like Bill Gates?"

Tcl variables can't include dashes; you have to use underscore instead. Note that doing arithmetic with Tcl requires escaping into a
world of infix syntax with the expr procedure.

Evaluation and quoting

Each line of Tcl is interpreted as a separate command:

procedure_name arg1 arg2 arg3

Arguments are evaluated in sequence. The resulting values are then passed to procedure_name, which is assumed to be a system-
or user-defined procedure. Tcl assumes that you're mostly dealing with strings and therefore stands some of the conventions of
standard programming languages on their heads. For example, you might think that set foo bar would result in Tcl complaining
about an undefined variable (bar). But actually what happens is that Tcl sets the variable foo to the character string "bar":

> tclsh
% set foo bar
bar
% set foo
bar
%

The first command line illustrates that the set command returns the new value that was set (in this case, the character string
"bar"), which is the result printed by the interpreter. The second command line uses the set command again, but this time with only
one argument. This actually gets the value of the variable foo. Notice that you don't have to declare variables before using them.
By analogy with Scheme, you'd think that we could just type the command line $foo and have Tcl return and print the value.
This won't work in Tcl, however, which assumes that every command line invokes a procedure. This is why we need to explicity
use set or puts.
Does this mean that you never need to use string quotes when you've got string literals in your program? No. In Tcl, the
double quote is a grouping mechanism. If your string literal contains any spaces, which would otherwise be interpreted as argument
separators, you need to group the tokens with double quotes:

% set the_truth Lisp is the world's best computer language


wrong # args: should be "set varName ?newValue?"
% set the_truth "Lisp is the world's best computer language"
Lisp is the world's best computer language

In the first command above, the Tcl interpreter saw that we were attempting to call set with seven arguments. In the second
command, we grouped all the words in our string literal with the double quotes and therefore the Tcl interpreter saw only two
arguments to set. Note a stylistic point here: multi-word variable names are all-lowercase with underscores separating the words.
This makes our Tcl code very compatible with relational database management systems where underscore is a legal character in a
column name.
In this example, we invoked the Unix command "tclsh" to start the Tcl interpreter from the Unix shell. Later on we'll see how
to use Tcl in other ways:
writing a program file and evaluating it
embedding Tcl commands in Web pages to create dynamic pages
extending the behavior of the Web server with Tcl programs
For now, let's stick with typing interactively at the shell. You can keep evaluating Tcl commands at the % prompt until you exit the
Tcl shell by evaluating exit.
To indicate a literal string that contains a space, you can wrap the string in double quotes. Quoting like this does not prevent the
interpreter from evaluating procedure calls and variables inside the strings:

% set checking_account_balance [expr 25 + 34 + 86]


145
% puts "your bank balance is $checking_account_balance dollars"
your bank balance is 145 dollars
% puts "ten times your balance is [expr 10 * $checking_account_balance] dollars"
ten times your balance is 1450 dollars

The interpreter looks for dollar signs and square brackets within quoted strings. This is known as variable interpolation What if
you need to include a dollar sign or a square bracket? One approach is to escape with backslash:

% puts "your bank balance is \$$checking_account_balance"


your bank balance is $145
% puts "your bank balance is \$$checking_account_balance \[pretty sad\]"
your bank balance is $145 [pretty sad]

If we don't need Tcl to evaluate variables and procedure calls inside a string, we can use braces for grouping rather than
double quotes:

% puts {your bank balance is $checking_account_balance dollars}


your bank balance is $checking_account_balance dollars
% puts {ten times your balance is [expr 10 * $checking_account_balance] dollars}
ten times your balance is [expr 10 * $checking_account_balance] dollars

Throughout the rest of this book you'll see hundreds of examples of braces being used as a grouping character for Tcl code. For
example, when defining a procedure or using control structure commands, conditional code is grouped using braces.

Keep it all on one line!

The good news is that Tcl does not suffer from cancer of the semicolon. The bad news is that any Tcl procedure call or command
must be on one line from the interpreter's point of view. Suppose that you want to split up

% set a_very_long_variable_name "a very long value of some sort..."

If you want to have newlines within the double quotes, that's just fine:

% set a_very_long_variable_name "a very long value of some sort...


with a few embedded newlines
makes for rather bad poetry"
a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry
%

It also works to do it with braces

% set a_very_long_variable_name {a very long value of some sort...


with a few embedded newlines
makes for rather bad poetry}
a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry
%
but if you were to try

set a_very_long_variable_name
"a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry"

Tcl would interpret this as two separate commands, the first a call to set to find the existing value of
a_very_long_variable_name and the second a call to the procedure named "a very long value...":

can't read "a_very_long_variable_name": no such variable


invalid command name "a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry"

If you want to continue a Tcl command on a second line, it is possible to use the backslash to escape the newline that would
otherwise terminate the command:

% set a_very_long_variable_name \
"a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry"
a very long value of some sort...
with a few embedded newlines
makes for rather bad poetry
%

Note that this looks good as code but the end-result is probably not what you'd want. The second and third lines of our poem
contain seven spaces at the beginning of each line. You probably want to do something like this:

% set a_very_long_variable_name "a very long value of some sort...


with a few embedded newlines
makes for rather bad poetry"

For completeness we note that semicolon will allow you to put multiple Tcl commands on one line:

% set x 2; set y 3; set z [expr $x+$y]


5

We never use this facility of the language.

Case Sensitivity, Poisonous Unix Heritage, and Naming Conventions

The great case-sensitivity winter descended upon humankind in 1970 with the Unix operating system.

% set MyAge 36
36
% set YearsToExpectedDeath [expr 80-$Myage]
can't read "Myage": no such variable
%

Variables and procedure names in Tcl are case-sensitive. We consider it very bad programming style to depend on this, though.
For example, you shouldn't simultaneously use the variables Username and username and rely on the computer to keep them
separate; the computer will succeed but humans maintaining the program in the future will fail. So use lowercase all the time with
underscores to separate words!

Procedures

One of the keys to making a large software system reliable and maintainable is procedural abstraction. The idea is to take a
complex operation and encapsulate it into a function that other programmers can call without worrying about how it works.
Here's the factorial procedure in Tcl:

% #this is good old recursive factorial


% proc factorial {number} {
if { $number == 0 } {
return 1
} else {
return [expr $number * [factorial [expr $number - 1]]]
}
}
% factorial 10
3628800

At first glance, you might think that you've had to learn some new syntax here. In fact, the Tcl procedure-creation procedure is
called like any other. The three arguments to proc are procedure_name arglist body. The creation command is able to extend
over several lines not because the interpreter recognizes something special about proc but because we've used braces to group
blocks of code. Similarly the if statement within the procedure uses braces to group its arguments so that they are all on one line
as far as the interpreter is concerned.
As the example illustrates, we can use the standard base-case-plus-recursion programming style in Tcl. Our factorial
procedure checks to see if the number is 0 (the base case). If so, it returns 1. Otherwise, it computes factorial of the number minus
1 and returns the result multiplied by the number. The # character signals a comment.
We'll have more to say about procedures later on. For now, examine the following example:

% set checking_account_balance [expr 25 + 34 + 86]


145
% puts "\nYour checking balance is \$$checking_account_balance.
If you're so smart, why aren't you rich like Bill Gates?
He probably has \$[factorial $checking_account_balance] by now."

Your checking balance is $145.


If you're so smart, why aren't you rich like Bill Gates?
He probably has $0 by now.

There are a few things to observe here:


The "\n" at the beginning of the quoted string argument to puts resulted in an extra newline in front of the output.
The newline after balance. did not terminate the puts command. The string quotes group all three lines
together into a single argument.
The [factorial... ] procedure call was evaluated but resulted in an output of 0. This isn't a bug in the
evaluation of quoted strings, but rather a limitation of the Tcl language itself:

% factorial 145
0

One of the reasons that commercial programmers work so hard and accomplish so little is that they use languages that can't even
do arithmetic! Tcl's feeble brain is overwhelmed by the idea of an integer larger than one machine word (32 bits in this case). You'll
have to go back to Scheme if you want to find Bill G's predicted worth:

(define (factorial number)


(if (= number 0)
1
(* number (factorial (- number 1)))))
;Value: factorial

(factorial 145)
;Value:
80479260574719919448490292577980627710999743900750061634474528104711541237364652141085048187983964922743929823029
89150198131082216516636595724416094085569177391493159059928114118666357860755246018358156427933025042432000000000
00000000000000000000000000

Some powerful things about Tcl


A language that isn't powerful by itself can become powerful if a procedure can invoke the interpreter, i.e., if a program can write a
program and then ask to have it evaluated. In Lisp, you do this by calling eval. In Tcl, you do this ... by calling eval! We'll see
examples of this later.
Tcl also supports reflection: You can write a Tcl program that pokes around the run-time environment. One basic
mechanism is

info exists foobar

which tests whether the variable foobar is defined. The info command can also tell a running program what local variables are
defined (info local), what procedures are defined (info procs), what arguments a procedure takes (info args
procedure_name), and what the source code for a procedure is (info body procedure_name).
One useful aspect of reflection extends the behavior of set desribed above: a Tcl procedure can examine the calling stack
and set variables in its caller's environment. For example, in the ArsDigita Community System toolkit, there's a procedure called
set_variables_after_query that looks at the last SQL query executed and sets local variables to the values retrieved from the
database:

set selection [ns_db 1row $db "select first_names,


last_name
from users
where user_id = 37"]

set_variables_after_query

ns_write "The above comment was made by $first_names $last_name.\n"

Note that this example uses procedures defined by AOLserver, e.g., ns_db to talk to the database and ns_write to send bytes
back to the Web client. The prefixing of procedure names by a package name is a good convention that you should follow in your
own programming. In this case, the "ns_" prefix alerts fellow programmers to the fact that you are calling API calls for the
"NaviServer". NaviServer? That's what AOLserver was called before being purchased by America Online. The fact that the API
prefixes were not changed illustrates another good programming practice: don't change things gratuitiously.

Tcl for Web Use

In most cases, rather than typing Tcl expressions at the interpreter you'll be using Tcl to generate dynamic Web pages. AOLserver
provides two mechanisms for doing this:
1..tcl pages are Tcl programs that are executed by the webserver (in this case AOLServer). They typically generate
character strings that are sent to the client browser with ns_write.
2..adp pages are like ordinary HTML pages, but they contain escapes to Tcl commands that are evaluated by the server.
Which option you choose depends largely on whether static HTML or Tcl constitutes the majority of the page you are
creating. If you're printing fixed character strings with Tcl, you'll need to "quote" any embedded quotes by preceding them with
backslash. E.g., to print to a Web page the string <a href="goodies.html"> you would use the Tcl command

ns_write "<a href=\"goodies.html\">"

So if the majority of your Tcl expressions are simply printing strings, you'll find it more convenient to avoid the hassles of quoting
by
using .adp pages rather than complete .tcl programs.
Why the use of ns_write instead of puts? The puts command writes to a program's standard output stream. If you were
writing a Tcl CGI script, standard output would be linked through the Web server to the Web client and you could indeed use
puts. This idea doesn't make sense with AOLserver, where all the Tcl scripts are running inside the Web server process. If the
Web server is serving 100 clients simultaneously, to which client should bytes written with puts be sent?
Anyway, if you want to have some fun one night, try redefining puts to forward its argument to ns_write and see what
happens.

Lisp Without a Brain

If in reading this introduction, you've come to realize that "Hey, Tcl is just like Lisp, but without a brain, and with syntax on
steroids", you might wonder why Lisp isn't a more popular scripting language than Tcl. Lisp hasn't been a complete failure, by the
way; it is used as an extension language by users of some popular programs, notably AutoCAD. But Tcl has been much more
successful. It has been compiled into hundreds of larger programs, including AOLserver, which is why we wrote this book.
As a software developer, you're unlikely to get rich. So you might as well try to get through your life in such a way that you
make a difference to the world. Tcl illustrates one way:
make something that is simple enough for almost everyone to understand
give away your source code
explain how to weave your source code in with other systems
In the case of AOLserver, for example, Jim Davidson and Doug McKee had only a few months to build the whole server from
scratch. They wanted users to be able to write small programs that ran inside the server process. They therefore needed a safe
interpreted language so that a programming error by one user didn't crash the server and bring down all the Web services for an
organization.
Tcl was available. Tcl was easy to download and designed to fit inside larger application programs. But the Tcl interpreter as
distributed had one terrible bug: it wasn't thread safe, i.e., you couldn't have two copies of the Tcl interpreter running inside the
same program at the same time. Doug and Jim had to read through the Tcl source code and modify it to be thread safe. So it was
critically important for them that Tcl was open-source and simple enough so as to not require months or years of study to
understand the whole system.
Compare this to Lisp. Some of the best and brightest computer scientists raised money to build commercial Lisp
implementations that they then went out and hawked in an indifferent and confused marketplace. They succeeded only in breaking
their hearts and their investors' wallets. A handful of academics produced free open-source implementations, notably CMU
Common Lisp (see http://www.cons.org/cmucl/) and various versions of Scheme (see
http://www-swiss.ai.mit.edu/scheme-home.html; Scheme 48 is the closest to Tcl in spirit). But these multi-megabyte monsters
weren't designed to fit neatly into someone else's program. Nor was there any document explaining how to do it.
Lisp developers have the satisfaction of knowing that they got it right 30 years before anyone else. But that's about all they
have to show for 40 years of hard work and hundreds of millions of dollars in government and private funding. These days, most
former Lisp programmers are stuck using Unix and Microsoft programming environments and, not only do they have to put up with
these inferior environments, but they're saddled with the mournful knowledge that these environments are inferior.

Basic String Operations

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

If your program receives data from a Web client, it comes in as a string. If your program sends an HTML page back to a Web
client, it goes out as a string. This puts the string data type at the heart of Web page development:

set whole_page "some stuff for the top of the page\n\n"


append whole_page "some stuff for the middle of the page\n\n"
append whole_page "some stuff for the bottom of the page\n\n"
# done composing the page, let's write it back to the user
ns_return 200 text/html $whole_page

If you're processing data from the user, typically entered into an HTML form, you'll be using a rich variety of built-in string-handling
procedures. Suppose that a user is registering at your site with the form variables first_names, last_name, email,
password. Here's how we might build up a list of exceptions (using the Tcl lappend command, described in the chapter on lists):

# compare the first_names value to the empty string


if { [string compare $first_names ""] == 0 } {
lappend exception_list "You forgot to type your first name"
}

# see if their email address has the form


# something at-sign something
if { ![regexp {.+@.+} $email] } {
lappend exception_list "Your email address doesn't look valid."
}

if { [string length $password] > 20 } {


lappend exception_list "The password you selected is too long."
}

If there aren't any exceptions, we have to get these data ready for insertion into the database:

# remove whitespace from ends of input (if any)


set last_name_trimmed [string trim $last_name]
# escape any single quotes with an extra one (since the SQL
# string literal quoting system uses single quotes)
regsub -all ' $last_name_trimmed '' last_name_final

set sql_insert "insert into users (..., last_name, ...)


values
(..., '$last_name_final', ...)"

Looking for stuff in a string

The simplest way to look for a substring within a string is with the string first command. Some users of photo.net complained
that they didn't like seeing classified ads that were simply pointers to the eBay auction site. Here's a simplified snippet from
http://software.arsdigita.com/www/gc/place-ad-3.tcl:

if { [string first "ebay" [string tolower $full_ad]] != -1 } {


# return an exception
...
}

an alternative formulation would be

if { [regexp -nocase {ebay} $full_ad] } {


# return an exception
...
}

Both implementations will catch any capitalization variant of "eBAY". Both implementations will miss "e-bay" but it doesn't matter
because if the poster of the ad includes a link with a URL, the hyperlink will contain "ebay". What about false positives? If you visit
www.m-w.com and search for "*ebay*" you'll find that both implementations might bite someone selling rhododendrons or a
water-powered mill. That's why the toolkit code checks a "DisalloweBay" parameter, set by the publisher, before declaring this an
exception.
If you're just trying to find a substring, you can use either string first or regexp. If you're trying to do something more
subtle, you'll need regexp (described more fully in the chapter "Pattern Matching"):

if { ![regexp {[a-z]} $full_ad] } {


# no lowercase letters in the ad!
append exception_text "
Your ad appears to be all uppercase.
ON THE INTERNET THIS IS CONSIDERED SHOUTING. IT IS ALSO MUCH
HARDER TO READ THAN MIXED CASE TEXT. So we don't allow it,
out of decorum and consideration for people who may
be visually impaired."
incr exception_count
}

Using only part of a string

In the ArsDigita Community System, we have a page that shows a user's complete history with a Web service, e.g.,
http://photo.net/shared/community-member.tcl?user_id=23069 shows all of the postings by Philip Greenspun. If a comment on a
static page is short, we want to show the entire message. If not, we want to show just the first 1000 characters.
In http://software.arsdigita.com/www/shared/community-member.tcl, we find the following use of the string range
command:

if { [string length $message] > 1000 } {


set complete_message "[string range $message 0 1000]... "
} else {
set complete_message $message
}

Fortran-style formatting and reading of numbers


The Tcl commands format and scan resemble C's printf and scanf commands. That's pretty much all that any Tcl manual will
tell you about these commands, which means that you're kind of S.O.L. if you don't know C. The basic idea of these commands
comes from Fortran, a computer language developed by John Backus at IBM in 1954. The FORMAT command in Fortran would
let you control the printed display of a number, including such aspects as spaces of padding to the left and digits of precision after
the decimal point.
With Tcl format, the first argument is a pattern for how you'd like the final output to look. Inside the pattern are
placeholders for values. The second through Nth arguments to format are the values themselves:

format pattern value1 value2 value3 .. valueN

We can never figure out how to use format without either copying an earlier fragment of pattern or referring to the man page
(http://www.scriptics.com/man/tcl7.5/TclCmd/format.n.html). However, here are some examples for you to copy:

% # format prices with two digits after the point


% format "Price: %0.2f" 17
Price: 17.00
% # pad some stuff out to fill 20 spaces
% format "%20s" "a long thing"
a long thing
% format "%20s" "23"
23
% # notice that the 20 spaces is a MINIMUM; use string range
% # if you might need to truncate
% format "%20s" "something way longer than 20 spaces"
something way longer than 20 spaces
% # turn a number into an ASCII character
% format "%c" 65
A

The Tcl command scan performs the reverse operation, i.e., parses an input string according to a pattern and stuffs values as it
finds them into variables:

% # turn an ASCII character into a number


% scan "A" "%c" the_ascii_value
1
% set the_ascii_value
65
%

Notice that the number returned by scan is a count of how many conversions it was able to perform successfully. If you really want
to use scan, you'll need to visit the man page: http://www.scriptics.com/man/tcl7.5/TclCmd/scan.n.html. For an idea of how useful
this is for Web development, consider that the entire 250,000-line ArsDigita Community System does not contain a single use of
the scan command.

Reference: String operations

Commands that don't start with string

append variable_name value1 value2 value3 ... valueN


sets the variable defined by variable_name to the concatenation of the old value and all the remaining arguments
(http://www.scriptics.com/man/tcl7.5/TclCmd/append.n.html)
regexp ?switches? expression string ?matchVar? ?subMatchVar subMatchVar ...?
Returns 1 if expression matches string; 0 otherwise. If successful, regexp sets the match variables to the
parts of string that matches the corresponding parts of expression.

% set fraction "5/6"


5/6
% regexp {(.*)/(.*)} $fraction match num denom
1
% set match
5/6
% set num
5
% set denom
6

(more: the pattern matching chapter and http://www.scriptics.com/man/tcl7.5/TclCmd/regexp.n.html)


regsub ?switches? expression string substitution_spec result_variable_name
Returns a count of the number of matching items that were found and replaced. Primarily called for its effect in
setting result_variable_name.
Here's an example where we ask a user to type in keywords, separated by commands. We then expect
to feed this list to a full-text search indexer that will throw an error if handed two commas in a row. We use
regsub to clean up what the user typed:

# here we destructively modify the variable $query_string'


# replacing every occurrence of one or more commas with a single
# command
% set query_string "samoyed,, sledding, harness"
samoyed,, sledding, harness
% regsub -all {,+} $query_string "," query_string
2
% set query_string
samoyed, sledding, harness

(more: the pattern matching chapter and http://www.scriptics.com/man/tcl7.5/TclCmd/regexp.n.html)


regexp and regsub were dramatically improved with the Tcl 8.1 release. For a Web developer the
most important feature is the inclusion of non-greedy regular expressions. This makes it easy to match the
contents of HTML tags. See http://www.scriptics.com/services/support/howto/regexp81.html for a full discussion
of the differences.

Commands that start with string

(all of which are documented at http://www.scriptics.com/man/tcl7.5/TclCmd/string.n.html)


string compare string1 string2
returns 0 if the two strings are equal, -1 if string1 sorts lexicographically before string2, 1 if string2 sorts
lexicographically before string1:

string compare apple applesauce ==> -1


string compare apple Apple ==> 1

string first string1 string2


returns -1 if string1 is not within string2, else returns the index of the first occurrence. Indices start from
zero, e.g.,

string first tcl catclaw ==> 2

string last string1 string2


-1 if string1 is not within string2, else index of last occurrence.

string last abra abracadabra ==> 7

string match pattern string


1 if string matches pattern, 0 if not. See the chapter on pattern matching for an explanation of patterns.

string range string i j


range of characters in string from index i to j, inclusive.

string range catclaw 2 4 ==> tcl

string tolower string


string in lower case.
string compare weBmaster Webmaster => 1

string compare [string tolower weBmaster] \


[string tolower Webmaster] => 0

string toupper string


string in upper case.

set password "ferrari"


string compare "FERRARI" [string toupper $password] ==> 0

string trim string ?chars?


trims chars from right and left side of string; defaults to whitespace.

set password [string trim $form_password] ; # see above example

string trimleft string ?chars?


trims chars from left of string; defaults to whitespace.

set password [string trimleft $form_password]

string trimright string ?chars?


trims chars from right of string; defaults to whitespace.

set password [string trimright $form_password]

string wordend string index


index of the first character after the last character of the word containing index.

string wordend "tcl is the greatest" 0 ==>3

string wordstart string index


index of the first char of the word containing index.

string wordstart "tcl is the greatest" 5 ==> 4

List Operations

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

A Tcl list holds a sequence of elements, each of which can be a number, a string, or another list. Let's look at the commands for
constructing a list:

% # create an empty list using the list command


% set user_preferences [list]
% # verify that we've created a 0-item list
% llength $user_preferences
0
% lappend user_preferences "hiking"
hiking
% lappend user_preferences "biking"
hiking biking
% lappend user_preferences "whale watching"
hiking biking {whale watching}
% llength $user_preferences
3

At this point, the variable user_preferences is a three-element list. We can pull individual items out with lindex:
% lindex $user_preferences 0
hiking
% lindex $user_preferences 1
biking
% lindex $user_preferences 2
whale watching
% lindex $user_preferences 3
% lindex $user_preferences 5

Indexing is 0-based and lindex will return the empty string rather than an error if you supply an out-of-range index.
When producing a page for a user, we'd be more likely to be interested in searching the list. The command lsearch returns
the index of the list element matching a query argument or -1 if unsuccessful:

if { [lsearch -exact $user_preferences "hiking"] != -1 } {


# look for new articles related to hiking
}

Suppose that User A marries User B. You want to combine their preferences into a household_preferences variable using the
concat command:

% # use the multiple-argument form of list to create an N-element


% # list with one procedure call
% set spouse_preferences [list "programming" "computer games" "slashdot"]
programming {computer games} slashdot
% set household_preferences [concat $user_preferences $spouse_preferences]
hiking biking {whale watching} programming {computer games} slashdot
% llength $household_preferences
6

The Tcl shell's output is giving you an ugly insight into the internal representation of lists as strings, with elements being separated
by
spaces and grouped with braces. There is no reason to rely on how Tcl represents lists or even think about it. Practice data
abstraction by using Tcl lists as your underlying storage mechanism but define constructor and accessor procedures that the rest of
your source code invokes. You won't find a huge amount of this being done in Web development because the really important data
structures tend to be RDBMS tables, but here's an example of how it might work, taken from
http://photo.net/philg/careers/four-random-people.tcl. We're building a list of lists. Each sublist contains all the information on a
single
historical figure. Method A is quick and dirty:

set einstein [list "A. Einstein" "Patent Office Clerk" "Formulated Theory of Relativity."]

set mill [list "John Stuart Mill" "English Youth" "Was able to read Greek and Latin at age 3."]

# let's build the big list of lists


set average_folks [list $einstein $mill ...]

# let's pull out Einstein's title


set einsteins_title [lindex $einstein 1]

Method B uses data abstraction:

proc define_person {name title accomplishment} {


return [list $name $title $accomplishment]
}

proc person_name {person} {


return [lindex $person 0]
}

proc person_title {person} {


return [lindex $person 1]
}

proc person_accomplishment {person} {


return [lindex $person 2]
}

% set einstein [define_person "A. Einstein" "Patent Office Clerk" "Formulated Theory of Relativity."]
{A. Einstein} {Patent Office Clerk} {Formulated Theory of Relativity.}
% set einsteins_title [person_title $einstein]
Patent Office Clerk

Data abstraction will make building and maintaining a large system much easier. As noted above, however, the stateless nature of
HTTP means that any information you want kept around from page to page must be kept by the RDBMS. SQL already forces you
to refer to data by table name and column name rather than positionally.

Split and Join

Suppose we have a file called addressees.txt with information about people, one person to a line. Suppose each of these lines
contains, among other information, an email address which we assume we can recognize by the presence of an at-sign (@). The
following program extracts all the email addresses and joins them together, separated by commas and spaces, to form a string
called spam_address that we can use as the Bcc: field of an email message, to spam them all:

# open the file for reading


set addressees_stream [open "~/addressees.txt" r]

# read entire file into a variable


set contents_of_file [read $addressees_stream]

close $addressees_stream

# split the contents on newlines


set list_of_lines [split $contents_of_file "\n"]

# loop through the lines


foreach line $list_of_lines {
if { [regexp {([^ ]*@[^ ]*)} $line one_address] } {
lappend all_addresses $one_address
}
}

# use the join command to mush the list together


set bcc_line_for_mailer [join $all_addresses ", "]

Some things to observe here:


We've used the foreach operator (see the chapter on control structure) to iterate over the list formed by
splitting the file at newline characters.
We use pattern matching to extract the email address from each line. The pattern here specifies "stuff that
doesn't contain a space, followed by at-sign, followed by stuff that doesn't contain a space." (See the
explanation of regexp in the chapter on pattern matching.)
The iteration keeps lappending to all_addresses, but this variable is never initialized. lappend treats an
unbound list variable the same as an empty list.

Reference: List operations

list arg1 arg2 ...


Construct and return a list the arguments. Akin to (list arg1 arg2...) in Scheme.

set foo [list 1 2 [list 3 4 5]] ==> 1 2 {3 4 5}


http://www.scriptics.com/man/tcl7.5/TclCmd/list.n.html

lindex list i
Returns the ith element from list; starts at index 0.

lindex $foo 0 ==> 1

http://www.scriptics.com/man/tcl7.5/TclCmd/lindex.n.html

llength list
Returns the number of elements in list.

llength $foo ==> 3

http://www.scriptics.com/man/tcl7.5/TclCmd/llength.n.html

lrange list i j
Returns the ith through jth elements from list.

lrange $foo 1 2 ==> 2 {3 4 5}

http://www.scriptics.com/man/tcl7.5/TclCmd/lrange.n.html

lappend listVar arg arg...


Append elements to the value of listVar and reset listVar to the new list. Please note that listVar is the name
of a variable and not the value, i.e., you should not put a $ in front of it (the same way that set works.

lappend foo [list 6 7] ==> 1 2 {3 4 5} {6 7}


set foo ==> 1 2 {3 4 5} {6 7}

http://www.scriptics.com/man/tcl7.5/TclCmd/lappend.n.html

linsert list index arg arg...


Insert elements into list before the element at position index. Returns a new list.

linsert $foo 0 0 ==> 0 1 2 {3 4 5} {6 7}

http://www.scriptics.com/man/tcl7.5/TclCmd/linsert.n.html

lreplace list i j arg arg...


Replace elements i through j of list with the args. Returns a new list and leaves the original list unmodified.

lreplace $foo 3 4 3 4 5 6 7 ==> 0 1 2 3 4 5 6 7


set foo ==> 1 2 {3 4 5} {6 7}

http://www.scriptics.com/man/tcl7.5/TclCmd/lreplace.n.html

lsearch mode list value


Return the index of the element in list that matches the value according to the mode, which is -exact, -glob,
or -regexp. -glob is the default. Return -1 if not found.

set community_colleges [list "caltech" "cmu" "rpi"]


lsearch -exact $community_colleges "caltech" ==> 0
lsearch -exact $community_colleges "harvard" ==> -1

http://www.scriptics.com/man/tcl7.5/TclCmd/lsearch.n.html

lsort switches list


Sort elements of the list according to the switches: -ascii, -integer, -real, -increasing, -decreasing,
-command command. Returns a new list.
set my_friends [list "herschel" "schlomo" "mendel"]
set my_sorted_friends [lsort -decreasing $my_friends] ==> schlomo mendel herschel

http://www.scriptics.com/man/tcl7.5/TclCmd/lsort.n.html

concat arg arg...


Join multiple lists together into one list.

set my_wifes_friends [list "biff" "christine" "clarissa"]


concat $my_wifes_friends $my_friends ==> biff christine clarissa herschel schlomo mendel

http://www.scriptics.com/man/tcl7.5/TclCmd/concat.n.html

join list joinString


Merge the elements of list to produce a string, where the original list elements are separated by joinString.
Returns the resulting string.

set foo_string [join $foo ":"] ==> 0:1:2:3:4:5:6:7

http://www.scriptics.com/man/tcl7.5/TclCmd/join.n.html

split string splitChars


Split a string to produce a list, using (and discarding) the characters in splitChars as the places where to
split. Returns the resulting list.

set my_ip_address 18.1.2.3 ==> 18.1.2.3


set ip_elements [split $my_ip_address "."] ==> four element list,
with values 18 1 2 3

http://www.scriptics.com/man/tcl7.5/TclCmd/split.n.html

Pattern matching

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

Pattern matching is important across a wide variety of Web programming tasks but most notably when looking for exceptions in
user-entered data and when trying to parse information out of non-cooperating Web sites.
Tcl's pattern matching facilities test whether a given string matches a specified pattern. Patterns are described using a syntax
known as regular expressions. For example, the pattern expression consisting of a single period matches any character. The
pattern a..a matches any four-character string whose first and last characters are both a.
The regexp command takes a pattern, a string, and an optional match variable. It tests whether the string matches the
pattern, returns 1 if there is a match and zero otherwise, and sets the match variable to the part of the string that matched the
pattern:

% set something candelabra


candelabra
% regexp a..a $something match
1
% set match
abra

Patterns can also contain subpatterns (delimited by parentheses) and denote repetition. A star denotes zero or more occurrences of
a pattern, so a(.*)a matches any string of at least two characters that begins and ends with the character a. Whatever has
matched the subpattern between the a's will get put into the first subvariable:

% set something candelabra


candelabra
% regexp a(.*)a $something match
1
% set match
andelabra
Note that Tcl regexp by default behaves in a greedy fashion. There are three alternative substrings of "candelabra" that match the
regexp a(.*)a: "andelabra", "andela", and "abra". Tcl chose the longest substring. This is very painful when trying to pull HTML
pages apart:

% set simple_case "Normal folks might say <i>et cetera</i>"


Normal folks might say <i>et cetera</i>
% regexp {<i>(.+)</i>} $simple_case match italicized_phrase
1
% set italicized_phrase
et cetera
% set some_html "Pedants say <i>sui generis</i> and <i>ipso facto</i>"
Pedants say <i>sui generis</i> and <i>ipso facto</i>
% regexp {<i>(.+)</i>} $some_html match italicized_phrase
1
% set italicized_phrase
sui generis</i> and <i>ipso facto

What you want is a non-greedy regexp, a standard feature of Perl and an option in Tcl 8.1 and later versions (see
http://www.scriptics.com/services/support/howto/regexp81.html).
Lisp systems in the 1970s included elegant ways of returning all possibilities when there were multiple matches for an
expression.
Java libraries, Perl, and Tcl demonstrate the progress of the field of computer science by ignoring these superior systems of
decades past.

Matching Cookies From the Browser

A common problem in Web development is pulling information out of cookies that come from the client. The cookie spec at
http://home.netscape.com/newsref/std/cookie_spec.html mandates that multiple cookies be separated by semicolons. So you look
for "the cookie name that you've been using" followed by an equals sign and them slurp up anything that follows that isn't a
semicolon. Here is how the ArsDigita Community System looks for the value of the last_visit cookie:

regexp {last_visit=([^;]+)} $cookie match last_visit

Note the square brackets inside the regexp. The Tcl interpreter isn't trying to call a procedure because the entire regexp has been
grouped with braces rather than double quotes. Square brackets denote a range of acceptable characters:
[A-Z] would match any uppercase character
[ABC] would match any of first three characters in the alphabet (uppercase only)
[^ABC] would match any character other than the first three uppercase characters in the alphabet, i.e., the ^
reverses the sense of the brackets
The plus sign after the [^;] says "one or more characters that meets the preceding spec", i.e., "one or more characters that isn't a
semicolon". It is distinguished from * in that there must be at least one character for a match.
If successful, the regexp command above will set the match variable with the complete matching string, starting from
"last_visit=". Our code doesn't make any use of this variable but only looks at the subvar last_visit that would also have been
set.
Pages that use this cookie expect an integer and this code failed in one case where a user edited his cookies file and
corrupted it so that his browser was sending several thousands bytes of garbage after the "last_visit=". A better approach might
have been to limit the match to digits:

regexp {last_visit=([0-9]+)} $cookie match last_visit

Matching Into Multiple Variables

More generally regexp allows multiple pattern variables. The pattern variables after the first are set to the substrings that matched
the subpatterns. Here is an example of matching a credit card expiration date entered by a user:

% set date_typed_by_user "06/02"


06/02
% regexp {([0-9][0-9])/([0-9][0-9])} $date_typed_by_user match month year
1
% set month
06
% set year
02
%

Each pair of parentheses corresponds to a subpattern variable.

Full Syntax

The most general form of regexp includes optional flags as well as multiple match variables:

regexp [flags] pattern data matched_result var1 var2 ...

The various flags are


-nocase
uppercase characters in the data are bashed down to lower for case-insensitive matching (make sure that
your pattern is all lowercase!)
-indices
the returned values of the regexp contain the indices delimiting the matched substring, rather than the
strings themselves.
If your pattern begins with a -, put a -- flag at the end of your flags
Regular expression syntax is:
.
matches any character.
*
matches zero or more instances of the previous pattern item.
+
matches one or more instances of the previous pattern item.
?
matches zero or one instances of the previous pattern item.
|
disjunction, e.g., (a|b) matches an a or a b
()
groups a sub-pattern.
[]
delimits a set of characters. ASCII Ranges are specified using hyphens, e.g., [A-z] matches any character
from uppercase A through lowercase z (i.e., any alphabetic character). If the first character in the set is ^,
this complements the set, e.g., [^A-z] matches any non-alphabetic character.
^
Matches only when the pattern appears at the beginning of the string. The ^ must appear at the beginning
of the pattern expression.
$
Matches only when the pattern appears at the end of the string. The $ must appear last in the pattern
expression.
Also see http://www.scriptics.com/man/tcl7.5/TclCmd/regexp.n.html

Matching with substitution

It's common in Web programming to create strings by substitution. Tcl's regsub command performs substitution based on a
pattern:

regsub [flags] pattern data replacements var

matches the pattern against the data. If the match succeeds, the variable named var is set to data, with various parts modified, as
specified by replacements. If the match fails, var is simply set to data. The value returned by regsub is the number of
replacements performed.
The flag -all specifies that every occurrence of the pattern should be replaced. Otherwise only the first occurrence is
replaced. Other flags include -nocase and -- as with regexp
Here's an example from the banner ideas module of the ArsDigita Community System (see
http://photo.net/doc/bannerideas.html). The goal is that each banner idea contain a linked thumbnail image. To facilitate cutting and
pasting of the image html, we don't require that the publisher include uniform subtags within the IMG. However, we use regexp to
clean up:

# turn "<img align=right hspace=5" into "<img align=left border=0 hspace=8"


regsub -nocase {align=[^ ]+} $picture_html "" without_align
regsub -nocase {hspace=[^ ]+} $without_align "" without_hspace
regsub -nocase {<img} $without_hspace {<img align=left border=0 hspace=8} final_photo_html

In the example above, <replacements> specified the literal characters ''. Other replacement directives include:
& inserts the string that matched the pattern
The backslashed numbers \1 through \9 inserts the strings that matched the corresponding sub-patterns in the
pattern.
Here's another web example, which parses HTML, and replaces the comments (delineated in HTML by <!-- and -->) by the
comment text, enclosed in parentheses.

% proc extract_comment_text {html} {


regsub -all {<!--([^-]*)-->} $html {(\1)} with_exposed_comments
return $with_exposed_comments
}

% extract_comment_text {<!--insert the price below-->


We give the same low price to everyone: $219.99
<!--make sure to query out discount if this is one of our big customers-->}
(insert the price below)
We give the same low price to everyone: $219.99
(make sure to query out discount if this is one of our big customers)

Also see http://www.scriptics.com/man/tcl7.5/TclCmd/regsub.n.html

String match

Tcl provides an alternative matching mechanism that is simpler for users to understand than regular expressions. The Tcl command
string match uses "GLOB-style" matching. Here is the syntax:

string match pattern data

It returns 1 if there is a match and 0 otherwise. The only pattern elements permitted here are ?, which matches any single character;
*, which matches any sequence; and [], which delimits a set of characters or a range. This differs from regexp in that the pattern
must match the entire string supplied:

% regexp "foo" "foobar"


1
% string match "foo" "foobar"
0
% # here's what we need to do to make the string match
% # work like the regexp
% string match "*foo*" foobar
1

Here's an example of the character range system in use:

string match {*[0-9]*} $text

returns 1 if text contains at least one digit and 0 otherwise.

Also see http://www.scriptics.com/man/tcl7.5/TclCmd/string.n.html

Array Operations

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

Tcl arrays are actually hash tables and have nothing in common with the data structures called arrays in other programming
languages . A Tcl array provides a rapid answer to the question "is there a value associated with this key". Here is a rat-simple
example:

% set numeric_day(Sunday) 0
0
% set numeric_day(Monday) 1
1
% set numeric_day(Tuesday) 2
2
% # pull one value out of the hash table
% set numeric_day(Monday)
1
% # let's ask Tcl what keys are defined in the hash table
% array names numeric_day
Monday Sunday Tuesday
% # let's see if there are values for Sunday and Wednesday
% info exists numeric_day(Sunday)
1
% info exists numeric_day(Wednesday)
0

You don't have to declare to Tcl that you're going to treat a particular variable as an array; just start setting variables with the form
"variable_name(key)".

You Can Use Tcl Array with Numbers as Keys

Here's a procedure that computes Fibonacci numbers in linear time by storing intermediate values in an array called fibvals. It
uses the for loop, which we'll see again in the section on control structure.

proc fib {n} {


set fibvals(0) 0
set fibvals(1) 1
for {set i 2} {$i <= $n} {incr i} {
set fibvals($i) [expr $fibvals([expr $i - 1]) + $fibvals([expr $i - 2])]
}
return $fibvals($n)
}

Dealing with spaces inside your keys

If your index contains spaces, it will confuse the Tcl parser . For example, imagine an array called snappy_response that contains
appropriate responses to various insults, which are used as the indices to the array. Suppose you want to store a response for
"Have you gained weight?". You can't feed this to Tcl as

set snappy_response(Have you gained weight?) "Your mama is so fat when


she goes to beach little kids shout out 'Free Willy'!"

Alternatives that work:


Escape all the spaces with backslash:
set snappy_response(Have\ you\ gained\ weight?) "Your mama..."
Enclose the array name and parenthesized key in curly braces:
set {snappy_response(Have you gained weight?)} "Your mama..."
Name the index with a variable and then use the variable:
set this_insult "Have you gained weight?"
set snappy_response($this_insult) "Your mama..."

% set {snappy_response(Have you gained weight?)}


Your mama is so fat when she goes to beach little kids shout out 'Free Willy'!

How We Actually Use Tcl Arrays: Caching


One of the nice things about AOLserver is that it is a single Unix process. Thus it is easy for the result of an expensive computation
to be cached for later use by another thread. Here is an extremely powerful procedure that enables a programmer to cache the
result of executing any Tcl statement:

proc memoize {tcl_statement} {


# tell AOLserver that this variable is to be shared among threads
ns_share generic_cache

# we look up the statement in the cache to see if it has already


# been eval'd. The statement itself is the key
if { ![info exists generic_cache($tcl_statement)] } {
# not in the cache already
set statement_value [eval $tcl_statement]
set generic_cache($tcl_statement) $statement_value
}
return $generic_cache($tcl_statement)
}

This first time this procedure is called with a particular argument, the Tcl statement is evaluated (using Tcl's built-in eval
command). The result of that evaluation is then stored in the array variable generic_cache with a key consisting of the full Tcl
statement. The next time memoize is called with the same argument, the info exists generic_cache($tcl_statement) will
evaluate to true and the value will be returned from the cache.
Here's how a piece of code might look before:

ns_return 200 text/html [page_with_top_10_popular_items]

If someone notices that (1) page_with_top_10_popular_items requires sweeping the database and takes 30 seconds to
execute, and (2) the result doesn't change more than once or twice a day, the natural conclusion is memoization:

ns_return 200 text/html [memoize "page_with_top_10_popular_items"]

Our actual toollkit contains Memoize and Memoize_for_Awhile, the latter of which takes an argument of after how many
seconds
the information in the cache should be considered stale and reevaluated.

How We Actually Use Tcl Arrays: In-Memory Database

Typically on the Web the last thing that you'd want is an in-memory database. If the server crashes or the user gets bounced to
another machine by a load-balancer, you don't want critical data to be trapped inside a Web server's virtual memory. However,
there is one situation where you would want an in-memory database: to store information about the server itself.
In the ArsDigita Community System, an attempt is made to document every externally-called procedure. We want to build
up a documentation database that grows as procedures are defined on a running server. The fundamental mechanism is to define
procedures using our own procedure, proc_doc. This takes a documentation string as an extra argument, calls proc to actually
define the procedure, then records in a Tcl array variable the file from which the procedure definition was read and the doc string:

proc proc_doc {name args doc_string body} {


ns_share proc_doc
ns_share proc_source_file
# let's define the procedure first
proc $name $args $body
set proc_doc($name) $doc_string
set proc_source_file($name) [info script]
}

The end-result? http://photo.net/doc/procs.tcl.

Full Documentation

Tcl provides support for iterating through the indices, and for coverting lists to arrays. These are documented in
http://www.scriptics.com/man/tcl7.5/TclCmd/array.n.html
ns_set instead

If you're using AOLserver and need to associate keys with values, you might be better off using the ns_set data structure. The
advantages of ns_set over Tcl arrays are the following:
you can easily pass ns_sets from procedure to procedure
you can easily pass ns_sets from C code to Tcl and vice versa
A disadvantage of ns_sets is that they require O[n] time to look up the value of key, compared to O[1} time for the Tcl array,
which, as noted above, is actually a hash table. If you are managing thousands of keys, this might become significant. Otherwise,
program using whichever data structure seems more natural.
See the Tcl Developer's guide at www.aolserver.com for documentation of the ns_set facility.

Numbers

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

Arithmetic is not done directly by the Tcl interpreter. It is done by calling the C library using the expr command on arthmetic
expressions. The detailed parsing rules for arithmetic expressions depend on the particular Unix implementation, but they are more
or less like in C.
Here are some examples:

# integer division truncates


% expr 7 / 2
3

# the percent sign is used to compute integer remainder


% expr 7%2

# floating point propagates


% expr 7.0 / 2
3.5

% expr sin(.5)+cos(.9)
1.10103550687

% # a zero in front of number means to interpret as octal


% expr 017 + 01
16

% # a 0x in front means to interpret as hex


% expr 0xA + 1
11

% # numbers can be treated like strings!


% string length 100.34
6
% string range 100.34 0 2
100

More: See http://www.scriptics.com/man/tcl7.5/TclCmd/expr.n.html.

Reference

Here are the numeric functions included in Tcl. (Details may vary depending on your Unix implementation of expr.)
abs(x)
asin(x)
acos(x)
atan(x)
atan2(y,x)
atan2 returns the angle theta of the polar coordinates returned when (x,y) is
converted to (r, theta).
ceil(x)
cos(x)
cosh(x)
double(x)
returns x as a double or floating point.
exp(x)
returns e^x
floor(x)
fmod(x,y)
returns the floating point remainder of x/y.
hypot(x,y)
returns the square root of the sum of x squared plus y squared, the length of the
line from (0,0) to (x,y).
int(x)
truncates x to an integer.
log(x)
returns the natural log of x.
log10(x)
returns log base 10 of x.
pow(x,y)
returns x to the y power.
round(x)
sin(x)
sinh(x)
sqrt(x)
tan(x)
tanh(x)

Control Structure

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

Control structures let you say "run this fragment of code if X is true" or "do this a few times" or "do this until something is no longer
true". The available control structures in Tcl may be grouped into the following categories:
conditional
looping (iteration)
error-handling
miscellaneous (non-local exit)

The Fundamental Conditional Command: if

The most basic Tcl control structure is the if command:

if boolean ?then? body1 ?else? ?body2?

Note that the words "then" and "else" are optional, as is the entire else clause. The most basic if statement looks like this:

if {condition} {
body
}

In the ArsDigita Community System, we always leave out the "then", but if we include an else or elseif clause, we put in those
optional words. Consistency is the hobgoblin of little minds...

if {condition} {
body
} elseif {other_condition} {
alternate_body
} else {
another_body
}
Note how the curly braces and keywords are artfully positioned so that the entire if statement is on one line as far as the
interpreter is concerned, i.e., all the newlines are grouped within curly braces. An easy way to break your program is to rewrite the
above statement as follows:

if {condition} {
body
}
elseif {other_condition} {
alternate_body
} else {
another_body
}

The Tcl interpreter will think that the if statement has ended after the first body and will next try to evaluate "elseif" as a procedure.
Let's look at an example from http://software.arsdigita.com/www/register/user-login.tcl. At this point in the ArsDigita
Community System flow, a user has already typed his or her email address.

# Get the user ID


set selection [ns_db 0or1row $db "select user_id, user_state,
converted_p
from users
where upper(email)=upper('$QQemail')"]

if {$selection == ""} {
# Oracle didn't find a row; this email addres is not in the database
# redirect this person to the new user registration page
ns_returnredirect "user-new.tcl?[export_url_vars return_url email]"
return
}

The same page provides an example both of nested if and if then else:

if [ad_parameter AllowPersistentLoginP "" 1] {


# publisher has elected to provide an option to issue
# a persistent cookie with user_id and crypted password
if [ad_parameter PersistentLoginDefaultP "" 1] {
# persistent cookie shoudl be the default
set checked_option "CHECKED"
} else {
set checked_option ""
}
ns_write "<input type=checkbox name=persistent_cookie_p value=t $checked_option>
Remember this address and password?
(<a href=\"explain-persistent-cookies.adp\">help</a>)"
}

Notice that the conventional programming style in Tcl is to call if for effect rather than value. It would work just as well to write
the inner if in a more Lisp-y style:

set checked_option [if [ad_parameter ...] {


subst "CHECKED"
} else {
subst ""
}]

This works because if returns the value of the last expression evaluated. However, being correct and being comprehensible to the
community of Tcl programmers are different things. It is best to write code adhering to indentation and other stylistic conventions.
You don't want to be the only person in the world capable of maintaining a service that has to be up 24x7.

Another Conditional Command: switch


The switch dispatches on the value of its first argument: particular variable as follows:

switch flags value {


pattern1 body1
pattern2 body2
...
}

If http://software.arsdigita.com/www/register/user-login.tcl finds a user in the database, it uses a switch on the user's state to
decide what to do next:

switch $user_state {
"authorized" { # just move on }
"banned" {
ns_returnredirect "banned-user.tcl?user_id=$user_id"
return
}
"deleted" {
ns_returnredirect "deleted-user.tcl?user_id=$user_id"
return
}
"need_email_verification_and_admin_approv" {
ns_returnredirect "awaiting-email-verification.tcl?user_id=$user_id"
return
}
"need_admin_approv" {
ns_returnredirect "awaiting-approval.tcl?user_id=$user_id"
return
}
"need_email_verification" {
ns_returnredirect "awaiting-email-verification.tcl?user_id=$user_id"
return
}
"rejected" {
ns_returnredirect "awaiting-approval.tcl?user_id=$user_id"
return
}
default {
ns_log Warning "Problem with registration state machine on user-login.tcl"
ad_return_error "Problem with login" "There was a problem authenticating the account: $user_id. Most likely, the database
contains users with no user_state."
return
}
}

In this case, we're using the standard switch behavior of matching strings exactly. We're also provide a "default" keyword at the
end that indicates some code to run if nothing else matched.
It is possible to use more sophisticated patterns in switch. Here's a fragment that sends different email depending on the
pattern of the address:

switch -glob $email {


{*mit.edu} { ns_sendmail $email $from $subject $body }
{*cmu.edu} { ns_sendmail $email $from $subject "$body\n\nP.S. Consider applying to MIT. Boston is much nicer than
Pittsburgh"}
{*harvard.edu} { ns_sendmail $email $from $subject "$body\n\nP.S. Please ask your parents to invest in our tech startup."}
}

The third behavior for switch is invoked using the "-regexp" flag. See the pattern matching chapter for more on how these patterns
work.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/switch.n.html
Looping commands while, foreach, and for

The while command in Tcl operates as follows:

while { conditional_statement } {
loop_body_statements
}

The conditional statement is evaluated; if it is true, the loop body statement is executed, and then the conditional statement is
reevaluated and the process repeats. If the conditional statement is ever false, the interpreter does not execute the loop body
statements, and continues to the next line after the conditional.
Here is a while statement used to display the last name, first name of each MIT nerd using a Web service. The conditional is
the result of calling AOLserver's ns_db getrow API procedure. This procedure returns 1 if it can fetch the next row from the
SQL cursor, 0 if there aren't any more rows to fetch.

set selection [ns_db select $db "select first_names, last_name


from users
where lower(email) like '%mit.edu'"]

while { [ns_db getrow $db $selection] } {


# set local variables to the SQL column names
set_variables_after_query
ns_write "<LI>$last_name, $first_names"
}

More: see http://www.scriptics.com/man/tcl7.5/TclCmd/while.n.html


The Tcl foreach command loops through the elements of a list, setting a loop variable to each element in term:

foreach variable_name list {


body
}

Here's an example from http://software.arsdigita.com/www/monitor.tcl, a page that displays current server activity:

# ask AOLserver to return a list of lists, one for each current connection
set connections [ns_server active]

foreach connection $connections {


# separate the sublist elements with "" tags
ns_write $conn "[join $connection ""]"
}

The program http://sofware.arsdigita.com/www/admin/static/link-check.tcl checks every HTML file in an ArsDigita Community


System for dead links. Here's a helper procedure that works on one file:

proc check_file {f} {


# alert the administrator that we're working on this file
ns_write "<li>$f\n<ul>\n"
# read the contents into $content
set stream [open $f]
set content [read $stream]
close $stream
# loop through each reference, relying on API call ns_hrefs
# to parse the HTML and tell us where this file points
foreach url [ns_hrefs $content] {
# do all the hard work
...
}
ns_write "</ul>\n"
}
Notice how easy this procedure was to write thanks to the AOLserver developers thoughtfully providing us with ns_hrefs, which
takes an HTML string and returns a list of every HREF target.

More: see http://www.scriptics.com/man/tcl7.5/TclCmd/foreach.n.html


The last looping command, for, is good for traditional "for i from 1 to 10" kind of iteration. Here's the syntax:

for start test next body

We use this control structure in the winner picking admin page of the ArsDigita Comunity System's contest module:
http://software.arsdigita.com/www/admin/contest/pick-winners.tcl. The input to this page specifies a time period, a contest, and how
many winners are to be picked. Here the result of executing the for loop is a list of N elements, where N is the number of desired
winners:

for {set i 1} {$i <= $n_winners} {incr i} {


# we'll have winner_numbers between 0 and $n_contestants - 1
# because randomRange returns a random integer between 0
# and its argument
lappend winner_numbers [randomRange $n_contestants]
}

More: see http://www.scriptics.com/man/tcl7.5/TclCmd/for.n.html

Error-handling command: catch

If a Tcl command throws an error in a CGI script or an AOLserver API page, by default the user will be presented with an error
page. If you don't want that to happen, fix your bugs! Sometimes it isn't possible to fix your bugs. For example, the ns_httpget
API procedure fetches a Web page from the wider Internet. Under certain network-dependent conditions, it may throw an error. If
you don't want your users to be exposed to that as an error, put in a catch:

catch script ?variable_name?

catch returns 1 if script threw an error, 0 otherwise. If you supply the second argument (variable_name), catch will set that
variable to the result of executing script, whether or not the script threw an error.
Our classic example always involves ns_httpget. Here's one from http://www.webho.com/WealthClock:

# define a procedure that computes the entire page


proc wealth_ReturnWholePage {} {
# do a couple of ns_httpgets and some arithmetic
# to produce the user-visible HTML
...
}

# this is the procedure registered to http://www.webho.com/WealthClock


proc wealth_Top {ignore} {
if [catch {set moby_string [Memoize wealth_ReturnWholePage]} errmsg] {
# something went wrong with our sources
... return an apology message to the users
} else {
# we computed the result (or Memoize got it from the cache)
ns_return 200 text/html $moby_string
}
}

Sending email is another time that a Web server has to go outside its carefully controlled world and might experience an error. Here
is the entire http://software.arsdigita.com/tcl/ad-monitor.tcl, which implements a central facility for other sections in the ArsDigita
Community System. The idea is that programmers can put in "email the administrator if broken" instructions on pages that won't
result in a nightmare for the administrator if the page is getting hit every few seconds.
# the overall goal here is that the ad_host_administrator gets
# notified if something is horribly wrong, but not more than once
# every 15 minutes

# we store the last [ns_time] (seconds since 1970) notification time


# in ad_host_administrator_last_notified

ns_share -init { set ad_host_administrator_last_notified 0 } ad_host_administrator_last_notified

proc ad_notify_host_administrator {subject body {log_p 0}} {


ns_share ad_host_administrator_last_notified
if $log_p {
# usually the error will be in the error log anyway
ns_log Notice "ad_notify_host_administrator: $subject\n\n$body\n\n"
}
if { [ns_time] > [expr $ad_host_administrator_last_notified + 900] } {
# more than 15 minutes have elapsed since last note
set ad_notify_host_administrator [ns_time]
if [catch { ns_sendmail [ad_host_administrator] [ad_system_owner] $subject $body } errmsg] {
ns_log Error "failed sending email note to [ad_host_administrator]"
}
}
}

Make sure that you don't overuse catch. The last thing that you want is a page failing silently. Genuine errors should always be
brought to a user's attention and ideally to the site administrator's. Users should not think that a server has done something on their
behalf when in fact the task was not accomplished.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/catch.n.html

Miscellaneous commands: break, continue, return, and error

When inside a looping command, it is sometimes desirable to get the command to stop looping or to stop executing the current
iteration but to continue on the next one. The break command is used to permanently escape the loop; the continue command is
used to escape the current iteration of the loop but to start again at the next iteration. The syntax for each consists only of the
appropriate word written on a line by itself within a loop.
We often use the break command when we want to limit the number of rows to display from the database. Here's an
example from the photo.net neighbor-to-neighbor system. By default, we only want to show a "reasonable" number of postings on
one page:

set selection [ns_db select $db ... big SQL query ... ]

set list_items ""


# see what the publisher thinks is a reasonable number (default to 100)
set n_reasonable [ad_parameter NReasonablePostings neighbor 100]

# initialize a counter of the number of rows displayed so far


set counter 0
while {[ns_db getrow $db $selection]} {
set_variables_after_query
incr counter
if { $counter > $n_reasonable) } {
# append ellipses
append list_items "<p>\n..."
# flush the database cursor (tell Oracle that we don't need the
# rest of the rows)
ns_db flush $db
# break out of the loop
break
}
append list_items "<li><a href=\"view-one.tcl ..."
}

More: http://www.scriptics.com/man/tcl7.5/TclCmd/break.n.html
The return command has been shown before. It quits the proc it's in and returns the supplied value. Remember that any
procedure lines after return aren't executed. Too many times we've seen code of the following form:

proc a_new_programmers_proc {} {
set db [ns_db gethandle]
# do a bunch of stuff with the database
return $result
# release the database handle
ns_db releasehandle $db
}

The most interesting thing that you can do with return is write procedures that force their callers to return as well. Here's an example
from http://software.arsdigita.com/tcl/ad-security.tcl:

proc ad_maybe_redirect_for_registration {} {
if { [ad_verify_and_get_user_id] != 0 } {
# user is in fact logged in, return happiness
return
} else {
ns_returnredirect "/register/index.tcl?return_url=[ns_urlencode [ns_conn url]$url_args]"
# blow out of 2 levels
return -code return
}
}

A .tcl page can simply call this in-line

ad_maybe_redirect_for_registration

# the code below will never get executed if the user isn't registered
# ... update the database or whatever ...

More: http://www.scriptics.com/man/tcl7.5/TclCmd/return.n.html
The error command returns from a proc and and raises an error that, if not caught by a catch statement, will result in the user
seeing a server error page. The first argument to error is displayed in the debugging backtrace:

proc divide {x y} {
if {$y == 0} {
error "Can't divide by zero."
} else {
return [expr $x / $y]
}
}

More: http://www.scriptics.com/man/tcl7.5/TclCmd/error.n.html

Procedures

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

To define a procedures in Tcl use the following syntax:


proc name { list_of_arguments } {
body_expressions
}

This creates a procedure with the name "name." Tcl has a global environment for procedure names, i.e., there can be only one
procedure called "foobar" in a Tcl system.
The next part of the syntax is the set of arguments, delimited by a set of curly braces. Each argument value is then mapped
into the procedure body, which is also delimited by curly braces. As before, each statement of the procedure body can be
separated by a semi-colon or a newline (or both). Here's an example, taken from the calendar widget component of the ArsDigita
Community System:

proc calendar_convert_julian_to_ansi { date } {


set db [ns_db gethandle subquery]
# make Oracle do all the real work
set output [database_to_tcl_string $db \
"select trunc(to_date('$date', 'J')) from dual"]
ns_db releasehandle $db
return $output
}

Of course in the actual ACS, we encourage programmers to use proc_doc instead, which defines the procedure but also records
a doc string, as described in array variable chapter. Here's what the definition would look like with proc_doc:

proc_doc calendar_convert_julian_to_ansi { date } "Return an ANSI date


for a Julian date" {
set db [ns_db gethandle subquery]
# make Oracle do all the real work
set output [database_to_tcl_string $db \
"select trunc(to_date('$date', 'J')) from dual"]
ns_db releasehandle $db
return $output
}

More: http://www.scriptics.com/man/tcl7.5/TclCmd/proc.n.html

Scope, Upvar and Uplevel

There are three possible scopes for a variable in an AOLserver Tcl script:
local to a procedure (the default)
shared among all procedures executing in one thread (global)
shared among all threads on a server and persistent from one connection to another (ns_share)
To use a variable locally, you need not declare it. To instruct the Tcl interpreter to read and set a variable in the global
environment, you must call global every place that the variable is used. For example, when side graphics have been displayed on
a page, ad_footer needs to know so that it can insert a <BR CLEAR=RIGHT> tag.

# a proc that might display a side graphic


proc ad_decorate_side {} {
# we use a GLOBAL variable (shared by procs in a thread) as opposed to
# an ns_share (shared by many threads)
global sidegraphic_displayed_p
...
set sidegraphic_displayed_p 1
}

proc ad_footer {{signatory ""}} {


global sidegraphic_displayed_p
if [empty_string_p $signatory] {
set signatory [ad_system_owner]
}
if { [info exists sidegraphic_displayed_p] && $sidegraphic_displayed_p } {
# we put in a BR CLEAR=RIGHT so that the signature will clear any side graphic
# from the ad-sidegraphic.tcl package
set extra_br "<br clear=right>"
} else {
set extra_br ""
}
return "
$extra_br
<hr>
<a href=\"mailto:$signatory\"><address>$signatory</address></a>
</body>
</html>"
}

One of the strangest and most difficult to use features of Tcl is the ability to read and write variables up the calling stack with
uplevel and upvar.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/upvar.n.html; http://www.scriptics.com/man/tcl7.5/TclCmd/uplevel.n.html;


http://www.scriptics.com/man/tcl7.5/TclCmd/global.n.html

Optional arguments

Here is an example of a procedure that has one required and one optional argument:

proc ad_header {page_title {extra_stuff_for_document_head ""}} {


set html "<html>
<head>$extra_stuff_for_document_head
<title>$page_title</title>
</head>
"
return $html
}

If a page calls ad_header with one argument, it gets the standard appropriate HTML header. If a page supplies the extra optional
argument, that information gets written into the HEAD. Otherwise, the default value of empty string is used for the second
argument.

Variable number of arguments

In addition, Tcl can also provide for a variable number of arguments at the end, using a special last argument called args in any
procedure definition. After all of the other (previous) arguments are bound to names, the rest of the arguments are shoved into a list
called args which can then be accessed inside the procedure.
You could imagine that between optional arguments and extra ones, the interpreter might get confused. It doesn't, because it
assumes that you aren't using extra args at the end without binding all of the optional ones in the middle; that is, it stuffs argument
values into the argument names in strict order without regard to options, extras, etc.

Rename

In case this wasn't flexible enough, Tcl lets you rename procedures using a proc called rename old_name new_name. If you
wanted to keep the old proc but still take advantage of the great abstraction you've used throughout your site (i.e. not change all of
the command calls when you change their bodies), you can rename the old proc and create a new one with the old one's name.
You can also use rename with the empty string for the second argument. This results in the proc being deleted.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/rename.n.html

File Operations

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon
Tcl has a built-in interface for dealing with Unix files. The commands themselves are relatively straightforward, so we'll just explain
them in a reference list below.

Reference

file atime filename


Returns as a decimal number the time that the file was last accessed.

set access_time [file atime "index.adp"] ==> 916612934

file dirname filename


Returns the name of the parent directory of the file.

set parent_dir [file dirname "~/home/dir/this.adp"] ==> ~/home/dir

file executable filename


Returns 1 if the file is executable, 0 otherwise.

chmod 1111 billg-wealth.tcl


file executable billg-wealth.tcl ==> 1

file exists filename


Returns 1 if the file exists, 0 otherwise.

file exists billg-wealth.tc ==> 0


file exists billg-wealth.tcl ==> 1

file extension filename


Returns the file extension of the file (i.e. from the last dot to the end)

file extension billg-wealth.tcl ==> .tcl

file isdirectory filename


Returns 1 if the file is a directory, 0 otherwise.

file isdirectory . ==> 1


file isdirectory billg-wealth.tcl ==> 0

file isfile filename


Returns 1 if the file is not a directory, symbolic link, or device, 0 otherwise.

file isfile billg-wealth.tcl ==> 1

file lstat filename variablename


Puts the results of the stat command on linkname into variablename.

ln -s billg-wealth.tcl temp.tcl
file lstat temp.tcl temp ==> (array holding stat info)

file mtime filename


Returns the modify time of file as a decimal string.

file modify billg-wealth.tcl ==> 915744902

file owned filename


Returns 1 if the current user owns the file, else 0.

file owned billg-wealth.tcl ==> 1

file readable filename


Returns 1 if the file is readable, else 0.
file readable billg-wealth.tcl ==> 1

file readlink filename


Returns the contents of the symbolic link named filename.

ln -s file.txt file1.txt
file readlink file1.txt ==> file.txt

file rootname filename


Returns all but the extension and the last . of the filename.

file rootname billg-wealth.tcl ==> billg-wealth

file size filename


Returns the size in bytes of the file.

file size billg-wealth.tcl ==> 774

file stat filename variablename


Returns the stat results about the file into the array named variablename. The elements of the variable
array are: atime, ctime, dev, gid, ino, mode, mtime, nlink, size, type, and uid.

file stat billg-wealth.tcl billg_info


set $billg_info(ctime) ==> 916615489

file tail filename


Returns all of the characters after the last / in the filename.

file tail ~/home/dir/subdir/file.txt ==> file.txt

file type filename


Returns the type identified of the filename arg, which can be one of the following: file, directory,
characterSpecial, blockSpecial, fifo, link, or socket.

file type billg-wealth.tcl ==> file

file writable filename


Returns 1 if the file is writable, 0 otherwise.

file writable billg-wealth.tcl ==> 0

More: http://www.scriptics.com/man/tcl7.5/TclCmd/file.n.html

Input/Output Commands

open filename ?access? ?permissions?


Returns a stream handle to open the file for the access specified with the permissions specified. Defaut
values are read for the access required and the permissions are the same as the default permissions on a
file. The access value options are r (read from existing file), r+ (read from and write to existing file), w
(write over or create and write to file as necessary), w+ (read from and write to or create file as
necessary), a (write to existing file; append data to it), a+ (read from and write to existing file; append
data to it).

set this_file_stream [open /tmp/file.txt r]

More: http://www.scriptics.com/man/tcl7.5/TclCmd/open.n.html

puts ?-nonewline? ?stream? string


Write the string to the stream. Default is STDOUT.
puts "Hello, world." ==> Hello, world.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/puts.n.html

gets stream ?varname?


Read a line from the stream. If a variable is specified, put the line into that variable.

gets $thisstream line

More: http://www.scriptics.com/man/tcl7.5/TclCmd/gets.n.html

read streamname ?numbytes?


If numbytes is specified, read that many bytes of the stream. If not, read the whole stream.

set first_ten_bytes [read $this_stream 10]

More: http://www.scriptics.com/man/tcl7.5/TclCmd/read.n.html

read -nonewline streamname


Read the whole stream and discard the last newline.

set this_file_contents [read -nonewline $this_stream]

tell streamname
Return the "seek offset." (See below for seek.)

set seek_offset [tell $this_stream]

More: http://www.scriptics.com/man/tcl7.5/TclCmd/tell.n.html

seek streamname offset ?origin?


Set the seek offset. Origin is either start, current, or end.

seek $this_stream offset end

More: http://www.scriptics.com/man/tcl7.5/TclCmd/seek.n.html

eof stream
Returns 1 if you have reached the end of the stream; 0 otherwise.

if {[eof $this_stream]} {
break
}

More: http://www.scriptics.com/man/tcl7.5/TclCmd/eof.n.html

flush streamname
Write buffers of a stream

flush $this_stream

More: http://www.scriptics.com/man/tcl7.5/TclCmd/flush.n.html

close streamname
Close the stream.

close $this_stream

More: http://www.scriptics.com/man/tcl7.5/TclCmd/close.n.html

Eval
part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

The interpreter can be called explicitly to complete extra rounds of substitutions or simply to interpret an additional time, using the
subst and eval instructions respectively. The eval command takes a string argument which is a command, as follows:

% set cmd {puts stdout "Hello, World!"}


puts stdout "Hello, World!"

% eval $cmd
Hello, World!

The subst command does the single round of substitution ordinarily completed by the interpreter, but without invoking any
command. It takes one argument, the string to be substituted into.

% set a "foo bar"


foo bar

% subst {a=$a date=[exec date]}


a=foo bar date=Thu Feb 30 1:11:11 EST 1901

While curly braces normally prevent internal substitution, they are not respected by the subst command. In order to prevent the
single round of substitution, the backslash must be used before special characters like a dollar sign or square brackets.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/eval.n.html

Exec

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

You can invoke other Unix programs from within a Tcl script. For a Web developer this is either a tremendous convenience or a
dangerous security hole.
Let's cover the convenience part first. If we want to have a Web page that displays how long the server has been up and the
current load average, the exec command is helpful:

% exec /usr/bin/uptime
4:04pm up 29 days, 4:32, 3 users, load average: 0.61, 0.66, 0.63

To make an AOLserver Tcl page that returns this output, you need only wrap the exec in an API call:

ns_return 200 text/plain [exec /usr/bin/uptime]

The photo sharing system at http://photo.net/photodb/ stores user-uploaded content in the Unix file system and hence makes
extensive use of Unix commands to create directories, build thumnails of uploaded images, etc. Here are some examples of how the
photodb system uses exec:

# see how much disk space a user is consuming


set disk_usage [exec du -k $data_path]

# find out how large an uploaded image is (X-Y pixel size)


# by invoking the ImageMagick command "identify"
set identify_str [exec /usr/local/bin/identify -verbose $filename]
regexp {geometry: ([0-9]*)x([0-9]*)} $identify_str match image_x image_y

# create a thumbnail-sized image using the ImageMagick command "convert"


set result_status [exec /usr/local/bin/convert $filename -geometry $size_sm -quality 75 $filename_sm]

# remove a directory of user-uploaded images


if [catch { exec rm -f $path } errmsg] ...

The Dangerous Part


Scripting languages like Perl or Tcl are convenient for Web development but it is possible to write a script that takes user-supplied
input and evaluates it. With Tcl, you run the risk that a user will upload a string containing "[exec /usr/openwin/bin/xterm -display
18.30.0.1]". Because of the [] characters, if this string is ever fed to eval or subst a cracker would have a shell on your Web
server.
If you don't need to use exec an easy solution to the problem is to redefine it:

% proc exec args { return "happy happy joy joy" }


% exec cat /etc/passwd
happy happy joy joy

If you do need to use exec, at least make sure that your Web server is running as an unprivileged user with limited authority
to execute Unix programs. Depending on your publishing requirements and choice of Web server, it may be possible to run the
Web server in a chroot() environment (this is very easy with AOLserver 3.0). This changes the root directory as far as the Web
server is concerned. Thus a Tcl program running within the Web server will not be able to even look at files or programs elsewhere
on the computer.
If you decide to run chrooted, you will have to copy any programs that you actually do need to exec so that they are
underneath the Web server's root directory.

More: http://www.scriptics.com/man/tcl7.5/TclCmd/exec.n.html

This is the last of the main Tcl topics. For more information, check out aolserver.com and the books referenced in this book.

Further Study

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

Here are our suggestions for improving your knowledge and skills, organized by goal.

I want to make fuller use of Tcl per se

Tcl for Web Nerds only covers a small part of Tcl/Tk and public domain Tcl libraries and extensions.
If you want to learn how to build graphical user interface (GUI) applications with the Tk (toolkit), you're best off reading a
book on Tcl/Tk, such as Brent Welch's Practical Programming in Tcl and Tk. Remember, you've just finished a book about Tcl
the language and haven't learn anything about Tk because we expect that all the user interface will be handled by a Web browser.
http://www.scriptics.com/resource/doc/books/ contains a comprehensive and up-to-date list of the traditional Tcl/Tk books.
Scriptics.com is probably the most likely long-term source of information about significant Tcl developments.

I want to become a better Web nerd

It would be nice if modesty prevented us from recommending our own books, such as Philip and Alex's Guide to Web Publishing
and SQL for Web Nerds, both available as hyperlinks from http://photo.net/wtr/, which also contains links to Web standards.
Additional books for Web developers are reviewed at http://photo.net/wtr/bookshelf.html.

I want to become a better computer scientist

Sadly, your path to intellectual glory as a computer scientist will not be smoothed by your knowledge of Tcl. Modesty surely would
prevent Hal from recommending Structure and Interpretation of Computer Programs (Abelson and Sussman; MIT Press) but it is
philg who is typing this page and he feels comfortable telling anyone to start there.

Index

part of Tcl for Web Nerds by Hal Abelson, Philip Greenspun, and Lydia Sandon

abs
acos
append
asin
atan
atan2
break
catch
ceil
close
concat
continue
cos
cosh
double
error
eof
exec
exit
exp
expr
eval
file atime
file dirname
file executable
file exists
file extension
file isdirectory
file isfile
file lstat
file mtime
file owned
file readable
file readlink
file rootname
file size
file stat
file tail
file type
file writable
floor
flush
fmod
for
foreach
format
gets
hypot
global
if
incr -- not listed in TCLfWN; here's the description at scriptics
info args
info body
info exists
info local
info procs
int
join
lindex
linsert
list
llength
log
log10
lrange
lreplace
lsearch
lsort
open
pow
proc
puts
read
regexp
regsub
rename
return
round
scan
seek
set
sin
sinh
split
sqrt
string compare
string first
string last
string match
string range
string tolower
string toupper
string trim
string trimleft
string trimright
string wordend
string wordstart
subst
switch
tan
tanh
tell
uplevel -- not listed in TCLfWN; description at Scriptics:
http://www.scriptics.com/man/tcl7.5/TclCmd/uplevel.n.html
upvar
while

Vous aimerez peut-être aussi