The tcl shell


Topics

How it works
Variables and the "set" operator
Number representation
Interpretation using [] & {} and the "expr" operator
Procedures
String substitution, commands & regular expressions
Control Flow
Error handling via "catch" and the "exec" command

How it works

All except the most recent version of tcl (ver 8.0) are completely string based interpretive. The newest version includes some binary phases to increase speed. The language is procedure based, with all operations taking a list of string parameters. Even the built in tcl commands are approached in this fashion. The string parameters are handled in a list, with list operatives being built into the language. When dealing with C the items in the list become strings in the *argv[] array which is supplied to the procedure, in a similar fashion in which the operating system hands the command line parameters to the main() function of a program.

The list of parameters is interpreted before being passed to the procedure which is called. Any variables which are in the parameter list are replaced with their contents. This replacement is similar to "glob" replacement by a shell, where a "*.html" in a command is replaced by a list of all of the files ending in the ".html" extension.

Once a procedure has received its arguments, it processes and then returns. A numeric value is returned by most built in commands, with '1' indicating success and '0' indicating failure. Some of the more complex commands can also return string values.

Back to the top


Variables and the "set" operator

Variables in tcl/tk are "declared" by setting them to a value. Each variable can be "set" multiple times, and exists within scope until "unset". When setting a variable all that is required is the variable name. When using the variable the name must be preceded by a "$".

The "puts" command and the comments symbol "#"

A few quick notes to get you started: the comment symbol in tcl/tk is "#" and it works up until the next end of line character. The selection of "#" is rather convenient as it allows tclsh to ignore the line which is used by UNIX to determine what shell to run. The "puts" command takes a string and prints it to the screen. Now for your first tclsh program. Not wanting to break with tradition, here is "hello world":

#!/.software/local/.admin/bins/bin/tclsh

#the following outputs the text "hello world" to the terminal
puts "hello world"
The output looks like this:
hello world
Notice that the "puts" automatically prints the new line character for you at the end of the string. From here on in, the header won't be bothered with, as it should be understood. If you are trying to run the above piece of code don't forget to make the file executable. Also bear in mind the #!/.software/local/.admin/bins/bin/tclsh line should be replaced with the full path location of your tclsh interpreter.

Putting things in variables

Now that we can put things to the screen, how about playing with some variables. The following program uses the "set" operator to put the string "world" in the variable "stuff". Notice that the "$stuff" is interpreted by the "puts" command, and the contents are printed, not the word "stuff".

set stuff world
puts "hello $stuff"
Simple enough. So what if you actually want a "$" to show up in your text? Like in C the back-slash ("\") character allows you to display "non-printable" characters. The following code will print: "hello stuff"
puts "hello \$stuff"
Like the "set" operator there is also an "unset" operator. The "unset" command removes the variable from existence, and any further references to that variable will cause an error. The following code will print the error: "can't read `stuff`: no such variable".
set stuff world
unset stuff
puts "hello $stuff"

Back to the top


Number representation

Everything seen so far has been dealing with text. There is a good reason for this, everything in tcl is text. This is not to say that numbers can not be handled, but to do so, tcl requires special commands. Numbers are strings with just numbers in them. A mix of both numbers and letters will most often result in tcl interpreting the string as a word.

The "incr" command

The "set" and "unset" operators work just as they did above with strings when dealing with numerics. The "incr" operator takes a variable and increments it by a specified value. The following piece of code plays with a variable called "number" to give you an example of the "incr" operator.

set number 3
puts "the number is $number"

incr number 2
puts "the number is now $number"

# if you don't give "incr" the second parameter it defaults to 1
incr number
puts "the number is now $number"
Notice that the "incr" operator is given the name "number" without the "$", this is because we don't want the interpreter to give "incr" the value in "number", but the name of the variable itself.

Number Representation

Tcl also handles negative numbers, scientific, hexadecimal, octal, and floating point. The following code shows off all of these things:

set number 3.14
puts "pi is almost $number"

set number -7e2
puts "a negative scientific number: $number"

set number 0xFFFF
puts "all good programmers like hex: $number"

set number 01234
puts "just like C a leading `0` means octal: $number"

Back to the top


Interpretation using [] & {} and the "expr" operator

The square and brace brackets have special meaning in tcl, and effect how the interpreter handles the string contained within. The brace brackets force no interpretation, and everything inside is passed to a command as it appears. There are numerous places where a programmer might not want interpretation to happen, and this will become more clear in the sections on procedures and controls.

The Square Brackets "[" and "]"

The square brackets mean evaluation. In essence any string inside of the square brackets is treated as a tcl script and run by the interpreter. This parallels the back-quote in many shell programming languages.

The "expr" command

A useful operation is the "expr" command, this is short for expression and it returns the results of an equation contained in the string passed to it. The combination of the square brackets and the "expr" operator allows manipulation of variables. The following code sets a variable called "number" to 3, and then sets a variable "stuff" to be 10 more than what is contained within "number". Notice that the contents of "number" are not effected.

set number 3
set stuff [expr $number + 10]
puts "stuff contains: $stuff"
The above code works by evaluating the script between the square brackets, and replacing this code with the returned value from the script. The script has its variables interpreted before it is run, so the "expr" command sees the string: "3 + 10". The "expr" command takes the string, realizes that we want the numbers added together and returns a string containing the resulting "13". This resulting string replaces where the square brackets were, and so we get "set stuff 13", which of course puts the value "13" in the variable called "stuff".

Semi-colons

The semi-colon character allows the programmer to issue multiple commands on the same line. This becomes useful if we wish to put a couple of operations inside of a set of square brackets. The following code outputs "hello world" to the screen as well as the contents of "stuff".

set number 3
set stuff [puts "hello world"; expr $number + 10]
puts "stuff contains: $stuff"
The return value from a script within square brackets is whatever the last line in the script returns. If we had put the "puts" after the "expr" in the above example, the contents of stuff would have been the empty string, since "puts" returns the empty string.

The semi-colon is a neat trick, but it really isn't necessary. It is perfectly legal to have end of line characters inside of square brackets. The following code does the same thing as the above example.

set number 3
set stuff [puts "hello world"
expr $number + 10]
puts "stuff contains: $stuff"

Back to the top


Procedures

What good is a programming language without being able to call procedures? Tcl supports the basic procedure stuff that you would expect, in fact all of the commands which have been looked at so far have just been procedures which are provided for you by the language. Calling a procedure is as simple as calling any of the commands, just give the name of the procedure plus any arguments you wish to pass it. The procedure must be defined before its first calling. Recursive procedures are supported.

Defining A Procedure: "proc"

The "proc" key-word is what allows you to define a procedure. After "proc" you give the name of the procedure, the name of arguments and then the script which is to be executed when the procedure is called. The arguments must be there, even when you don't need any. There are a couple of ways of dealing with this: give the name "args" and then ignore it, or use a set of brace brackets. The script which is executed when the procedure is called is also contained in brace brackets, this follows from the fact that we do not want the contents interpreted during the definition of the procedure, only during execution. The following code is a new twist on our familiar "hello world"

# the following code defines the procedure "hey_world"
proc hey_world {} {
puts "hello world"
}

# the following code calls "hey_world"
hey_world

Arguments

Accessing the arguments passed to a routine can be done in two ways. If you know how many arguments you want, then simply specify them inside of brace brackets. The second method is using the key-word "args" instead of an argument name, this puts all of the arguments in a variable called "args" which can then be accessed as a string or list. The following example takes exactly two arguments:

proc print {one two} {
puts "var one: $one"
puts "var two: $two"
}

print hello world
print "hey there you big" "fat globe you"

# the following line will cause an error
print hey there you big fat globe you
Notice that specifying items inside of quotes tells tcl that the whole thing is an argument. The second call to print passes two arguments, the third call to print attempts to pass seven arguments and thus fails.

Variable number of arguments

By using the "args" key-word in your argument list, you obtain a sort of catch-all. All of the arguments that aren't taken up by variables before the "args" are put into a variable called "args". This variable can be accessed like any other.

proc print {one args} {
puts "var one: $one"
puts "var two: $args"
}

print hello world

# the following line no longer causes an error
print hey there you big fat globe you
The section on strings discusses some ways of getting at each of the space-separated items in the "args" variable. Tcl also includes a fair amount of list processing techniques which are also useful, these can be found by looking at the web sites mentioned, or in the man pages.

Returning a value

The return value of a procedure is implicitly the return value of the last statement in the script. If you wish to force leaving of a procedure early, or return a specific value that isn't returned by some other line, use the key-word "return". This works just like the return statement in C. A simple procedure to add two numbers and return the sum is given below.

proc add2 {num1 num2} {
return [expr $num1 + $num2]
}

Back to the top


String substitution, commands & regular expressions

Strings in tcl/tk are a lot like those in C. We have already seen examples of variable substitution using the "$" operator, but this is not the only special character. As was mentioned in the introduction to variables the "\" character can precede a "$" to print a "$" and not have it interpreted as a special character. Tcl also provides a fairly standard array of "\" characters:

\$
the "$" character -- instead of variable substitution
\f
form feed
\a
audible alert
\b
back space
\n
new line
\r
carriage return
\t
tab
\xHH
any character represented by 0xHH hex (H ranging from 0 - F)
\ddd
any character represented by 0ddd octal (d ranging from 0-7)
Any character that is put after a back-slash that isn't in the above list is replaced by just the character. Thus "\\" is just "\", and "\p" is just "p". The "\\" is useful when you want a "\" to show up in your output.

String Commands

Everything in tcl is based on strings, so it is fitting that there be a series of string manipulation routines. The string routines are all based on a single command "string" which takes as its first parameter an indicator of what you would like to do with the string. Features exist for finding the length, ranges, and doing comparisons. The following code illustrates some of these features

set string1 "this is my most fine string"
set string2 "this is another string"

set size [string length $string1]
puts "this size is $size"

# comparison returns 0 for equal, 1 for larger, -1 for smaller
puts [string compare $string1 $string2]
puts [string compare $string2 $string1]
puts [string compare $string1 $string1]

# range returns a part of a string -- it starts at 0
puts [string range $string1 8 14]

# index returns the nth character of a string
puts [string index $string1 16]
The "string compare" operator evaluates greater than as coming later in the alphabet. Whenever any counting is done on strings (e.g. the "index" and "range" commands) the first character is accessed using "0". This mimics the C behaviour of strings as arrays. Other "string" operations include "first", "last", and "match", which all look for sub-patterns in the string. The "tolower" and "toupper" operators return an all lower case or upper case version of the string, respectively. The "trim", "trimleft", and "trimright" operators are used to remove white-space from the left, right, or both sides of the string. The trim operators can also be given specific characters to remove instead of the white-space.

Regular Expression Operator "regexp"

The "regexp" operator allows some of the rudimentary regular expression operations to be performed on a string. The typical regular expression operators are supported: ".", "^", "$", "\", "[...]", "(...)", "*", "+", "?", and "|". The "regexp" operator takes an argument for the expression to be evaluated and an argument for the string to be operated on. The "regexp" command returns a '1' or '0' indicating success and failure. Another command called "regsub" does the regular expression operations using substitution. For more information on either of these see the man pages.

Back to the top


Control Flow

Control flow in tcl/tk is performed using procedures. In most cases the control flow statement takes several scripts as arguments, some of the scripts being conditions for execution and some for the script to be executed if the condition is true.

The "if" Statement

The "if" statement takes two scripts as arguments, the first is the condition, and the second is what is to be run if the condition is true. The "if" statement can also be paired with "elsif" and "else" statements. The following code shows an example

if {$i < 3} {incr n}
elsif {$i >3} {incr n 2}
else {incr n 3}
This simple example shows the usage of each of the key-words. The resulting scripts to be executed can be multiple lines and multiple commands. Be careful that the opening brace bracket is on the same line as the "if", "elsif", or "else" key-word, otherwise it won't work.

The "while" Statement

The "while" statement takes two scripts as arguments, the first is the condition, and the second is the script that is executed multiple times until the condition is no longer met. If the condition is not met in the first place then the execute script is not run.

set i 0
while {$i < 3} {puts "hello"; incr i }
This example will print the word "hello" three times.

The "for" Statement

The "for" statement is unnecessary, as everything that can be done with a "for" can be done with a "while". The "for" statement, builds in some of the common things that happen in a "while" as a short-cut for the programmer. The "for" statement behaves in a similar fashion to its C counterpart. This command takes four scripts. The first script is where variables are initialized, the second is the stopping condition, the third script is for incrementing, and the last is the actual body of the loop.

for {set i 0} {$i < 3} {incr i} {puts "hello"}
This funky little one line example does the same thing as the previous example using a "while" statement, just a little more compactly.

The "foreach" Statement

The "foreach" statement is very useful when trying to process lists or parse. This command takes three arguments, the first is a variable name, the second is a string (or list), and the third is a script. For each item in the list the third script gets executed. Upon each iteration of the loop the first variable gets the next word in the list. The following code prints the words "one", "two", and "three" on separate lines.

foreach i "one two three" { puts $i }
A common use for this statement is to process the "args" variable in a subroutine. The next example shows a procedure which returns the sum of a series of numbers.
proc sum args {
set total 0
foreach i $args { set total [expr $total + $i] }
return $total
}


puts "the total is [sum 1 2 3 4 5 6]"

The "switch" Statement

The "switch" statement is a shortcut for multiple if-elsif clauses, using the same condition variable. The structure of the "switch" is a little different from the other control statements. It also takes an argument and a script, but the script has to contain a specific format. The following code fragment should help to make this clear.

switch $x {
a -
b {puts "got an a or b"}
c {puts "definitely got a c"}
}
In the above fragment the contents of variable "x" is compared to the string "a". If the value of "x" is "a" then perform "-", which in this case means to execute the next script which is available. The next available script here is that following the "b" condition. If the "a" to contents of "x" comparison fails, then the "switch" compares against the next item, which in this case is the "b". This process continues until a match is found, or the end of the "switch" statement is found. For example, if the contents of "x" was "a", then "got an a or b" would be output to the screen. Like with the "if" statements, multi-command, multi-line scripts can be used within the "switch" as long as the opening brace bracket is on the same line as the condition.

Back to the top


Error handling via catch and the "exec" command

Tcl does not handle errors very well, the addition of the tk toolkit actually makes the error handling even uglier. Typical response of tcl to errors is to bail out. Tk is a little better in that it brings up an exception window, but there is not much that can be done after that. In the most frequent cases the error has been caused from a syntax or illegal operation, and so forcing the exit of a program is almost expected, so why the concern? A useful aspect of most scripting languages is an interface out to the command line so that programs can be run from the operating system. The problem arises in what these programs return. Many UNIX programs are not very careful about their exit codes, and the meaning of the codes varies from program to program. Unfortunately a non-zero error code is interpreted by tcl as a fatal error. The "exec" command takes a string which is the command to pass through to the operating system. The following example uses the UNIX date program to print the date to the screen.

puts "the date is: [exec date]"
The date program is relatively well behaved, if it had returned with a non-zero exit code the tcl program would have stopped. The solution to this is the "catch" statement. The "catch" operation catches any exceptions thrown by a command, and returns '1' or '0' indicating success or error of the command. An added bonus is that "catch" can also take a variable for the output of a command. The following code snipet is a safe version of the previous example.
catch {exec date} stuff
puts "the date is: $stuff"

Back to the top