w w w . a q u a m e n t u s . c o m
  What is Tcl?
    This page
    Hello, world
    Absolute basics
      Absolute Basic #1: everything is a string
      Absolute Basic #2: all commands follow the same format
    Basics
      Calling procedures
      Math / expr
  Control
    if
    while
    for
    switch
  Procedures
    Variable arguments
    Default argument values
  Variable scoping
    global
    upvar
    uplevel
  Lists
    list
    split
    join
    lindex
    llength
    concat
    foreach
    lappend
    linsert
    lreplace
    lset
    lsearch
    lsort
    lrange
  Strings
    string match
    string length
    string index
    string range
    string compare
    string first
    string last
    string wordend
    string wordstart
    string tolower
    string toupper
    string trim
    string trimleft
    string trimright
    format
  regexes
    regexp
    regsub
  Hashes
    array exists
    array names
    array size
    array get
    array set
    array unset
    hash iterating with foreach
    arrays as parameters
    multi-level arrays
  Dictionaries
  File I/O
    open
    close
    gets
    puts
    read
    read
    File Reading Example
    File Writing Example
    seek
    tell
    flush
    eof
    reading/writing binary data
    glob
  file
    (file attributes)
      file atime
      file mtime
      file size
      file exists
      file executable
      file isdirectory
      file isfile
      file readable
      file owned
      file writeable
      file lstat
      file readlink
      file stat
      file type
    (file manipulation)
      file copy
      file delete
      file mkdir
      file rename
    (file path manipulation)
      file dirname
      file extension
      file rootname
      file tail
  Subprocesses
    exec
    open
    pid
  Subprocessing with interp
    interp create
    interp delete
    interp eval
    interp alias
  introspection
    info exists
    info script
    info level
    info tclversion
    info patchlevel
    less useful "info" commands
      info cmdcount
      info commands
      info functions
      info globals
      info locals
      info procs
      info vars
      info args
      info default
      info body
    subst
  namespaces
    namespace eval
    namespace export
    namespace import
    namespace current
    namespace delete
    namespace ensemble
    variable
    Example
  packaging with "source"
  packaging with "package"
    package provide
    package require
    pkg_mkIndex
  Putting packages and namespaces together
  eval
    eval
  system
    cd
    pwd
    argv/argc
    ENV settings
    clock seconds
    clock format
    clock scan
  exceptions (and other errors)
    error
    catch
    return
  Debugging
    error messages
    trace
  profiling
    time
  sockets

What is Tcl?

Tcl ("tool control language") is an interpreted programming language whose design priority was integration into point tools. To that end, it employs minimalism to ensure its interpreters are tiny and easily integrated. (Why would you put a programming language inside a point tool? Because it provides a user control and interface mechanism that's more sophisticated and customizable than, say, menus and commandline switches.)

Tcl's minimalism comes at a cost, though. Its requirement for limited lexical complexity results in a language with precious little syntactic sugar. Users find that some whitespace is irritatingly important (such as the needed lack of a carriage return between a "}" and an "else"). Software engineering is difficult when there's no type safety.

Does "minimal syntax" and "lack of variable typing" remind you of any other languages? Indeed it should: Tcl is, in essence, the assembly language of the scripting world. (Except it's not fast, and the parse error messages are essentially math puzzles.)

This page

The real Tcl tutorial (at tcl.tk) is suboptimal for a few reasons. First, it seems to have been written for people who need to write a Tcl interpreter, not so much for people who need to write a Tcl program. Second, there are some inconsistencies (and some actual errors), which is frustrating.

This web page has two purposes:

Which is why it's called a "tutref".

For most commands, I do not list out every last detail you may ever want to know. For more information on any Tcl command, the best place to go is actually its man page. For example, to get all the details on the foreach command:

% man -s n foreach

Hello, world

What is any programming-language tutorial without a "hello, world" example to kick it off? But it can't just be "hello, world", it has to be "hello, world" from Marvin, the depressed robot in The Hitchhiker's Guide to the Galaxy:

puts "Hello, cruel world!"
Things to notice right away:

Absolute basics

Before I get into any syntax, there are some very core fundamentals that you should understand about Tcl. These core fundamentals will hang over your head through this entire tutorial, and through your entire Tcl-programming career.

Absolute Basic #1: everything is a string

All variable values are strings. Even numbers. Even arrays. Even hashes. Even code blocks. Further, since everything is a string, you can sometimes leave out the quote delimiters (if the string has no whitespace). All of the following commands set the variable myvar to the string "hi" (without quotes):

set myvar "hi"
set myvar {hi}
set myvar hi
(The difference between quotes and braces is that quotes allow escape sequences, variable interpolation, and command interpolation...though you can always disable them with escapes. Interestingly, both quotes and braces allow you to escape an end-of-line in the middle of a string. This code:
set myvar "foo\
    bar"
sets myvar to "foo bar", with a single space between the words, regardless of the indenting before 'bar'!)

(Also you'll notice there's no single-quote version. Tcl doesn't like single-quotes.)

Where this will be most immediately irritating to the new Tcl developer is when you try to do math:

set var1 4
set var2 7
set var3 $var1+$var2           ;# 'var3' set to '4+7'
set var4 $var1 + $var2         ;# syntax error: 'set' expects two params, not 4
set var5 [expr $var1+$var2]    ;# yay! 'var5' set to '11'
set var6 [expr $var1 + $var2]  ;# yay! 'var6' set to '11'

Absolute Basic #2: all commands follow the same format

Everything you do in Tcl will look like the following:

commandname <arg>*
You've seen the set command already, which (usually) takes two arguments. But when I say "everything looks like this" I mean "everything". The if command looks like this. The for command looks like this. Even the syntax for creating namespaces and packages are Tcl commands that look like this. There is precious little syntactic sugar in the Tcl language.

Basics

The first handy thing to know is that the interpreter binary is called tclsh, for "Tcl shell". Running it acts just like every other interpreted-language binary:

So if you want to make a Tcl script just like a perl script, you would have a header like the following in your file:
#!/usr/local/bin/tclsh
...
One curious thing about tclsh is that there is no -v option to tell you which version of Tcl you've got. You have to use a Tcl command to get it:
puts [info patchlevel]

Calling procedures

The way to call a Tcl procedure depends on whether you're calling it directly or if it's part of another procedure call. If you're calling it directly, it follows the syntax you've already seen in the above set examples:

commandname <arg>*
If it's part of another procedure call, then the syntax is to enclose the sub-procedure invocation in square brackets. This code:
command1 [command2]
invokes command2 and feeds its return value as an argument to an invocation of command1.

Math / expr

Math is its own section because Tcl does not have normal infix operators. Instead, you have to use the expr function, but at least expr does allow infix. expr allows you to use all standard math operations, logic operations, bitwise functions, and math functions. (See below for the complete list.)

expr is a function, but it's a good idea to always give its parameters in braces, such as "expr {$i + 1}" instead of "expr $i + 1". Ostensibly that's for security, so that the user can't give $i a value like "[runSomeNastyFunction]" that will get inadvertantly executed by expr.

Syntactic sugar: expr is called implicitly by the condition checks in control statements such as if, while, and for. Yay!

You can specify any numbers in decimal, octal (with a prefix of "0"), or hex (with a prefix of "0x").

For completeness, here are all the operators you can give to expr, in precedence order:

1+unary plus
-unary negate
~bitwise negate
!logical negate
2**exponent
3*mult
/divide
%modulus
4+add
-subtract
5<<bit shift left
>>bit shift right
6<less-than
>greater-than
<=less-than-or-equal
>=greater-than-or-equal
7eqstring equal
nestring not-equal
instring-in check
nistring-not-in check
8&bit and
9^bit xor
10|bit or
11&&logical and
12||logical or
13?:ternary
All logic checks return a 0 or 1.

Here are all the math functions you can call:

abs         acos        asin        atan
atan2       bool        ceil        cos
cosh        double      entier      exp
floor       fmod        hypot       int
isqrt       log         log10       max
min         pow         rand        round
sin         sinh        sqrt        srand
tan         tanh        wide
While Tcl does not have truly boolean values, it gives you some syntactic sugar to pretend. If an expression evaluates to the string values true, false, yes, or no, then its value to expr will be the corresponding boolean value.

Control

if

if {<check>} then? {<block>} [elseif {<check>} then? {<block>}]* [else {<block>}]

Notice that the then keyword is actually optional.

if { $var < 5 } {
  puts "value too small!"
} elseif { $var > 100 } {
  puts "value too large!"
} else {
  ...
}

while

while {<test>} {<block>}

while works exactly as you'd expect: while the <test> condition evaluates to true, the <block> is executed.

while allows both continue and break statements.

set a 0
while { $a < 10 } {
  puts "a is now $a"
  incr a
}

Also note the cool function incr, which is as close as Tcl gets to "x++". Given a variable name, it increments that variable's value. It is shorthand for "set a [expr $a+1]". Note in particular, that you pass the name of the variable to incr, not its value, so there is no dollar-sign sigil. (More on pass-by-reference later!)

for

for {<init>} {<test>} {<incr>} {<body>}

for also works exactly as you'd expect: it executes the <init> code, then checks the <test>. If it evaluates to true, it executes the <body> code, followed by the <incr> code, and repeats back up to the <test>.

Like while, for also allows continue and break statements in the body.

for { set a 0 } { $a < 10 } { incr a } {
  puts "a is now $a"
}

switch

switch <value> { [<pattern> {<block>}]+ }

The switch statement is almost exactly equivalent to a chain of if-else's. In particular, the blocks do not fall over into the next block, like they do in C.

The last <pattern> of a switch can be the string "default", which acts as a catch-all if none of the other patterns matches.

switch returns the value of the executed block (which is the value of its last command -- don't try to use return for that!), or else an empty string if nothing was run.

There are two very similar forms to the switch command -- one uses braces to look just like C's switch, and the other doesn't. The version with braces looks more normal, but the version without braces allows variables in <pattern>s. Examples:

% set a 5
5
% switch 5 {
$a { puts "A" }
5 { puts "B" }
}
B

% switch 5 "
$a { puts {A} }
5 { puts {B} }
"
A
%

Warning! switch uses string comparisons! Always! To pretend to do integer evals, use lots of exprs:

set a 5.000
switch [expr int($a)] {
  5 { ... }
}

Procedures

proc <name> {<args>} {<block>}

Defines a procedure. You can call these newly-defined procedures directly, like they were any other Tcl command.

The returned value of a proc is either set by an explicit return, or is implicitly the value of the last command run in the proc. Good style dictates you use return. :)

proc my_add { a b } {
  return [expr $a + $b]
}
..
set foo 7
set my_sum [my_add 5 $foo]   ;# my_sum set to 12

Believe it or not, Tcl allows two pretty advanced features for procedures: a variable number of arguments, and default argument values.

Variable arguments

The number of procedure arguments can be fixed or variable. For a fixed number, the <args> block is just a list of local variable names. For a variable number, it's still a list of local variable names, but make the last element of <args> be the special string "args" -- inside your procedure, it will be set to the list of everything else passed to it.

Default argument values

Procedure arguments can have default values, so that callers don't have to specify them. In the <args> list, specify a variable as "{name value}" instead of just "name", and its default value will be value. All the usual rules for default function values apply: they all have to be at the end of the argument list, and they are assigned by position.

You can have both default values and a variable number of arguments. It behaves exactly the way you'd think it would -- defaulted variables get assigned first, and if there are more arguments than parameters, it sets args to the overflow.

proc my_func { a {b 1} args } {
  set res [expr $a+$b]
  foreach i $args {
    set res [expr $res+$i]
  }
  return $res
}
..
my_func 1       ;# returns 2
my_func 1 2     ;# returns 3
my_func 1 2 3   ;# returns 6

Variable scoping

Variable scoping in Tcl is a little tricky, because it's neither lexical scoping (such as what C has) nor dynamic scoping (such as what perl kind of has). By default you cannot access variables in any outer scopes, so, bizarrely, entering a procedure immediately hides all the global variables. To get to global variables, you should always prefix them with "::".

global

global <vars>

Declares that the given variables come from the global scope. They are then directly accessible to the local code. This is sort of similar to C++'s using, even though that's for namespaces.

set foo "bar"
proc my_proc {} {
  global foo
  puts "foo = $foo"
}

upvar

upvar [<level>] <parent_context_var> <new_local_var>

Declares a new local variable to be a reference to a variable in a runtime parent scope. (Not the lexical parent scope!!) Any time Tcl encounters your new_local_var in an interpolation, it will instead use the specified parent_context_var from the parent scope.

The thought should cross your brain that if the parent_context_var is from a runtime scope and not a lexical scope, then you really have no idea what to say for parent_context_var because the function could be called by anyone, who may or may not have a variable with a particular name. Indeed, this is the bane of dynamic scoping. In Tcl, this is a hack for a very specific situation -- it's used for implementing pass-by-reference semantics, and the only way it works is if the callers tell you the name of the variable to use. This is how incr works, and here is exactly the only way it can be done:

proc incr { parent_var } {
  upvar $parent_var local_var
  set local_var [expr $local_var + 1]
}
..
set a 5
incr a
The optional <level> argument can either be a bare number to indicate the number of contexts to go up, or a number with a sharp (#) prefix to indicate the number of contexts to go down from global. (So, "1" means "go up one context", which means "parent context"; "#0" means "go down one context from global", which means "global context".) The global function is really just a special case of upvar.

The following quote from the Tcl documentation indicates why you should try not to use either upvar or, really, Tcl in general:

If you are using upvar with anything except #0 or 1, you are most
likely asking for trouble, unless you really know what you're doing.

uplevel

uplevel 1 <code>
This is essentially eval of <code> but in the parent's context, so that it has native access to all of the parent's variables. This is needed for implementing things like while.
def myloop { condition body } {
  #while { $condition } {   ;# fails because 'a' is unknown
  while { [uplevel 1 $condition] } {
    #eval $body    ;# fails because 'count' is unknown
    uplevel 1 $body
  }
}
...
set count 0
myloop { a > 0 } {
  set count [expr $count + $a]
  decr a
}
(Note: I didn't test this example..I wrote this one away from a machine that has tclsh..)

Lists

Remember how I said, in absolute basic #1, that everything in Tcl is just a string? Well, lists are just strings with whitespace. Even commands are really just lists, which are really just strings with whitespace.

The following quote comes directly from the Tcl tutorial:

Tcl commands often have subcommands. The "string" command is an example
of one of these. The "string" command treats its first argument as a
subcommand. Utilizing subcommands is a good way to make one command do
multiple things without using cryptic names. For instance, Tcl has
"string length" instead of, say, "slength".
Given that plausible argument for all the string-related commands, what do you think we're going to do for all the list-related commands? If you said "we'll have a main command called 'list' with subcommands for each of the functions we need!", then you, my friend, are gonna hate this section. The Tcl designers decided instead to implement all the list functions with "cryptic" names. Even after saying how awesome it was that they chose "string length" instead of "slength", the list length function is named "llength".

Great. Well, here we go!

list

list <arg>*

Creates a list from the given elements. This is just one way to create a list.

set my_list [list a b c d e]   ;# my_list has 5 elements
set my_list {a b c d e}        ;# exactly equivalent
set my_list "a b c d e"        ;# also equivalent

split

split <string> <chars>?

Splits the given <string> wherever there are <chars>, which defaults to whitespace characters. Note: <chars> is a list of characters, not a string: split will split when it finds any one of the given characters!

set my_comma_separated_string "a,b,c,d,e"
set my_list [split $my_comma_separated_string ","]

join

join <list> <str>
Joins together each element of the <list> with a copy of <str>.
set mylist    [list 1 2 3 4]
set as_string [join $mylist ", "]  ;# "1, 2, 3, 4"

lindex

lindex <list> <index>

Returns the (index+1)'th element of the given list. (It's index+1 because the index starts at zero.)

set my_list [list a b c d e]
set fourth_element [lindex $my_list 3]

llength

llength <list>

Returns the number of elements in the given list.

set my_list [list a b c d e]
set list_len [llength $my_list]

concat

concat <arg>+

Joins all the given args into a single list. If the arg is a list, its elements are separately added to the result. (i.e. it doesn't add the list as a single object)

set my_list [concat [list a b c] d [list e f]]  ;# creates 'a b c d e f'

foreach

foreach [<var>+ <list>]+ {<block>}

foreach sounds like it should be in with all the other control statements like for and while, but it turns out that foreach is very specific to iterating over lists.

As you can surmise, foreach iterates over each element of the given lists, and executes the given <block> for each value. The easiest, most common way to use foreach is with one index variable and one list. However, you can also specify multiple index variables per list (to let you walk through more than one element per iteration), as well as multiple variables in multiple lists (to let you walk through more than one list in parallel).

foreach j $list { ... }
foreach j $list1 k $list2 { ... }
foreach {j k} $list { ... }
But if you want to iterate through multiple lists serially, you have to concat the lists:
foreach j [concat $list1 $list2] {... }

lappend

lappend <listname> [&kt;arg>]+

Appends the given args to the given listname. List-type args are not flattened, so lists are appended as a single object. Also note that this command takes a list NAME and not a LIST.

set a [list a b c]
set b [list 1 2 3]
lappend a $b         ;# -> a is now 'a b c {1 2 3}'

linsert

linsert <list> <index> [<arg>]+

Inserts the given args to the given list starting at the given index. List-type args are not flattened; lists are inserted as a single object. Also: the special value "end" can be used for the index to indicate that the elements should be inserted at the end of the list, making it essentially the same as calling lappend.

set l [list a b c]
linsert l 1 d e      ;# => returns 'a d e b c'

lreplace

lreplace <list> <first_index> <last_index> [<arg>]+

Replaces elements first_index through last_index (inclusive) with the args given. List-type args are not flattened.

Note that this is how to remove things from lists, since there's no lremove function.

set l [list a b c d]
lreplace l 1 2 e f g      ;# => returns 'a e f g d'
lreplace l 0 1 {}         ;# => returns with the first element removed

lset

lset <listname> <index> <value>

Sets the index'th value of listname to value. List-type args are not flattened.

set l [list a b c]
lset l 1 d          ;# => l is now 'a d c'

lsearch

lsearch <list> <glob_pattern>

Looks for the given glob_pattern in list, and returns the index of the first element that matches. lsearch uses glob patterns, not regexs, though there's a switch if you want to use a regex pattern instead. If no match is found, lsearch returns -1.

set l [list a1 b2 c3]
set i [lsearch $l *2]     ;# => i set to 1

lsort

lsort <list>

Sorts the given list alphabetically, and returns the sorted version.

set l1 [list b d a c]
set l2 [lsort $l1]       ;# => l2 set to 'a b c d'

lrange

lrange <list> <first_index> <last_index>

Returns the sub-list of list from first_index to last_index (inclusive). Once again you may use the special value "end" to indicate the end of the list. (There is no "first", you would just use 0.)

set l1 [list a b c d]
set l2 [lrange $l1 1 2]    ;# => l2 set to 'b c'

Strings

string match

string match <glob_pattern> <string>

Uses glob-type patterns to see if pattern matches string. Glob-type patterns allow "?" (which matches any single character), "*" (which matches any number of any kind of character), and "[]" (which match just the characters listed in the brackets.

set str "the quick brown fox"
if { [string match "qu?ck" $str] } { ... }       ;# => fails
if { [string match "*qu?ck*" $str] } { ... }     ;# => succeeds

string length

string length <string>

Returns the length of the given string.

set str "foo"
set len [string length $str]    ;# => len set to 3

string index

string index <string> <index>

Returns the index'th character of string. You can use the special value "end".

set str "asdf"
set c1 [string index $str 1]    ;# => c1 set to 's'
set c2 [string index $str end]  ;# => c2 set to 'f'

string range

string range <string> <first_index> <last_index>

Returns a string consisting of the characters between first_index and last_index (inclusive) of string.

set str "asdf"
set substr [string range $str 1 2]   ;# => substr set to 'sd'

string compare

string compare <string1> <string2>

Returns what C's strcmp would return: -1 if string1 ascii-sorts before string2, +1 if vice versa, and 0 if the strings are the exact same.

Note that you can use eq and ne as well for string comparisons. The math operator == will actually work for strings as well, as long as the two strings can't both be coerced into numbers.

set str1 "asdf"
set str2 "fdsa"
if { [string compare $str1 $str2] == 0 } { ... }

string first

string first <sub_string> <string>

Returns the index at which sub_string first appears in string, or -1 if it doesn't.

set str "the quick brown fox"
set offset [string first "quick" $str]  ;# => offset set to 4

string last

string last <sub_string> <string>

Returns the index at which sub_string last appears in string, or -1 if it doesn't.

set str "foo bar bas"
set offset [string last "ba" $str]   ;# => offset set to 8

string wordend

string wordend <string> <index>

Assuming the indexth character of string is in the middle of a word, this function returns the index of the end of that word. (Actually, the first character past the end of the word.) "words" contain letters, numbers, and underscores, and possibly also unspecified but specific kinds of punctuation marks.

set str "the quick brown fox"
set offset [string wordend $str 5]   ;# => offset set to 9

string wordstart

string wordstart <string> <index>

See the description for "string wordend"

set str "the quick brown fox"
set offset [string wordstart $str 5]   ;# => offset set to 4

string tolower

string tolower <string>

Returns a fully lowercased version of string.

set str "The Quick @#$@#%@ Fox!"
set lstr [string tolower $str]

string toupper

string toupper <string>

Returns a fully uppercased version of string.

set str "The Quick @#$@#%@ Fox!"
set ustr [string toupper $str]

string trim

string trim <string> [<chars>]?

Returns string but with all of the chars stripped off the beginning and ending. By default, chars is whitespace.

set str "   asdf   "
set str [string trim $str]  ;# => str is 'asdf'

string trimleft

string trimleft <string> [<chars>]?

Returns string but with all of the chars stripped off the beginning. By default, chars is whitespace.

set str "   asdf   "
set str [string trim $str]  ;# => str is 'asdf   '

string trimright

string trimright <string> [<chars>]?

Returns string but with all of the chars stripped off the end. By default, chars is whitespace.

set str "   asdf   "
set str [string trim $str]  ;# => str is '   asdf'

format

format <format_str> [<arg>]+

format is Tcl's equivalent of C's sprintf. You can use pretty much everything you can with sprintf; the most common things you would need are:

You can left/right justify with -/+, and specify field width with a number, as usual.
set val1 42
set val2 18.2
set str "eek"
set nice_str [format "%+4s!  %d != %f!" $str $val1 $val2]  ;# nice_str set to ' eek!  42 != 18.2!"
See the documentation on sprintf for a lot more details. :)

regexes

Regular expressions ("regexs") are an immense topic, but fortunately you can get 80% of their usefulness with just the basics.

regexp

regexp [<opts>] <pattern> <orig_str> [<output_full_match>] [<output_sub_matches>]*

Looks for the given pattern in str. Returns 0 or 1 to indicate if the pattern was found or not. (Usually.)

Since the gold standard for regex specification is perl, here are several examples contrasting common regex usage between it and Tcl:

basic match

perl:  if ($str =~ /f.o/)
Tcl:   if { [regexp {f.o} $str] }

capture of one element

perl:  if ($str =~ /f(.o)/) { my $match = $1; ...
Tcl:   if { [regexp {f(.o)} $str match] }

capture of two elements

perl:  if ($str =~ /(ab)?cd(e.*)?f/) { my ($m1, $m2) = ($1, $2); ...
Tcl:   if { [regexp {(ab)?cd(e.*)?f} $str full m1 m2] }

non-capture of parentheses

perl:  if ($str =~ /f(?:.o)*/)
Tcl:   if { [regexp {f(?:.o)*} $str] }

case-insensitive match

perl:  if ($str =~ /f.o/i)
Tcl:   if { [regexp -nocase {f.o} $str] }

return captured strings, instead of success flag

perl:  my @matches = ($str =~ /f.o/g);
Tcl:   set matches [regexp -inline -all {f.o} $str]

count the number of matches

perl:  my @matches = ($str =~ /f.o/g); my $cnt = scalar @matches;
Tcl:   set cnt [regexp -all {f.o} $str]

regsub

regsub [<opts>] <pattern> <str> <new_text> <res>

Substitutes string matches. Returns the number of substitutions it made, and puts the resulting string into res.

regsub -all {,} "a,b,c,d,e" ":" res      ;# returns '4'
puts $res                                ;# prints "a:b:c:d:e"

regsub is the Tcl equivalent to perl's s/// statement. They're not exactly the same, because the Tcl version returns a new string instead of modifying one in place. The approximate perl version of the above would be:

my $res = "a,b,c,d,e";
$res =~ s/,/:/g;
print "$res\n";

One horrific concern with regexs in Tcl is that even regex patterns are just strings, so you have to be more careful than usual with escaping and interpolation. It is always a good idea to put the pattern in braces, so that Tcl doesn't try to interpolate brackets and dollar signs.

Hashes

Tcl prefers to call hashes "associative arrays", presumably because they are quite adamant that efficiency is not one of their priorities. Like for strings, there is one big command that has several sub-commands. The big command is called, disappointingly, array.

The syntax for array variables is to include the key name in parentheses as part of the variable name. Example:

set foo(bar) "hi mom!"
set foo(asdf) "bye mom!"

Oddly, you cannot use braces around the full names of the variables:

set asdf ${foo(bar)}   ;# no
set asdf $foo(bar)     ;# yes

array exists

array exists <array_name>

Returns whether array_name is an array variable. array_name is the part before the parentheses.

set foo(bar) "hi mom!"
if {[array exists foo]} {
  puts "'foo' is a hash!"
}

This function does NOT tell you if a given key exists in an array!! For that, use info exists!!

if { [info exists foo(bar)] } {
  puts "yes, foo has a 'bar' entry."
}

array names

array names <array_name>

Returns the list of key names that exist for the given array_name.

set foo(bar) "asdf"
set foo(bas) "fdsa"
set keys [array names foo]  ;# => keys is 'bar bas'

array size

array size <array_name>

Returns the number of elements in array_name

set foo(bar) "asdf"
set foo(bas) "fdsa"
set num_keys [array size foo]  ;# => num_keys is '2'

array get

array get <array_name>

Returns a list that is constructed from the keys and values of array_name. The resulting string can be fed into array set to get the original array back.

This is useful for serializing arrays.

set foo(bar) "asdf"
set foo(bas) "fdsa"
set str [array get foo]  ;# => str is 'bar asdf bas fdsa'

array set

array set <array_name> <list>

Deconstructs the given list into key-value pairs, and assigns them into the given array_name. This function will overwrite existing settings in array_name that are defined in list, but it will leave the others alone. In that sense, it's a sort of bulk append.

set str 'bar asdf bas fdsa'
array set foo $str   ;# => foo(bar) is 'asdf', and foo(bas) is 'fdsa'

array unset

array unset <array_name> [<pattern>]?

Deletes all the elements of the array_name whose keys match pattern. By default, pattern matches everything.

set foo(bar) "asdf"
array unset foo   ;# => foo doesn't have 'bar' anymore

hash iterating with foreach

For iterating over a hash, you can use the sneaky multi-variable version of foreach along with the array serialization function:
foreach {key value} [array get my_array] { ... }
For example:
set foo(bar) "asdf"
set foo(bas) "fdsa"
foreach { this_key this_value } [array get foo] {
  puts "$this_key is $this_value"
}

arrays as parameters

Tcl hashes have a nasty gotcha when passed as procedure parameters. Since they have no value, they cannot be passed by value; therefore you have to pass them by reference, which means passing a name and upvar'ing:

proc print12 {array} {
   upvar $array a
   puts "$a(1), $a(2)"
}

set array(1) "A"
set array(2) "B"

print12 array

multi-level arrays

Another drawback to arrays is that they are nonhierarchical -- you cannot have a hash of a hash. The best you can do is to emulate it by including a comma in the key name, like so:

set my_arr(foo,bar) 1

But note that all we do there is have a single key named "foo,bar". If you introduce any whitespace around the comma, you're toast -- the string "foo, bar" is lexigraphically different from "foo,bar", so it won't be found.

Tcl for the win..

Dictionaries

Dictionaries are the next evolution of associative arrays. They serve the exact same function (pretending to be a hash), except they stop sucking in the following ways:

The one big drawback: they're pretty recent. They only came out in Tcl version 8.5, which was released in June 2011 and does not seem to be common yet.

Another drawback is that they have different syntax. Unfortunately I can't give you a good description of dictionaries; the tutorial page is almost useless, and I've got Tcl 8.4 so the man page doesn't exist. You can check out the tutorial page for more info, if you dare.

File I/O

open

open <path> <mode> [<permissions>]?

Opens the given file and returns a file handle. mode follows the C convention: r, r+, w, w+, a, or a+. permissions is only applicable when creating a file; its default value is 0666 anyway.

(Full example below.)

close

close <file_handle>

Closes the file associated with file_handle.

(Full example below.)

gets

gets <file_handle> [<var>]

Reads a line from file_handle, discarding the newline. If var is specified, then the line is put into that variable, and gets returns the number of characters read (or -1 if EOF). If var is not specified, gets returns the line read. An EOF will manifest as an empty string, but so will any empty lines in the input, so if you use gets this way you'll have to use eof to check for end-of-input.

(Full example below.)

puts

puts [-nonewline] <file_handle> <string>

Writes the given string to the file_handle, optionally with or without a newline. For file_handle you can also specify the special values "stdout" or "stderr".

(Full example below.)

read

read [-nonewline] <file_handle>

Reads all of the rest of the file, returning it as a string. -nonewline will discard only the very last character if it happens to be a newline.

(Full example below.)

read

read <file_handle> <num_bytes>

Reads the next num_bytes of the file, returning it as a string.

(Full example below.)

File Reading Example

## all problems found by 'open' throw exceptions:
if {[catch {set read_fh [open "input.txt" "r"]} errmsg]} {
  error "ERROR: $errmsg"
}

## read the file line-by-line:
while {[gets $read_fh this_line] != -1} {
  ...
}
## or read the entire file into one string:
set file_contents [read $read_fh]
## or read the next N bytes into a string:
set next_block [read $read_fh 1024]

close $read_fh

File Writing Example

## all problems found by 'open' throw exceptions:
if {[catch {set write_fh [open "output.txt" "w"]} errmsg]} {
  error "ERROR: $errmsg"
}

puts $write_fh "blah blah blah"
## need a catch on close, because that's where most errors happen:
if {[catch { close $write_fh } errmsg]} {
  error "ERROR: $errmsg"
}

seek

seek <file_handle> <offset> [<origin>]

Moves the position in the file to the given offset. origin can be the special strings "start", "current", or "end".

# read just the last 256 bytes of the file:
seek $read_fh 256 end
set last_block [read $read_fh 256]

tell

tell <file_handle>

Returns the current file position.

set my_current_loc [tell $read_fh]

flush

flush <file_handle>

Flushes any buffers on the associated file_handle.

flush $write_fh

eof

eof <file_handle>

Returns 1 if the given file_handle has hit the end-of-file; otherwise returns 0.

reading/writing binary data

All the functions listed so far were meant for dealing with text files. Reading or writing binary data requires you to go through fconfigure.

Power tip: if you have control over your script's input format, you could make it be Tcl source code. Then you can use source instead of having to open files and parse them yourself!

glob

glob [-nocomplain] [-types <types>] <pattern>

Lists the files that match the glob-pattern pattern. The glob-pattern lets you use "." and "*" as usual, but also allows you to braces to specify a sub-list. If you want files in another directory, you need to specify the path as part of the pattern. The -nocomplain keeps glob from throwing a fit if there don't happen to be any matches. The -type switch lets you restrict the matches to files or directories, or certain permissions. There are lots of things you can do with glob; check the man page.

set file_list [glob diffs.{old,new}]

file

file is another major command that has a ton of subcommands, mostly for querying timestamps and status. Times are reported in seconds since 1/1/1970.

(file attributes)

file atime

file atime <path>

Returns the last access time of the given file.

set last_read_time [file atime "/usr/blah/blah"]

file mtime

file mtime <path>

Returns the last modification time of the given file.

set last_changed [file mtime "myinput.txt"]

file size

file size <path>

Returns the size of the file (in bytes).

set size [file size "myfile.txt"]

file exists

file exists <path>

Returns whether or not the given file exists and the user has read access to all its parent directories.

if {![file exists "input.txt"]} {
  ...
}

file executable

file executable <path>

Returns whether or not the given file is executable.

if {[file executable "/usr/local/bin/perl"]} {
  ...
}

file isdirectory

file isdirectory <path>

Returns whether or not the given path is a directory.

if {![file isdirectory "indir"]} {
  ...
}

file isfile

file isfile <path>

Returns whether or not the given path is a regular file.

if {[file isfile "maybe_a_link.txt"]} {
  ...
}

file readable

file readable <path>

Returns whether the file is readable by the current owner.

if {![file readable "input.txt"]} {
  ...
}

file owned

file owned <path>

Returns whether or not the current user owns this file.

if {[file owned "input.txt"] ne $env(USER)} {
  ...
}

file writeable

file writeable <path>

Returns whether or not the current user can write the the path.

if {![file writeable "proposed.output.txt"]} {
  ...
}

file lstat

file lstat <path> <varname>

Runs the usual lstat function on the given path, and populates the given varname as an array variable with fields such as atime, ctime, dev, gid, ino, mode, etc.

file lstat "input.txt" file_info
if {file_info(mode) == 0666} {
  ...
}

file readlink

file readlink <path>

Returns the text content of a symlink.

set tgt [file readlink "link.txt"]

file stat

file stat <path> <varname>

Runs the stat function on the given path, and populates the given varname as an array with various fields.

file stat "myfile.txt" file_info
if {file_info(atime) < ... } {
  ...
}

file type

file type <path>

Returns a string that describes what path is. The string is one of file, directory, characterSpecial, blockSpecial, fifo, link, or socket.

if {[file type "myinput.txt"] ne "file"} {
  ...
}

(file manipulation)

file copy

file copy [-force] <source> <dest>

Copies the file. dest may be a directory.

file copy "myfile.txt" "myfile.txt-backup"

file delete

file delete [-force] <dest>

Deletes the given file or directory. -force lets you delete non-empty directories.

file delete "goner.txt"

file mkdir

file mkdir <path>

Makes a new directory. The "-p" is implied, so it will create all necessary parent directories.

file mkdir "newdir"

file rename

file rename [-force] <source> <dest>

Renames the given file/directory. -force lets you overwrite an existing file.

file rename "existing_output.txt" "out_of_the_way.txt"

(file path manipulation)

file dirname

file dirname <path>

Returns the directory portion of the given path

set dir [file dirname "/foo/bar/bas.txt"]  ;# => dir is '/foo/bar'

file extension

file extension <path>

Returns the extension part of the given path (such as ".txt" or ".tcl").

set ext [file extension "/foo/bar/bas.txt"] ;# => ext is '.txt'

file rootname

file rootname <path>

Returns the root of the path, which is the part that isn't the extension.

set root [file rootname "/foo/bar/bas.txt"] ;# => root is '/foo/bar/bas'

file tail

file tail <path>

Returns the non-directory parts of the file path string.

set tail [file tail "/foo/bar/bas.txt"] ;# => tail is 'bas.txt'

Believe it or not, there are even more file subcommands. Check the man page.

Subprocesses

There are two ways to run a sub-process from Tcl. The first is exec, which, despite its name, behaves more like what's usually called "system": it runs things as a separate subprocess. The second is to use open, which lets you connect the sub-process's I/O to a file handle.

exec

exec [-keepnewline] <program> [<args>]*

Runs the given program with the given args, and returns either the output or an error. You can redirect and pipe pretty much anything you want, like you would with the shell, but Tcl gives you the cool new ability to redirect stdin or stdout to or from Tcl file handles using "<@" and ">@".

I highly recommend reading through the man page for exec if you're not entirely comfortable with this hand-wavy discussion of redirections. :)

Warning on exec: if the subprocess writes anything to stderr, exec considers it to have failed, and it will then die! You have one of two workarounds: first, call exec with -ignorestderr; second, use catch. (Well, you should probably use catch any time you use exec anyway.)

By default, exec is blocking. You can push it into the background by appending "&" to the very end of the command line; in that case, exec returns the pids of the sub-processes (instead of the output).

# check no errors at all:
exec /bin/ls *

# catch errors:
if {[catch {exec /bin/ls *} output]} { ...
  puts "ERROR: ls failed.  Output: $output"
} else {
  puts "Result of ls: $output"
}

open

open |<progname> [<access>]?

access is technically optional because it defaults to "r", but when you use it for connecting to a process I think it's a good idea to always specify it.

To drive a sub-process's input, use "w":

# some problems (missing program) happen on open():
if { [catch { set fh [open {|some_prog} {w}] } ] } {
  error "ERROR: $errmsg"
}
puts $fh "blah blah blah"
# most problems happen on close() instead of open():
if { [catch { close $fh } errmsg] } {
  error "ERROR: $errmsg"
}

To read a sub-process's output, use "r":

# some problems (missing program) happen on open():
if { [catch { set fh [open {|some_prog} {r}] } ] } {
  error "ERROR: $errmsg"
}
# read each line:
while {[gets $fh this_line] != -1} {
  ..
}
# most problems happen on close() instead of open():
if { [catch { close $fh } errmsg] } {
  error "ERROR: $errmsg"
}

To both drive stdin and gets stdout, use "r+", and note that you will probably need to call flush before every gets:

% set fh [open {|bc} {r+}]
file5
% puts $fh "2+2"
% flush $fh
% gets $fh
4
% puts $fh "5*4"
% flush $fh
% gets $fh
20

pid

pid

Returns the PID of the current process.

set mypid [pid]

Subprocessing with interp

Another way to run a subprocess is interp. interp creates a child interpreter that can then run on a block of code in its own isolated namespace. This can be used to run possibly unsafe code (say, something given to you by a web page). They're sort of green threads -- they don't run independently, and if one blocks they all block. Almost no variables are shared (with the notable exception of $::env), nor are any file handles.

interp create

interp create [-safe] [<name>]

Creates a child interpreter. -safe keeps the interpreter from executing anything that might be harmful.

interp create "my_interp"

interp delete

interp delete <name>

Deletes the given child interpreter.

interp delete my_interp

interp eval

interp eval <name> [<arg>]+

Just like eval, except the code is run in the child interpreter.

interp eval my_interp {
  set foo 42
  set bar 13
}

interp alias

interp alias <...>+

Creates a link between master/slave interpreters, so that they can access each other's state.

(Instead of an example, I am hereby giving you a snarky comment that anything this advanced should be done in an another language.)

introspection

Introspection is unique to interpreted languages. It allows the programmers to ask questions about the program itself, such as "is there a variable named 'foo'?" Tcl's command for introspection is info, and it has a bunch of sub-commands.

info exists

info exists <varname>

Returns whether or not the given variable name is accessible in the current context. This is particularly useful for checking if a particular element exists in an array.

set h(e1) "foo"
set h(e2) "bar"
if {[info exists h(e3)]} {
  ...
}

It is also useful for checking if a variable is defined:

unset -nocomplain errmsg      ;# make sure 'errmsg' is undefined
catch { myfunction } errmsg
if {[info exists errmsg]} {   ;# then there was an error

info script

info script

Returns the name of the script being run. (Same as $0 in perl, or argv[0] in C.)

if {[llength $::argv] == 0} {
  puts "[info script] -foo -bar"
  return -1
}

info level

info level [<number>]?

Returns the stack level at which the program is currently running, which you can use to generate a stack trace. Positive values for number will instead return the procedure (and arguments) for that level. ("0" is the global level.) Negative values do the same thing except they count up from the current level instead of counting down from the global level.

puts "currently at level [info level]"
puts "called by [info level -1]"

info tclversion

info tclversion

Returns the version of the interpreter, such as "8.4".

set this_ver [info tclversion]  ;# => this_ver is '8.4'

info patchlevel

info patchlevel

Returns the version of the interpreter, such as "8.4.6".

set ver [info patchlevel]  ;# => ver is '8.4.6'

less useful "info" commands

info cmdcount

info cmdcount

Returns the number of commands that have been run so far by this program.

if {[info cmdcount > 100]} {
  puts "whatever you're doing should probably be in another language!"
}

info commands

info commands <glob_pattern>

Returns a list of available Tcl commands that match the given pattern.

set all_cmds [info commands *]

info functions

info functions <glob_pattern>

Returns the list of available math functions that match glob_pattern. These are the functions you can use in expr commands.

set math_funcs [info functions *]

info globals

info globals <glob_pattern>

Returns the list of global variables that match glob_pattern.

set globs [info globals *]

info locals

info locals <glob_pattern>

Returns the list of local variables that match glob_pattern.

set locs [info locals *]

info procs

info procs <glob_pattern>

Returns the list of visible procedures that match glob_pattern.

set procs [info procs *]

info vars

info vars <glob_pattern>

Returns the list of local and global variables that match glob_pattern.

set vars [info vars *]

info args

info args <procname>

Returns the list of names of arguments to the given procedure.

proc foo { a b } { ... }
set arglist [info args foo]  ;# => arglist is 'a b'

info default

info default <procname> <argname> <varname>

Returns whether or not arg argname of procname has a default value; if it does, it also puts that default into varname.

proc foo { a {b 0} } { ... }
info default foo a d1   ;# d1 is not set
info default foo b d2   ;# d2 is '0'

info body

info body <procname>

Returns the body of the given procedure.

puts "The code for the sqrt function is:\n[info body sqrt]"

subst

Another introspection-like ability is the subst function, which tells Tcl to do a round of variable and command interpolation. Tcl already does one round as it's compiling the source code, but that's not enough to let you do weird stuff like, say, construct a variable name on the fly.

% set a "foo"
foo
% set b a
a
% puts {No substitution: $a $$b}
No substitution: $a $$b
% puts "With Tcl's first level of substitution: $a $$b"
With Tcl's first level of substitution: foo $a
% puts [subst "With Tcl's plus a subst: $a $$b"]
With Tcl's plus a subst: foo foo

namespaces

One common problem that programmers run into is name collision. Tcl lets you create hierarchical namespaces so that you can carve out your own little sandbox. Tcl's namespaces look just like C++'s namespaces, in that they're separated by double-colons, and the global namespace is an empty string. However, Tcl also lets you selectively import or export symbols to or from a namespace.

Namespaces are critical for working with packages.

In almost typical Tcl fashion, there's one Tcl command for namespace functions, with several sub-commands.

namespace eval

namespace eval <ns_name> <code>

Defines the namespace in which code lives. It looks very much like C++'s namespaces, except that you can later define things in that namespace outside of the "namespace eval" block.

namespace eval foo {
  set a "foo"   ;# => full var name is foo::a.  Or ::foo::a.
}

namespace export

namespace export [-clear]? [<glob_pattern>]+

Adds any symbols matching pattern to the list of this namespace's exported symbols. This is incremental, so you can call it many times to add things; if you don't want it to be incremental, use the -clear switch to clear out all the existing symbols first.

namespace eval foo {
  namespace export *
}

namespace import

namespace import [<glob_pattern>]+

Imports any commands matching glob_pattern into the current namespace. Namespaces kind of act like procedures, in that once you're in one you can't see anything in any parent scopes. You can use namespace import to let a seemingly local variable actually refer to something in another package, which saves you from having to type a lot of scope-resolution over and over.

namespace eval {
  namespace import foo
  put "foo::a = $a"   ;# "$a" actually picks up "$foo::a"
}

namespace current

namespace current

Returns the name of the current namespace, fully qualified all the way to the global namespace.

puts "vars in [namespace current] are: [info vars]"

namespace delete

namespace delete <ns_name>

Deletes the namespace, including everything in it. I have absolutely no idea what value there is in being able to delete namespaces on the fly, but if you come up with one, you'll be a happy Tcl user.

namespace delete foo

namespace ensemble

namespace ensemble

Lets you access a namespace exactly like one of Tcl's typical major-commands-with-subcommands. Unfortunately the docs are thin on how to use this, and this requires Tcl 8.5 so I don't have the man page for it.

variable

variable [<name> <value>]+

Declares variables that are local to a namespace. The intent of this function is to encapsulate variables so that they don't pollute the global namespace. They sort of act like global variables (in that you can access them from multiple procedures, regardless of calling context), but won't collide with either global variables or variables in other namespaces.

namespace eval myspace {
  variable foo
}

proc ::myspace::myfunc {
  variable foo   ;# now $foo refers to this namespace's "foo"
}

Example

namespace eval foo {
  proc bar {} {
  }
}
namespace eval fee {
  proc bar {} {
  }
}

bar   ;# not found
foo::bar   ;# found
fee::bar   ;# found

Example for imports/exports:

namespace eval foo {
  namespace export bar
  proc bar {} {}   ;# true path is "::foo::bar"
}

namespace eval asdf {

  # with or without any export or import, it can be accessed like this:
  ::foo::bar

  # with only export, we can still only use ::foo::bar

  # with only import, we can still only use ::foo::bar

  # with both import and export, we can do this:
  namespace import ::foo::bar
  bar
}

# with or without any export, it can be accessed like this:
::foo::bar

packaging with "source"

The first approximation to software packaging comes from the source command, which is exactly like csh's source and somewhat similar to C's #include. The source command tells the Tcl interpreter to go execute the commands in the given file. If there's a return in the sourced file, the interpreter returns back to the original program.

source can be used to approximate several common mechanisms:

source returns the value of the last statement executed in the script, and if there's an error in the script, source will return that. The best way to tell if a source was successful is to run it through catch:

if {[catch {source foo.tcl}]} { ... }

You can get the return code from source while catching:

if {[catch { set res [source foo.tcl]}]} { ... }

Note that if the source fails, then the 'set res' will not be executed, so you have to check the result of the catch before using res. Here's another way to do that exact same code, but unrolled a bit for illustration:

set catch_res [catch {set source_res [source foo.tcl]}]
if {$catch_res} {
  ..catch caught something, so source had an error..
} else {
  ..catch caught nothing, so source was okay, and you can use $source_res...
}

packaging with "package"

Packages are the next step up from using source as a way to modularize code. They are much more similar to C's #include, and perl's 'use'.

You can package things up without using namespaces, and you can have namespaces without using packages, but from a software engineering perspective the best practice is to use them together.

package provide

package provide <pkg_name> [<pkg_version>]?

Declares that the package named pkg_name now exists, with the given (optional) version. (Multiple versions of a given package are allowed to be loaded at the same time!)

package provide my_module

package require

package require [-exact]? <pkg_name> [<pkg_version>]?"

Loads the given package. If you specify a pkg_version, then it will load any member of its major family that's at or after your specified version; with -exact, only the version you requested will be loaded.

package require tclxml

pkg_mkIndex

pkg_mkIndex <dir> [<pattern>]+

Creates a pkgIndex.tcl for you. When you're done creating your package, you'll want to run this (yes, from the interpreter) to generate a pkgIndex.tcl file. Then you'll want to install pkgIndex.tcl and your .tcl file into a directory with the package name, as as sub-directory of something in $::tcl_pkgPath or $::auto_path, so that "package require" will find it. (Or, consumers could lappend auto_path $somedir to pick it up from wherever you want to put it.)

Note: you must have a namespace in order for pkg_mkIndex to generate a nonempty file.

Note: in practice, a declared version number is required, because the package ifneeded call in pkgIndex.tcl needs it.

Of course, there's only one line in pkgIndex.tcl anyway, so if you really wanted you could just assume manual ownership of it:

package ifneeded Foo 0 [list source [file join $dir Foo.tcl]]
where you replace "Foo"s with your package name, and "0" with your version. Do not change "$dir" -- leave it as a variable; it gets magically filled in by the package code.

Putting packages and namespaces together

Packages allow you to bundle code and data together into some logical unit, and namespaces reduce the chances that your stuff will cause a name collision. To put the two together, it is highly recommended that you choose the same name for the package and the namespace.

This is as close as Tcl gets to object-oriented programming. It works almost the same way that C implements objects: by passing a token to procedures to indicate which object to use.

package provide mystack 1.0

# declare a namespace:
namespace eval mystack {
  # declare our procs:
  namespace export new push pop
  # declare our variables, though they're really private:
  variable stack
  variable stack_counter 0
}

# define procs:
proc ::mystack::new {} {
  variable stack
  variable stack_counter
  set stack_name "stack[incr stack_counter]"
  set stack($stack_name) [list]
  return $stack_name
}

proc ::mystack::push { stack_name val } {
  variable stack
  lappend stack($stack_name) $val
}

proc ::mystack::pop { stack_name } {
  variable stack
  set res [lindex $stack($stack_name) end]
  set stack($stack_name) [lrange $stack($stack_name) 0 end-1]
  return $res
}

The consuming code for this mystack object looks like this:

package require mystack
set mystack [mystack::new]
mystack::push $mystack foo
mystack::push $mystack bar
..
set val [mystack::pop $mystack]

eval

eval allows you to create and run Tcl code on the fly. Anyone with a deep history of compiled languages will be baffled by this, but it's quite common in interpreted languages. It's also fairly dangerous, so you're warned.

eval

eval <command> [<arg>]*

Runs the given Tcl command with the given args, as if you had run them directly. One reason to use eval is so that you can trap exceptions.

Another good reason for using eval is to expand out the parameters in a Tcl command string that you've assembled on the fly:

proc plus1 { a } {
  puts [expr $a + 1]
}

proc minus1 { a } {
  puts [expr $a - 1]
}

set cmd {plus1 4}
set cmd {minus1 4}

$cmd    ;# this does not work; there is no 'minus1 4' function
eval $cmd  ;# this works; Tcl now sees the '4' as a parameter to 'minus1'

The "best practice" for creating lines to eval is allegedly to use lists and lappend. The above example should then be:

...
set cmd [list minus1 4]
...
or
...
set cmd minus1
lappend minus1 4
...

system

cd

cd <dirname>

Changes this process's working directory to the given dirname.

cd workdir

pwd

pwd

Returns the current working directory.

set orig_dir [pwd]
...
cd $orig_dir

argv/argc

Command-line options are stored in global variables:

(if you want the name of the tcl interpreter, instead of the name of the Tcl script being executed, use 'info nameofexecutable'. That gives you the Tcl process's $0.)

ENV settings

Environment variables are read and written through the global array $::env.

puts "Hello, $env(USER)!"

clock seconds

clock seconds

Returns the number of seconds since 1/1/70.

set start_time [clock seconds]
...
set end_time [clock seconds]
puts "... took [expr $end_time - $start_time] seconds"

clock format

clock format <value>

Formats the given time, to select which fields are shown, and how.

set curr_time [clock seconds]
set file_time [file mtime "foo.txt"]
if { file_time < curr_time - 300 } {
  puts "foo.txt modified more than 5 minutes ago"
  puts "current time: [clock format $curr_time]"
  puts "file modified: [clock format $file_time]"
}

clock scan

clock scan <string>

Converts human-readable time to the number of seconds since 1/1/70.

set curr_time [clock seconds]
set pretty_string [clock format $curr_time]
if { [clock scan $pretty_string] != $curr_time } {
  puts "ERROR!  'clock scan' didn't get the original value!"
}

exceptions (and other errors)

Historically there have been three common ways for procedures to indicate errors:

Tcl uses all three. Whee!

The return-code method is straightforward, so there's nothing new to cover there.

The global variables for Tcl errors are $::errorCode (which is a number) and $::errorInfo (which is a string describing the error). Only some functions set these, which means it's best not to rely on them, but it's good to know they exist.

Tcl's documentation bends over backwards to avoid using the word "exception", though they do slip up a couple times. In Tcl parlance, throwing an exception is called "generating an error".

error

error <string> [<errcode> [<info>]?]?

Throws an exception with the given text. If you provide errcode and/or info, the global variables $::errorCode and $::errorInfo are set.

if { $val < 0 } {
  error "negative value!  bad!  ugga!"
}

catch

catch <code> [<varname>]?

Catches any exceptions thrown in code. If you give a varname, and code executes without an error, the last value of code is put into it, and catch returns 0. If code executes with an error, catch returns 1.

if {[catch { set fh [open "foo.txt"] } errmsg ] } {
  puts "ERROR> could not open 'foo.txt': $errmsg"
}
Note that if there is no exception, then errmsg above would not be defined, so trying to use it would cause a runtime exception. On the other hand, if the open did throw an exception, then fh would not be defined, so trying to use it would cause a runtime exception.

Have fun! :)

return

return [-errorcode <num>] [-errorinfo <string>] [<value>]?

The return function actually lets you generate an exception at the same time as returning a value.

if { $val < 0 } {
  return -errorcode -1 -errorinfo "negative value!  bad!  ugga!"
}

Debugging

error messages

Tcl's error messages are horrible. They are essentially stack traces, but with relative line numbers. Here's the example from tcl.tk, showing the error that occurs when you call a function that doesn't exist:

input file

1  proc a {} {
2      b
3  }
4  proc b {} {
5      c
6  }
7  proc c {} {
8      d
9  }
10 proc d {} {
11   some_command
12 }
13
14 a

execution output, with snarky annotations:

invalid command name "some_command"     <= the actual error is first, at least
    while executing
"some_command"
    (procedure "d" line 2)     <= line 2 of 'd' is actually line 11
    invoked from within
"d"
    (procedure "c" line 2)     <= line 2 of 'c' is actually line 8
    invoked from within
"c"
    (procedure "b" line 2)     <= line 2 of 'b' is actually line 5
    invoked from within
"b"
    (procedure "a" line 2)     <= line 2 of 'a' is actually line 2..by pure coincidence.
    invoked from within
"a"
    (file "errors.tcl" line 14)   <= note that line 14 isn't where the problem was

The way to read the error message is:

Of all the crappy parts of Tcl, compiler error reporting is, hands-down, Tcl's weakest link.

trace

There's a trace command that will let you watch variables and commands. It's not quite a full debugger (it doesn't let you step through execution or anything), but if you're interested you should check out the man page for it. And possibly the official tutorial page as well.

profiling

Tcl has a built in function for timing how long a block of code takes to run:

time

time <code> [<count>]?

Runs code, count times, and returns the average amount of (wall) time it took on each run.

set avg_time [time { exec "/bin/ls" } 100]
puts "ls takes about $avg_time seconds per run"

sockets

It seems hard to believe, but Tcl -- the scripting language world's answer to assembly language -- has direct support for networking sockets. Which I'm definitely not going into detail about - go see the tutorial.

But if you're grepping this file for function names, here are the ones of interest:


Chris verBurg
2014-07-22