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

split accepts:

  • a variable
  • a string of characters to split on

Example:

% set url "https://www.tcl.tk/man/tcl8.4/TclCmd/lsearch.htm"
https://www.tcl.tk/man/tcl8.4/TclCmd/lsearch.htm
% 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
https://www.tcl.tk/man/tcl8.4/TclCmd/lsearch.htm
% 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

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 "."
This.is.a.list.of strings...Yay!
% join $mylist " "
This is a list of strings . Yay!
%

String

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
-1
% string compare -nocase $s1 $s2
0

… or string tolower:

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

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

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
%

Note:

  • 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

Comments are specified with #.

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

Example:

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

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
0
% set hello "world"
world
% info exists hello
1
%

File

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.

Example:

% set ip_addr "10.10.10.10"
10.10.10.10
% set ip_addr [file rootname $ip_addr].20
10.10.10.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}
1
%

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]
WHAT YOU DOING?!
%

Exercises

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:
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?
%

Summary

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!