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:
- a list
- 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 firstreturns the first location of a string in a stringstring lastreturns the last location of a string in a stringstring lengthreturns the length of a stringstring indexreturns the character at given indexstring rangeinclusively returns a range of charactersstring trimleft,string trimright, orstring trimtrims 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
appendis much more efficient than usingsetto append to a list. - It also updates the original list in-place, like
lappend. - You can use
appendto 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 dirnamereturns the directory part of the file path (e.g.,/my/path/in/my/path/file.txt)file tailreturns the filename part of the file path (e.g.,file.txt)file extensionreturns the file extension (e.g.,.txt)file rootnamereturns 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!