Lists (Continued)

Appending to lists is simple with lappend:

Note: lappend takes just the name of the list. It updates the specified list, so you do not have to use set.

% set list {this is {a list}}
this is {a list}
% lappend list {more stuff here}
this is {a list} {more stuff here}
% puts $list
this is {a list} {more stuff here}

Inserting into a list is done with linsert. It is zero-indexed:

% set list [linsert $list 3 {of stuff with}]
this is {a list} {of stuff with} {more stuff here}
% puts $list
this is {a list} {of stuff with} {more stuff here}


split accepts:

  • a variable
  • a string of characters to split on


% set url ""
% split $url ": / ."
https {} {} www tcl tk man tcl8 4 TclCmd lsearch htm

Be sure to save the results of the split to a variable:

% puts $url
% set url_split [split $url ": . /"]
https {} {} www tcl tk man tcl8 4 TclCmd lsearch htm
% puts $url_split
https {} {} www tcl tk man tcl8 4 TclCmd lsearch htm


Join accepts two arguments:

  1. a list
  2. the character to join the list elements with
% set mylist {This is a list {of strings} . Yay!}
This is a list {of strings} . Yay!
% join $mylist "." strings...Yay!
% join $mylist " "
This is a list of strings . Yay!


When using string compare or string match, it may be useful to use -nocase

% set s1 "This is a string"
This is a string
% set s2 "this is a string"
this is a string
% string compare $s1 $s2
% string compare -nocase $s1 $s2

… or string tolower:

% string compare [string tolower $s1] [string tolower $s2]
% string match [string tolower $s1] [string tolower $s2]

Other useful string commands:

  • string first returns the first location of a string in a string
  • string last returns the last location of a string in a string
  • string length returns the length of a string
  • string index returns the character at given index
  • string range inclusively returns a range of characters
  • string trimleft, string trimright, or string trim trims leading or trailing characters (or both). If no characters are specified, then trims whitespace


append appends one string to another in place:

% set lorem "Lorem ipsum"
Lorem ipsum
% append lorem " dolor sit amet"
Lorem ipsum dolor sit amet
% puts $lorem
Lorem ipsum dolor sit amet


  • Using append is much more efficient than using set to append to a list.
  • It also updates the original list in-place, like lappend.
  • You can use append to set a brand new variable.


Comments are specified with #.

You can write an in-line comments with ;#. ; ends the line and # starts the command.


% set s "this" ;# Look, ma, a comment!

Check whether a variable exists

You can check whether a variable exists with: info exists <var_name> It will return a 1 when the variable exists, otherwise 0.

% info exists hello
% set hello "world"
% info exists hello


There are several useful file commands:

  • file dirname returns the directory part of the file path (e.g., /my/path/ in /my/path/file.txt)
  • file tail returns the filename part of the file path (e.g., file.txt)
  • file extension returns the file extension (e.g., .txt)
  • file rootname returns everything except the extension (e.g., /my/path/file)

Tip: When constructing arbitrary compound names, use dots and slashes so you can use the file commands.


% set ip_addr ""
% set ip_addr [file rootname $ip_addr].20

You can query info about a file using several different file commands:

  • file isdirectory filename - is the file a directory?
  • file isfile filename - is the file a file?
  • file executable filename - is the file executable?
  • And more!

Exec (super powers!)

You can run Unix/Linux commands with exec. You can use operators like <, >, |, and &.

Use whitespace before and after redirection symbols!

This redirection fails due to lack of whitespace:

% exec echo "This is bad redirection!">/root/blah.txt
extra characters after close-quote
% exec cat /root/blah.txt
cat: /root/blah.txt: No such file or directory

These redirections work because of proper whitespace:

% exec echo "This is good redirection!" > /root/blah.txt
% exec cat /root/blah.txt
This is good redirection!
% exec cat < /root/blah.txt
This is good redirection!

Note to self: cmd < file redirects the contents of file to stdin of cmd. I always find this one super confusing!

Unless you redirect stdout, exec returns the result. So you can save it to a variable and do fun stuff with it later.

% puts "Look, ma, it's [exec date]!"
Look, ma, it's Sat Feb 23 00:22:21 UTC 2019!
% set date [exec date]
Sat Feb 23 00:22:40 UTC 2019
% puts "Pa, it's $date!"
Pa, it's Sat Feb 23 00:22:40 UTC 2019!

TCL assumes that programs called with exec will exit with a 0 return code if they are successful.

This can lead to interesting results with programs do not conform to this standard:

TCL thinks there are no problems:

% exec true

TCL thinks there are problems and you can catch them with catch!:

% exec false
child process exited abnormally
% catch {exec false}

The unknown proc

TCL calls the unknown function when a command which is not known to the TCL interpreter is called.

Here’s an example of displaying a custom error message by definining the unknown function.

% set a [1+1]
invalid command name "1+1"
% proc unknown {args} {
      puts "WHAT YOU DOING?!"
% set a [1+1]


I wrote a proc which takes a string as input and spits it out in reverse order:

proc rev {args} {
    puts $args
    set mystring [join $args ""]

    for {set count [string length $mystring]} {$count >= 0} {incr count -1} {
        puts [string index $mystring $count]


Same thing, but with a list:

% proc revl {args} {
    puts "args: $args"
    puts "reversed: [lreverse $args]"

Just kidding… that’s cheating!

% proc revl {args} {
    puts "args: $args"
    for {set count [llength $args]} {$count >= 0} {incr count -1} {
        puts "Iteration: [lindex $args $count]"
% revl a b c
args: a b c
Iteration: c
Iteration: b
Iteration: a

And here’s a proc which will zip together two lists: a list of names and a list of values. If the list of names is longer thanthe list of values, it prints “Who knows?” instead of a value.

% proc zipper_list {names values} {
    set count 0
    foreach name $names {
        if {$count < [llength $values]} {
            puts "$name: [lindex $values $count]"
        } else {
            puts "$name: Who knows?"
        incr count
% zipper_list {alice bob eve john} {31 33 45}
alice: 31
bob: 33
eve: 45
john: Who knows?


So, what have I learned with my little adventure into TCL land? Well, for one that TCL is not as complex as I had made it out to be. It actually makes quite a bit a sense (except for some quicks like append being passed a variable and not the variable itself).

I am also much more comfortable iterating over various data structures as well. Which is a good thing, because I found it somewhat daunting before!

All in all I’m very glad I did this exercise and wrote it down. I’ve already referred to it several times at work!