Tcl-tk for Web Nerds

download Tcl-tk for Web Nerds

of 37

Transcript of Tcl-tk for Web Nerds

  • 8/8/2019 Tcl-tk for Web Nerds

    1/37

    Tcl for Web Nerds

    Tcl for Web Nerds

    by Hal Abelson, Philip Greenspun, and Lydia Sandon

    Preface1.Introduction2.Strings3.Lists4.Pattern matching5.Arrays6.Numbers and Arithmetic7.Control structure8.Procedures9.File commands

    10.eval: building Tcl commands with Tcl and feeding them back to Tcl11.exec: building Unix commands with Tcl and feeding them to Unix12.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 canaccuse Prentice-Hall of shortchanging the reader on information. Sadly, however, for the Web nerd most of this information isuseless. 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 getssent 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 interestedin 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 oneevening. By the end of the evening, that person should have learned Tcl, learned a little something about the Web, and not havebeen 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 andInterpretation of Computer Programs (Abelson and Sussman 1996; MIT Press). We're not going to try to redeliver theknowledge in SICP packaged up in Tcl syntax. However, we are going to try to show how some of the big ideas from SICP can beaccomplished 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 simplesyntax:

    (procedure-name arg1 arg2 arg3)

    e.g.,

    (set! ten-factorial (factorial 10))

  • 8/8/2019 Tcl-tk for Web Nerds

    2/37

    (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 graduatestudent'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 arg3procedure_name arg1 [subprocedure subarg1 subarg2]

    This is like Lisp, except that procedures invoked at top level need not be surrounded by delimeters. Procedures invoked fromwithin 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 aworld 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 complainingabout 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 onlyone 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 explicityuse 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, thedouble quote is a grouping mechanism. If your string literal contains any spaces, which would otherwise be interpreted as argumentseparators, you need to group the tokens with double quotes:

    % set the_truth Lisp is the world's best computer languagewrong # 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 secondcommand, we grouped all the words in our string literal with the double quotes and therefore the Tcl interpreter saw only twoarguments 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 acolumn 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

  • 8/8/2019 Tcl-tk for Web Nerds

    3/37

    to use Tcl in other ways:writing a program file and evaluating itembedding Tcl commands in Web pages to create dynamic pagesextending 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 theTcl 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 theinterpreter 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 thandouble 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 commandmust 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 newlinesmakes for rather bad poetry"a very long value of some sort...with a few embedded newlinesmakes 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 newlinesmakes for rather bad poetry}a very long value of some sort...with a few embedded newlinesmakes for rather bad poetry%

  • 8/8/2019 Tcl-tk for Web Nerds

    4/37

    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 variableinvalid command name "a very long value of some sort...

    with a few embedded newlinesmakes 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 wouldotherwise terminate the command:

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

    a very long value of some sort...with a few embedded newlinesmakes 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 poemcontain 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 newlinesmakes 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 3636% 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 themseparate; the computer will succeed but humans maintaining the program in the future will fail. So use lowercase all the time withunderscores 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 acomplex operation and encapsulate it into a function that other programmers can call without worrying about how it works.

  • 8/8/2019 Tcl-tk for Web Nerds

    5/37

    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 103628800

    At first glance, you might think that you've had to learn some new syntax here. In fact, the Tcl procedure-creation procedure iscalled like any other. The three arguments to proc are procedure_name arglist body. The creation command is able to extendover several lines not because the interpreter recognizes something special about proc but because we've used braces to groupblocks of code. Similarly the if statement within the procedure uses braces to group its arguments so that they are all on one lineas 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 minus1 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 linestogether into a single argument.The [factorial... ] procedure call was evaluated but resulted in an output of 0. This isn't a bug in theevaluation of quoted strings, but rather a limitation of the Tcl language itself:

    % factorial 1450

    One of the reasons that commercial programmers work so hard and accomplish so little is that they use languages that can't evendo arithmetic! Tcl's feeble brain is overwhelmed by the idea of an integer larger than one machine word (32 bits in this case). You'llhave 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:

    804792605747199194484902925779806277109997439007500616344745281047115412373646521410850481879839649227439298230298915019813108221651663659572441609408556917739149315905992811411866635786075524601835815642793302504243200000000000000000000000000000000000

    Some powerful things about Tcl

  • 8/8/2019 Tcl-tk for Web Nerds

    6/37

    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 aprogram 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 seeexamples of this later.

    Tcl also supports reflection: You can write a Tcl program that pokes around the run-time environment. One basicmechanism is

    info exists foobar

    which tests whether the variable foobar is defined. The info command can also tell a running program what local variables aredefined (info local), what procedures are defined (info procs), what arguments a procedure takes (info argsprocedure_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 calledset_variables_after_query that looks at the last SQL query executed and sets local variables to the values retrieved from thedatabase:

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

    from userswhere 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 bytesback 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 APIprefixes 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 generatecharacter 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 withbackslash. E.g., to print to a Web page the string you would use the Tcl command

    ns_write ""

    So if the majority of your Tcl expressions are simply printing strings, you'll find it more convenient to avoid the hassles of quotingby

    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 useputs. This idea doesn't make sense with AOLserver, where all the Tcl scripts are running inside the Web server process. If theWeb 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 whathappens.

    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 onsteroids", you might wonder why Lisp isn't a more popular scripting language than Tcl. Lisp hasn't been a complete failure, by theway; it is used as an extension language by users of some popular programs, notably AutoCAD. But Tcl has been much moresuccessful. It has been compiled into hundreds of larger programs, including AOLserver, which is why we wrote this book.

  • 8/8/2019 Tcl-tk for Web Nerds

    7/37

    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 youmake a difference to the world. Tcl illustrates one way:

    make something that is simple enough for almost everyone to understandgive away your source codeexplain 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 safeinterpreted language so that a programming error by one user didn't crash the server and bring down all the Web services for anorganization.

    Tcl was available. Tcl was easy to download and designed to fit inside larger application programs. But the Tcl interpreter asdistributed had one terrible bug: it wasn't thread safe, i.e., you couldn't have two copies of the Tcl interpreter running inside thesame 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 wascritically important for them that Tcl was open-source and simple enough so as to not require months or years of study tounderstand the whole system.

    Compare this to Lisp. Some of the best and brightest computer scientists raised money to build commercial Lispimplementations that they then went out and hawked in an indifferent and confused marketplace. They succeeded only in breakingtheir hearts and their investors' wallets. A handful of academics produced free open-source implementations, notably CMUCommon Lisp (see http://www.cons.org/cmucl/) and various versions of Scheme (seehttp://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, mostformer Lisp programmers are stuck using Unix and Microsoft programming environments and, not only do they have to put up withthese 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 Webclient, 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-handlingprocedures. 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 stringif { [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 somethingif { ![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]

  • 8/8/2019 Tcl-tk for Web Nerds

    8/37

    # 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 complainedthat they didn't like seeing classified ads that were simply pointers to the eBay auction site. Here's a simplified snippet fromhttp://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 visitwww.m-w.com and search for "*ebay*" you'll find that both implementations might bite someone selling rhododendrons or awater-powered mill. That's why the toolkit code checks a "DisalloweBay" parameter, set by the publisher, before declaring this anexception.

    If you're just trying to find a substring, you can use either string first or regexp. If you're trying to do something moresubtle, 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 MUCHHARDER 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 astatic 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 rangecommand:

    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

  • 8/8/2019 Tcl-tk for Web Nerds

    9/37

    The Tcl commands format and scan resemble C's printf and scanf commands. That's pretty much all that any Tcl manual willtell 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 commandscomes from Fortran, a computer language developed by John Backus at IBM in 1954. The FORMAT command in Fortran wouldlet 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 areplaceholders 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" 17Price: 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" 65A

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

    % # turn an ASCII character into a number % scan "A" "%c" the_ascii_value1% set the_ascii_value65%

    Notice that the number returned by scan is a count of how many conversions it was able to perform successfully. If you really wantto 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 usefulthis 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 ... valueNsets 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 theparts of string that matches the corresponding parts of expression.

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

  • 8/8/2019 Tcl-tk for Web Nerds

    10/37

    5/6% set num5% set denom6

    (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_nameReturns a count of the number of matching items that were found and replaced. Primarily called for its effect insetting result_variable_name.

    Here's an example where we ask a user to type in keywords, separated by commands. We then expectto feed this list to a full-text search indexer that will throw an error if handed two commas in a row. We useregsub 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_string2% set query_stringsamoyed, 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 thecontents of HTML tags. See http://www.scriptics.com/services/support/howto/regexp81.html for a full discussionof 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 string2returns 0 if the two strings are equal, -1 if string1 sorts lexicographically before string2, 1 if string2 sortslexicographically before string1:

    string compare apple applesauce ==> -1string compare apple Apple ==> 1

    string first string1 string2returns -1 if string1 is not within string2, else returns the index of the first occurrence. Indices start fromzero, 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 string1 if string matches pattern, 0 if not. See the chapter on pattern matching for an explanation of patterns.

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

    string range catclaw 2 4 ==> tcl

    string tolower stringstring in lower case.

  • 8/8/2019 Tcl-tk for Web Nerds

    11/37

    string compare weBmaster Webmaster => 1

    string compare [string tolower weBmaster] \[string tolower Webmaster] => 0

    string toupper stringstring 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 indexindex of the first character after the last character of the word containing index.

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

    string wordstart string indexindex 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_preferences0% lappend user_preferences "hiking"hiking% lappend user_preferences "biking"hiking biking% lappend user_preferences "whale watching"hiking biking {whale watching}% llength $user_preferences3

    At this point, the variable user_preferences is a three-element list. We can pull individual items out with lindex:

  • 8/8/2019 Tcl-tk for Web Nerds

    12/37

    % lindex $user_preferences 0hiking% lindex $user_preferences 1biking% 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_preferences6

    The Tcl shell's output is giving you an ugly insight into the internal representation of lists as strings, with elements being separatedby

    spaces and grouped with braces. There is no reason to rely on how Tcl represents lists or even think about it. Practice dataabstraction 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 datastructures tend to be RDBMS tables, but here's an example of how it might work, taken fromhttp://photo.net/philg/careers/four-random-people.tcl. We're building a list of lists. Each sublist contains all the information on a

    singlehistorical 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 listsset average_folks [list $einstein $mill ...]

    # let's pull out Einstein's titleset 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} {

  • 8/8/2019 Tcl-tk for Web Nerds

    13/37

    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 youto 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 (@). Thefollowing program extracts all the email addresses and joins them together, separated by commas and spaces, to form a stringcalled spam_address that we can use as the Bcc: field of an email message, to spam them all:

    # open the file for readingset addressees_stream [open "~/addressees.txt" r]

    # read entire file into a variableset contents_of_file [read $addressees_stream]

    close $addressees_stream

    # split the contents on newlinesset list_of_lines [split $contents_of_file "\n"]

    # loop through the linesforeach 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 bysplitting the file at newline characters.We use pattern matching to extract the email address from each line. The pattern here specifies "stuff thatdoesn't contain a space, followed by at-sign, followed by stuff that doesn't contain a space." (See theexplanation of regexp in the chapter on pattern matching.)The iteration keeps lappending to all_addresses, but this variable is never initialized. lappend treats anunbound 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}

  • 8/8/2019 Tcl-tk for Web Nerds

    14/37

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

    lindex list iReturns 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 listReturns the number of elements in list.

    llength $foo ==> 3

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

    lrange list i jReturns 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 nameof 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 7set foo ==> 1 2 {3 4 5} {6 7}

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

    lsearch mode list valueReturn 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" ==> 0lsearch -exact $community_colleges "harvard" ==> -1

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

    lsort switches listSort elements of the list according to the switches: -ascii, -integer, -real, -increasing, -decreasing,-command command. Returns a new list.

  • 8/8/2019 Tcl-tk for Web Nerds

    15/37

    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 joinStringMerge 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 splitCharsSplit a string to produce a list, using (and discarding) the characters in splitChars as the places where tosplit. Returns the resulting list.

    set my_ip_address 18.1.2.3 ==> 18.1.2.3set 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 inuser-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 syntaxknown as regular expressions. For example, the pattern expression consisting of a single period matches any character. Thepattern 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 thepattern, returns 1 if there is a match and zero otherwise, and sets the match variable to the part of the string that matched thepattern:

    % set something candelabracandelabra% regexp a..a $something match1% set matchabra

    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 hasmatched the subpattern between the a's will get put into the first subvariable:

    % set something candelabracandelabra% regexp a(.*)a $something match1% set matchandelabra

    http://www.scriptics.com/man/tcl7.5/TclCmd/split.n.htmlhttp://www.scriptics.com/man/tcl7.5/TclCmd/split.n.html
  • 8/8/2019 Tcl-tk for Web Nerds

    16/37

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

    % set simple_case "Normal folks might say et cetera"

    Normal folks might say et cetera% regexp {(.+)} $simple_case match italicized_phrase1% set italicized_phraseet cetera% set some_html "Pedants say sui generis and ipso facto"Pedants say sui generis and ipso facto% regexp {(.+)} $some_html match italicized_phrase1% set italicized_phrasesui generis and 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 athttp://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 asemicolon. 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 beengrouped 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 asemicolon". 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 beenset.

    Pages that use this cookie expect an integer and this code failed in one case where a user edited his cookies file andcorrupted it so that his browser was sending several thousands bytes of garbage after the "last_visit=". A better approach mighthave 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 matchedthe 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

  • 8/8/2019 Tcl-tk for Web Nerds

    17/37

    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-nocaseuppercase characters in the data are bashed down to lower for case-insensitive matching (make sure thatyour pattern is all lowercase!)-indicesthe 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 beginningof the pattern expression.$Matches only when the pattern appears at the end of the string. The $ must appear last in the patternexpression.

    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 apattern:

    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, asspecified 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 isreplaced. Other flags include -nocase and -- as with regexp

    Here's an example from the banner ideas module of the ArsDigita Community System (seehttp://photo.net/doc/bannerideas.html). The goal is that each banner idea contain a linked thumbnail image. To facilitate cutting andpasting of the image html, we don't require that the publisher include uniform subtags within the IMG. However, we use regexp to

  • 8/8/2019 Tcl-tk for Web Nerds

    18/37

    clean up:

    # turn "

  • 8/8/2019 Tcl-tk for Web Nerds

    19/37

    languages . A Tcl array provides a rapid answer to the question "is there a value associated with this key". Here is a rat-simpleexample:

    % set numeric_day(Sunday) 00% set numeric_day(Monday) 1

    1% set numeric_day(Tuesday) 22% # 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_dayMonday 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. Ituses the for loop, which we'll see again in the section on control structure.

    proc fib {n} {set fibvals(0) 0set fibvals(1) 1for {set i 2} {$i

  • 8/8/2019 Tcl-tk for Web Nerds

    20/37

  • 8/8/2019 Tcl-tk for Web Nerds

    21/37

    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. Theadvantages of ns_set over Tcl arrays are the following:

    you can easily pass ns_sets from procedure to procedureyou 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 arthmeticexpressions. The detailed parsing rules for arithmetic expressions depend on the particular Unix implementation, but they are moreor less like in C.

    Here are some examples:

    # integer division truncates% expr 7 / 23

    # the percent sign is used to compute integer remainder % expr 7%2

    # floating point propagates% expr 7.0 / 23.5

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

    % # a zero in front of number means to interpret as octal% expr 017 + 0116

    % # a 0x in front means to interpret as hex% expr 0xA + 111

    % # numbers can be treated like strings!% string length 100.346% string range 100.34 0 2100

    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) isconverted to (r, theta).ceil(x)

  • 8/8/2019 Tcl-tk for Web Nerds

    22/37

    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 theline 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:

    conditionallooping (iteration)error-handlingmiscellaneous (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 thoseoptional words. Consistency is the hobgoblin of little minds...

    if {condition} {body

    } elseif {other_condition} {alternate_body

    } else {another_body

    }

  • 8/8/2019 Tcl-tk for Web Nerds

    23/37

    Note how the curly braces and keywords are artfully positioned so that the entire if statement is on one line as far as theinterpreter is concerned, i.e., all the newlines are grouped within curly braces. An easy way to break your program is to rewrite theabove 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 IDset selection [ns_db 0or1row $db "select user_id, user_state,

    converted_pfrom userswhere 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 pagens_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 passwordif [ad_parameter PersistentLoginDefaultP "" 1] {

    # persistent cookie shoudl be the defaultset checked_option "CHECKED"

    } else {set checked_option ""

    }ns_write "

    Remember this address and password?(help)"}

    Notice that the conventional programming style in Tcl is to call if for effect rather than value. It would work just as well to writethe 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 thecommunity 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

  • 8/8/2019 Tcl-tk for Web Nerds

    24/37

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

    switch flags value {pattern1 body1pattern2 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 todecide 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 theend 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 thepattern 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 patternswork.

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

  • 8/8/2019 Tcl-tk for Web Nerds

    25/37

  • 8/8/2019 Tcl-tk for Web Nerds

    26/37

    Notice how easy this procedure was to write thanks to the AOLserver developers thoughtfully providing us with ns_hrefs, whichtakes an HTML string and returns a list of every HREF target.

    More: see http://www.scriptics.com/man/tcl7.5/TclCmd/foreach.n.htmlThe 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 howmany 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 desiredwinners:

    for {set i 1} {$i

  • 8/8/2019 Tcl-tk for Web Nerds

    27/37

    # 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_notifiedif $log_p {

    # usually the error will be in the error log anywayns_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 noteset 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 bebrought 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 currentiteration but to continue on the next one. The break command is used to permanently escape the loop; the continue command isused to escape the current iteration of the loop but to start again at the next iteration. The syntax for each consists only of theappropriate 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 anexample from the photo.net neighbor-to-neighbor system. By default, we only want to show a "reasonable" number of postings onone 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 0while {[ns_db getrow $db $selection]} {

    set_variables_after_queryincr counter if { $counter > $n_reasonable) } {

    # append ellipsesappend list_items "

    \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 loopbreak

    }append list_items "

  • 8/8/2019 Tcl-tk for Web Nerds

    28/37

    }

    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 anyprocedure 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 databasereturn $result# release the database handlens_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 examplefrom 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 happinessreturn

    } else {ns_returnredirect "/register/index.tcl?return_url=[ns_urlencode [ns_conn url]$url_args]"# blow out of 2 levelsreturn -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.htmlThe 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:

    http://www.scriptics.com/man/tcl7.5/TclCmd/error.n.htmlhttp://www.scriptics.com/man/tcl7.5/TclCmd/error.n.html
  • 8/8/2019 Tcl-tk for Web Nerds

    29/37

    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 mappedinto the procedure body, which is also delimited by curly braces. As before, each statement of the procedure body can beseparated by a semi-colon or a newline (or both). Here's an example, taken from the calendar widget component of the ArsDigitaCommunity 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 $dbreturn $output

    }

    Of course in the actual ACS, we encourage programmers to use proc_doc instead, which defines the procedure but also recordsa 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 datefor 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 $dbreturn $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 globalenvironment, you must call global every place that the variable is used. For example, when side graphics have been displayed ona page, ad_footer needs to know so that it can insert a
    tag.

    # a proc that might display a side graphicproc 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_pif [empty_string_p $signatory] {

    set signatory [ad_system_owner]

  • 8/8/2019 Tcl-tk for Web Nerds

    30/37

    }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 packageset extra_br "
    "

    } else {

    set extra_br ""}return "

    $extra_br $signatory"}

    One of the strangest and most difficult to use features of Tcl is the ability to read and write variables up the calling stack withuplevel 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 "

    $extra_stuff_for_document_head$page_title"

    return $html}

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

    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 anyprocedure definition. After all of the other (previous) arguments are bound to names, the rest of the arguments are shoved into a listcalled 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 itassumes that you aren't using extra args at the end without binding all of the optional ones in the middle; that is, it stuffs argumentvalues 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 youwanted 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

    http://www.scriptics.com/man/tcl7.5/TclCmd/rename.n.htmlhttp://www.scriptics.com/man/tcl7.5/TclCmd/rename.n.html
  • 8/8/2019 Tcl-tk for Web Nerds

    31/37

    Tcl has a built-in interface for dealing with Unix files. The commands themselves are relatively straightforward, so we'll just explainthem 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 filenameReturns the name of the parent directory of the file.

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

    file executable filenameReturns 1 if the file is executable, 0 otherwise.

    chmod 1111 billg-wealth.tcl

    file executable billg-wealth.tcl ==> 1

    file exists filenameReturns 1 if the file exists, 0 otherwise.

    file exists billg-wealth.tc ==> 0file exists billg-wealth.tcl ==> 1

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

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

    file isdirectory filenameReturns 1 if the file is a directory, 0 otherwise.

    file isdirectory . ==> 1file isdirectory billg-wealth.tcl ==> 0

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

    file isfile billg-wealth.tcl ==> 1

    file lstat filename variablenamePuts the results of the stat command on linkname into variablename.

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

    file mtime filenameReturns the modify time of file as a decimal string.

    file modify billg-wealth.tcl ==> 915744902

    file owned filenameReturns 1 if the current user owns the file, else 0.

    file owned billg-wealth.tcl ==> 1

    file readable filenameReturns 1 if the file is readable, else 0.

  • 8/8/2019 Tcl-tk for Web Nerds

    32/37

    file readable billg-wealth.tcl ==> 1

    file readlink filenameReturns the contents of the symbolic link named filename.

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

    file rootname filenameReturns all but the extension and the last . of the filename.

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

    file size filenameReturns the size in bytes of the file.

    file size billg-wealth.tcl ==> 774

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

    file stat billg-wealth.tcl billg_infoset $billg_info(ctime) ==> 916615489

    file tail filenameReturns all of the characters after the last / in the filename.

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

    file type filenameReturns 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 filenameReturns 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. Defautvalues are read for the access required and the permissions are the same as the default permissions on afile. 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 asnecessary), a (write to existing file; append data to it), a+ (read from and write to existing file; appenddata 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? stringWrite the string to the stream. Default is STDOUT.

  • 8/8/2019 Tcl-tk for Web Nerds

    33/37

    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 streamnameReturn 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 streamReturns 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 streamnameWrite buffers of a stream

    flush $this_stream

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

    close streamnameClose the stream.

    close $this_stream

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

    Eval

    http://www.scriptics.com/man/tcl7.5/TclCmd/close.n.htmlhttp://www.scriptics.com/man/tcl7.5/TclCmd/close.n.html
  • 8/8/2019 Tcl-tk for Web Nerds

    34/37

    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 thesubst 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 $cmdHello, World!

    The subst command does the single round of substitution ordinarily completed by the interpreter, but without invoking anycommand. 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 thesingle 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 adangerous 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 thecurrent load average, the exec command is helpful:

    % exec /usr/bin/uptime4: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 makesextensive use of Unix commands to create directories, build thumnails of uploaded images, etc. Here are some examples of how thephotodb system uses exec:

    # see how much disk space a user is consumingset 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 imagesif [catch { exec rm -f $path } errmsg] ...

    The Dangerous Part

    http://www.scriptics.com/man/tcl7.5/TclCmd/eval.n.htmlhttp://www.scriptics.com/man/tcl7.5/TclCmd/eval.n.html
  • 8/8/2019 Tcl-tk for Web Nerds

    35/37

    Scripting languages like Perl or Tcl are convenient for Web development but it is possible to write a script that takes user-suppliedinput and evaluates it. With Tcl, you run the risk that a user will upload a string containing "[exec /usr/openwin/bin/xterm -display18.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 Webserver.

    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/passwdhappy 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 authorityto execute Unix programs. Depending on your publishing requirements and choice of Web server, it may be possible to run theWeb server in a chroot() environment (this is very easy with AOLserver 3.0). This changes the root directory as far as the Webserver is concerned. Thus a Tcl program running within the Web server will not be able to even look at files or programs elsewhereon 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 areunderneath 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 Tclthe 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 Publishingand 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 wouldprevent Hal from recommending Structure and Interpretation of Computer Programs (Abelson and Sussman; MIT Press) but it isphilg 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

    absacosappendasinatanatan2

  • 8/8/2019 Tcl-tk for Web Nerds

    36/37

    break catchceilcloseconcatcontinue

    coscoshdoubleerror eof execexitexpexpr evalfile atimefile dirnamefile executable

    file existsfile extensionfile isdirectoryfile isfilefile lstatfile mtimefile ownedfile readablefile readlink file rootnamefile sizefile statfile tailfile typefile writablefloor flushfmodfor foreachformatgetshypotglobalif incr -- not listed in TCLfWN; here's the description at scripticsinfo argsinfo bodyinfo existsinfo localinfo procsint

    joinlindexlinsertlistllengthloglog10lrangelreplacelsearch

  • 8/8/2019 Tcl-tk for Web Nerds

    37/37

    lsortopen

    pow proc putsread

    regexpregsubrenamereturnroundscanseek setsinsinhsplitsqrtstring compare

    string firststring laststring matchstring rangestring tolower string toupper string trimstring trimleftstring trimrightstring wordendstring wordstartsubstswitchtantanhtelluplevel -- not listed in TCLfWN; description at Scriptics:http://www.scriptics.com/man/tcl7.5/TclCmd/uplevel.n.htmlupvar while