I’ve been improving my knowledge of TCL since I’m starting to work with a lot of iRules (which are based on TCL) in my current position. With that said, here’s some notes I took from my latest round of studying and learning.

Many thanks to Exploring Expect by Don Libes for facilitating my learning!

Getting setup

Installing TCL on Ubuntu: apt install tcl

Launching the TCL shell: tclsh

Variables

Setting a variable: set i 100

Unsetting a variable: unset i

Returning a variable’s value: set i or $i Example: puts "Hello there, $var_name!" or puts "Hello there, [set var_name]!"

Braces

Braces defer execution/interpretation so variables do not get interpreted. Notice how var2 returns a literal string whereas var1 does not.

% set b "foo"
foo
% set c "bar"
bar
% set var1 "a$b[set c]\r"
afoobar
% set var2 {a$b[set c]\r}
a$b[set c]\r
%

Loops

While loops

The wrong way to while loop (with sqaure brackets!):

% set i 1; while [$i < 10] { puts $i; incr i }
invalid command name "1"
%

The right way to while loop (with braces!!):

% set i 1; while {$i < 10} { puts $i; incr i }
1
2
3
4
5
6
7
8
9
%

While loops take two elements:

  1. Controlling expression
  2. Body

The while command itself evaluates the controlling expression. That’s why you must use braces instead of square brackets so that the expression is passed into while for evaluation on each iteration through the loop.

For loops

The for command take three items:

  1. The start
  2. The expression
  3. The next

Example:

for {set count 1} {$count < 100} {incr count} {
    puts [expr $count]
}

You can leave the start or next arguments blank, but you need their placeholders like so:

for {} {1} {} {
    puts "Blah!"
}

Important: Unlike while and for commands, if can (optionally) be used without braces because it only needs to be evaluated once. (while and for are evaluated on every loop.)

Thus, these are the same:

% if {1} {
      puts "True!"
  }
True!
%
% if 1 {
      puts "Still true!"
  }
Still true!
%

Incrementing variables

Incrementing variables can be done two ways (the hard way and the easy way):

% set j 10
10
% set j [expr $j + 1]
11
% incr j
12

Likewise, you can decrement variables using similar methods (note: there is no decr function. You use incr with a negative step value):

% set j [expr $j - 1]
11
% incr j -1
10
%

Commands & double-dash (--)

Command arguments should always be terminated with a -- (double dash) to prevent them from being interpreted as arguments.

Example 1: -force is interpreted as an argument rather than a filename.

root@expect1:~# echo "this is a file" > -force
root@expect1:~# ls
-force
root@expect1:~# tclsh
% file copy -force b.txt
wrong # args: should be "file copy ?-option value ...? source ?source ...? target"

Using -- fixes this:

% file copy -- -force b.txt
% ^C
root@expect1:~# ls
-force  b.txt

Example 2: The string -glob abc is interpreted as an invalid option for switch rather than as part of the search string.

% set myvar "-glob abc"
-glob abc
% switch $myvar \
    "-glob abc" {
        puts "Matched -glob abc"
    } default {
        puts "Matched default"
    }
bad option "-glob abc": must be -exact, -glob, -indexvar, -matchvar, -nocase, -regexp, or --

Again, using -- fixes this:

% switch -- $myvar \
    "-glob abc" {
        puts "Matched -glob abc"
    } default {
        puts "Matched default"
    }
Matched -glob abc

Functions

Defining a function is straightforward:

% proc f {a b c} {
        puts "a is: $a"
        puts "b is: $b"
        puts "c is: $c"
}
%

So is calling it:

% f 1 2 3
a is: 1
b is: 2
c is: 3
%

Procedures share a single namespace and may be called within themselves. Weeee!

% proc death {i} {
    puts $i
    death [incr i]
}
% death 1
1
2
3
...
997
998
999
too many nested evaluations (infinite loop?)
%

Lists

Here are some helpful list commands:

  • lindex - return item at index (zero indexed)
  • lrange - return range of items
  • llength - return length of list
  • foreach - iterates over a list

Here is how they are used:

lindex:

% set l "This is a list of strings"
This is a list of strings
% lindex $l 0
This
% lindex $l 3
list
% lindex $l 100

lrange:

% lrange $l 1 4
is a list of

llength:

% llength $l
6

foreach:

% foreach element $l {
      puts $element
  }
This
is
a
list
of
strings
%

Note: After using foreach, element remains set to what the last element was!

% puts $element
strings

You can nest elements in a list using either double quotes or curly braces, but braces are easier to read.

Compare this…

% set x "\"a b\" c d"
"a b" c d
% foreach i $x { puts $i }
a b
c
d
%

…with this:

% set z "{a b} c d"
{a b} c d
% foreach i $z {puts $i }
a b
c
d
%

list takes all of its arguments and combines them into a list.

% list a b c "Hi, ma"
a b c {Hi, ma}
%
% lindex [list a b c "Hi, ma"] 3
Hi, ma
%

Appending to a list can be done (properly) in two ways. (Hint: the second method is easier and better.)

Method 1:

% puts $a
foo bar
% puts $b
bar foo
% set l1 $a
foo bar
% set l1 "$l1 {$b}"
foo bar {bar foo}
%

Method 2:

% set l2 $a
foo bar
% lappend l2 $b
foo bar {bar foo}
%