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 first
returns the first location of a string in a stringstring last
returns the last location of a string in a stringstring length
returns the length of a stringstring index
returns the character at given indexstring range
inclusively returns a range of charactersstring trimleft
,string trimright
, orstring 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 usingset
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!