Emacs, scripting and anything text oriented.

Tcl

Kaushal Modi

Notes as I am learning Tcl syntax using its official tutorial.

Table of Contents

tclsh version #

puts $tcl_version
8.6

Simple Text Output #

  • If a string has more than one word (i.e. has space), it must be enclosed in " " or { }.

    puts "Hello, World - In Double Quotes"
    puts {Hello, World - In Braces}

    Note that single quotes have no significance in Tcl.

    Hello, World - In Double Quotes
    Hello, World - In Braces
  • If the string has no space, the quotes or braces are not needed.

    puts Hello
    Hello
  • This is how you normally type a comment

    # This is a comment at beginning of a line
  • A Tcl command is terminated by a newline or a semicolon.

    puts "This is line 1"
    puts "this is line 2"
    puts "This is line 3"; puts "this is line 4"
    puts "Hello, World; - With  a semicolon inside the quotes"
    This is line 1
    this is line 2
    This is line 3
    this is line 4
    Hello, World; - With  a semicolon inside the quotes
    • The same “semicolon ends a command” applies when ending a command and starting a comment on the same line.

      puts "Hello, World - In quotes"    ;# This is a comment after the command.
      Hello, World - In quotes
    • Below will not work as there is no semicolon separating the command and the comment.

      puts {Bad comment syntax example}   # *Error* - there is no semicolon!

Assigning values to variables #

set assigns a value to a variable and then also returns the same.

set fruit Cauliflower
Cauliflower
set X "This is a string"

set Y 1.24

puts $X
puts $Y

puts "..............................."

set label "The value in Y is: "
puts "$label $Y"
This is a string
1.24
...............................
The value in Y is:  1.24

The dollar sign tells Tcl to use the value of the variable - in this case X or Y.

Evaluation & Substitutions 1: Grouping arguments with "" #

Grouping words within double quotes allows substitutions to occur within the quotations - or, in fancier terms, “interpolation”. The substituted group is then evaluated as a single argument.

In general, the backslash (\) disables substitution for the single character immediately following the backslash. Any character immediately following the backslash will stand without substitution.

However, there are specific “Backslash Sequence” strings which are replaced by specific values during the substitution phase.

puts "abc\n\tdef \u0A95"
abc
	def ક
set Z Albany
set Z_LABEL "The Capital of New York is: "

puts "$Z_LABEL $Z"   ;# Prints the value of Z
puts "$Z_LABEL \$Z"  ;# Prints a literal $Z instead of the value of Z

puts "\nBen Franklin is on the \$100.00 bill"

set a 100.00
puts "Washington is not on the $a bill"    ;# This is not what you want
puts "Lincoln is not on the $$a bill"      ;# This is OK
puts "Hamilton is not on the \$a bill"     ;# This is not what you want
puts "Ben Franklin is on the \$$a bill"    ;# But, this is OK

puts "\n................. examples of escape strings"
puts "Tab\tTab\tTab"
puts "This string prints out \non two lines"
puts "This string comes out\
on a single line"
The Capital of New York is:  Albany
The Capital of New York is:  $Z

Ben Franklin is on the $100.00 bill
Washington is not on the 100.00 bill
Lincoln is not on the $100.00 bill
Hamilton is not on the $a bill
Ben Franklin is on the $100.00 bill

................. examples of escape strings
Tab	Tab	Tab
This string prints out
on two lines
This string comes out on a single line

Evaluation & Substitutions 2: Grouping arguments with {} #

In contrast to words grouped in double quotes, no substitution happens in words grouped in curly braces.

set Z Albany
set Z_LABEL "The Capital of New York is: "

puts "\n................. examples of differences between  \" and \{"
puts "grouped in double quotes: $Z_LABEL $Z"
puts {grouped in braces: $Z_LABEL $Z}

puts "\n....... examples of differences in nesting \{ and \" "
puts "braces in double quotes: $Z_LABEL {$Z}"
puts {double quotes in braces: Who said, "What this country needs is a good $0.05 cigar!"?}

puts "\n................. examples of escape strings"
puts {There are no substitutions done within braces \n \r \x0a \f \v}
puts {But, the escaped newline at the end of a\
string is still evaluated as a space}
................. examples of differences between  " and {
grouped in double quotes: The Capital of New York is:  Albany
grouped in braces: $Z_LABEL $Z

....... examples of differences in nesting { and "
braces in double quotes: The Capital of New York is:  {Albany}
double quotes in braces: Who said, "What this country needs is a good $0.05 cigar!"?

................. examples of escape strings
There are no substitutions done within braces \n \r \x0a \f \v
But, the escaped newline at the end of a string is still evaluated as a space

Evaluation & Substitutions 3: Grouping arguments with [] #

You obtain the results of a command by placing the command in square brackets ([]). This is the functional equivalent of the back single quote (`) in shell scripting.

As the Tcl interpreter reads in a line it replaces all the $variables with their values. If a portion of the string is grouped with square brackets, then the string within the square brackets is evaluated as a command by the interpreter, and the result of the command replaces the square bracketed string.

Except ..

  • A square bracket that is escaped with a \ is considered as a literal square bracket.
  • A square bracket within braces is not modified during the substitution phase.
set x abc
puts "A simple substitution: $x\n"

set y [set x "def"]
puts "Remember that set returns the new value of the variable: X: $x Y: $y\n"

set z {[set x "This is a string within quotes within square brackets withing braces"]}
puts "Note that the curly braces prevented evaluation of the string in square brackets: $z\n"

set a "[set x {This is a string within braces within square brackets within double quotes}]"
puts "See how the set is executed: $a"
puts "\$x is: $x\n"

set b "\[set y {This is a string within braces beginning with an escaped square bracket within quotes}]"
# Note the \ escapes the bracket, and must be doubled to be a
# literal character in double quotes
puts "Note the \\ escapes the bracket:\n \$b is: $b"
puts "\$y is still \"$y\" from the first assignment"
A simple substitution: abc

Remember that set returns the new value of the variable: X: def Y: def

Note that the curly braces prevented evaluation of the string in square brackets: [set x "This is a string within quotes within square brackets withing braces"]

See how the set is executed: This is a string within braces within square brackets within double quotes
$x is: This is a string within braces within square brackets within double quotes

Note the \ escapes the bracket:
 $b is: [set y {This is a string within braces beginning with an escaped square bracket within quotes}]
$y is still "def" from the first assignment

Results of a command - Math 101 #

The Tcl command for doing math type operations is expr.

It’s recommended to enclose expr arguments in curly braces – It is faster, and also more secure. So do expr {$i * 10} instead of simply expr $i * 10.

The expr command performs its own round of substitutions on variables and commands, so you should use braces to prevent the Tcl interpreter doing this as well (leading to double substitution). In the below example, the puts in $userinput is evaluated when evaluating expr, which very well might be unintended.

set userinput {[puts DANGER!]}
set foo [expr $userinput == 1]
puts $foo
DANGER!
0

Below as the expr arguments are wrapped in { }, the “DANGER!” is avoided.

set userinput {[puts DANGER!]}
set foo [expr {$userinput == 1}]
puts $foo
0

TODO Operands #

Math Functions #

Tcl supports the following mathematical functions in expressions:

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
set x 1
set w "Abcdef"
expr { [string length $w] - 2*$x }
4

Type Conversions #

FunctionDescription
double()Convert to a float
int()Convert to an ordinary integer using truncation
wide()Convert to a “wide” integer number
entier()Coerses a number to an integer of appropriate size to hold it without truncation.
set X 100
set Y 256
set Z [expr {$Y + $X}]
set Z_LABEL "$Y plus $X is "

puts "$Z_LABEL $Z"
puts "The square root of $Y is [expr { sqrt($Y) }]\n"

puts "Because of the precedence rules \"5 + -3 * 4\"   is: [expr {-3 * 4 + 5}]"
puts "Because of the parentheses      \"(5 + -3) * 4\" is: [expr {(5 + -3) * 4}]"

set A 3
set B 4
puts "The hypotenuse of a triangle: [expr {hypot($A,$B)}]"

#
# The trigonometric functions work with radians ...
#
set pi6 [expr {3.1415926/6.0}]
puts "The sine and cosine of pi/6: [expr {sin($pi6)}] [expr {cos($pi6)}]"
256 plus 100 is  356
The square root of 256 is 16.0

Because of the precedence rules "5 + -3 * 4"   is: -7
Because of the parentheses      "(5 + -3) * 4" is: 8
The hypotenuse of a triangle: 5.0
The sine and cosine of pi/6: 0.49999999226497965 0.8660254082502546
# Working with arrays
set a(1) 10
set a(2) 7
set a(3) 17
set b    2
puts "Sum: [expr {$a(1)+$a($b)}]"
Sum: 17

Computers and Numbers #

# Division
puts "1/2 is [expr {1/2}]"
puts "-1/2 is [expr {-1/2}]"
puts "1/2 is [expr {1./2}]"
puts "1/3 is [expr {1./3}]"
puts "1/3 is [expr {double(1)/3}]"
1/2 is 0
-1/2 is -1
1/2 is 0.5
1/3 is 0.3333333333333333
1/3 is 0.3333333333333333
set tcl_precision 17  ;# One of Tcl's few magic variables:
                      ;# Show all decimals needed to exactly
                      ;# reproduce a particular number
puts "1/2 is [expr {1./2}]"
puts "1/3 is [expr {1./3}]"

set a [expr {1.0/3.0}]
puts "3*(1/3) is [expr {3.0*$a}]"

set b [expr {10.0/3.0}]
puts "3*(10/3) is [expr {3.0*$b}]"

set c [expr {10.0/3.0}]
set d [expr {2.0/3.0}]
puts "(10.0/3.0) / (2.0/3.0) is [expr {$c/$d}]"

set e [expr {1.0/10.0}]
puts "1.2 / 0.1 is [expr {1.2/$e}]"
1/2 is 0.5
1/3 is 0.33333333333333331
3*(1/3) is 1.0
3*(10/3) is 10.0
(10.0/3.0) / (2.0/3.0) is 5.0000000000000009
1.2 / 0.1 is 11.999999999999998

Numeric Comparisons 101 - if #

Put the if test condition in curly braces.

set x 1

if {$x == 2} {puts "$x is 2"} else {puts "$x is not 2"}

if {$x != 1} {
    puts "$x is != 1"
} else {
    puts "$x is 1"
}

if $x==1 {puts "GOT 1"}
1 is not 2
1 is 1
GOT 1
set x 1
#
# Be careful, this is just an example
# Usually you should avoid such constructs,
# it is less than clear what is going on and it can be dangerous
#
set y x
if "$$y != 1" {
    puts "$$y is != 1"
} else {
    puts "$$y is 1"
}

#
# A dangerous example: due to the extra round of substitution,
# the script stops
#
set y {[exit]}
if "$$y != 1" {
    puts "$$y is != 1"
} else {
    puts "$$y is 1"
}
$x is 1
  • For numbers, any non-zero value is a TRUE expression.
  • For strings, "yes" and "true" is a TRUE expression.
set str1 yes
if { $str1} { puts "yep" } else { puts "nope" }
set str2 true
if { $str2 } { puts "yep" } else { puts "nope" }
yep
yep

TODO Textual Comparison - switch #

Looping 101 - while loop #

In Tcl everything is a command, and everything goes through the same substitution phase. For this reason, the test must be placed within braces.

set x 1

while {$x < 5} { ;# Notice the while test enclosed in curly braces
  puts "x is $x"
  set x [expr {$x + 1}]
}

puts "exited first loop with X equal to $x"
x is 1
x is 2
x is 3
x is 4
exited first loop with X equal to 5

The next example shows the difference between ".." and {..}.

set x 0
while "$x < 5" {  ;# This test is first evaluated and transformed to "0 < 5"
                  ;# .. which will always be true!
  set x [expr {$x + 1}]
  if {$x > 7} break    ;# This while loop would have run infinitely without this break
  if "$x > 3" continue ;# the while loop short-circuts back to the top for x>3
  puts "x is $x"
}

puts "exited second loop with X equal to $x"
x is 1
x is 2
x is 3
exited second loop with X equal to 8

Looping 102 - for and incr #

Put the for test condition in curly braces.

When braces are used for grouping, the newline is not treated as the end of a Tcl command. This makes it simpler to write multiple line commands. However, the opening brace must be on the line with the for command.

for {set i 0} {$i < 4} {incr i} {
  puts "I'm inside the first loop: $i"
}

for {set i 3} {$i < 2} {incr i} {
  puts "I'm inside the second loop: $i"
}
I'm inside the first loop: 0
I'm inside the first loop: 1
I'm inside the first loop: 2
I'm inside the first loop: 3

Below, a while loop is written similar to the first for loop above:

puts "Start"
set i 0
while {$i < 4} {
  puts "I'm inside the while loop: $i"
  incr i
  puts "I'm after incr: $i"
}
Start
I'm inside the while loop: 0
I'm after incr: 1
I'm inside the while loop: 1
I'm after incr: 2
I'm inside the while loop: 2
I'm after incr: 3
I'm inside the while loop: 3
I'm after incr: 4
set i 0
incr i                      ;# now i is 1
# This is equivalent to:
set i [expr {$i + 1}]       ;# now i is 2
incr i 3                    ;# now i is 5
5

Adding new commands to Tcl - proc #

In Tcl there is actually no distinction between commands (often known as ‘functions’ in other languages) and “syntax”. There are no reserved words (like if and while) as exist in C, Java, Python, Perl, etc.

So below works (but please don’t code like that!)!

set xxx set
$xxx foo if
$xxx bar puts
$xxx zoo else
$foo { true } { $bar "yep" } $zoo { $bar "nope" }
yep
proc sum {arg1 arg2} {
  set x [expr {$arg1 + $arg2}]
  return $x
}

puts " The sum of 2 + 3 is: [sum 2 3]"
 The sum of 2 + 3 is: 5

In the below example, the original for command has been destroyed to do something else.

proc for {a b c} {
  puts "The for command has been replaced by puts commands!"
  puts "The arguments were:\n\t$a\n\t$b\n\t$c"
}

for {set i 1} {$i < 10} {incr i}
The for command has been replaced by puts commands!
The arguments were:
	set i 1
	$i < 10
	incr i

Variations in proc arguments and return values #

A proc can be defined with a set number of required arguments (as was done with sum in the previous section, or it can have a variable number of arguments. An argument can also be defined to have a default value.

Variables can be defined with a default value by placing the variable name and the default within braces within args. For example:

proc justdoit {a {b 1} {c -1}} {
  puts "a = $a, b = $b, c = $c"
}
justdoit 10
justdoit 10 20
justdoit 10 20 30
a = 10, b = 1, c = -1
a = 10, b = 20, c = -1
a = 10, b = 20, c = 30

A proc will accept a variable number of arguments if the last declared argument is the word args.

proc example {first {second ""} args} {
  if {$second eq ""} {
    puts "There is only one argument and it is: $first"
    return 1
  } else {
    if {$args eq ""} {
      puts "There are two arguments - $first and $second"
      return 2
    } else {
      puts "There are many arguments - $first, $second and $args"
      return "many"
    }
  }
}

set count1 [example ONE]
set count2 [example ONE TWO]
set count3 [example ONE TWO THREE ]
set count4 [example ONE TWO THREE FOUR]

puts "The example was called with $count1, $count2, $count3, and $count4 Arguments"
There is only one argument and it is: ONE
There are two arguments - ONE and TWO
There are many arguments - ONE, TWO and THREE
There are many arguments - ONE, TWO and THREE FOUR
The example was called with 1, 2, many, and many Arguments

Using foreach to loop through all the args of a proc:

proc my_proc {args} {
  puts "Number of args = [llength $args]"
  set idx 0
  foreach arg $args {
    puts "  arg $idx = $arg"
    incr idx
  }
}
my_proc x
my_proc a b c d e f
Code Snippet 1: Looping though a proc's args using foreach
Number of args = 1
  arg 0 = x
Number of args = 6
  arg 0 = a
  arg 1 = b
  arg 2 = c
  arg 3 = d
  arg 4 = e
  arg 5 = f

TODO Variable scope - global and upvar #

Tcl Data Structures 101 - The list #

The list is the basic Tcl data structure. A list is simply an ordered collection of stuff; numbers, words, strings, or other lists.

set x "a b c" ;# Yes, Tcl interprets this string as a list too.
puts "Item at index 2 of the list {$x} is: [lindex $x 2]\n"

set i 0
foreach j $x {
  puts "$j is item number $i in list x"
  incr i
}
Item at index 2 of the list {a b c} is: c

a is item number 0 in list x
b is item number 1 in list x
c is item number 2 in list x
lindex lst idx
Returns the item at idx index from the lst list. Note that the list indices begin from 0.
llength lst
Returns the length of lst list.
set y [split 7/4/1776 "/"]
puts "We celebrate on the [lindex $y 1]'th day of the [lindex $y 0]'th month"

set z [list puts "arg 2 is $y" ]
puts "A command resembles: $z"
puts "Length of \$z list = [llength $z]"
eval $z
We celebrate on the 4'th day of the 7'th month
A command resembles: puts {arg 2 is 7 4 1776}
Length of $z list = 2
arg 2 is 7 4 1776

proc my_proc {args} {
  puts "Number of args = [llength $args]"
  for {set idx 0} {$idx < [llength $args]} {incr idx} {
    puts "  arg $idx = [lindex $args $idx]"
  }
}
my_proc x
my_proc a b c d e f
Code Snippet 2: Looping though a proc's args using for
Number of args = 1
  arg 0 = x
Number of args = 6
  arg 0 = a
  arg 1 = b
  arg 2 = c
  arg 3 = d
  arg 4 = e
  arg 5 = f

foreach can be used to take more than one variable at a time from a list:

set x "a b c d e f"
foreach {var1 var2} $x {
  puts "var1 = $var1, var2 = $var2"
}
foreach {var1 var2 var3} $x {
  puts "var1 = $var1, var2 = $var2, var3 = $var3"
}
var1 = a, var2 = b
var1 = c, var2 = d
var1 = e, var2 = f
var1 = a, var2 = b, var3 = c
var1 = d, var2 = e, var3 = f

foreach can even take a variable at a time from multiple lists:

set x "a b c"
set y "A B C"
set z "1 2 3"
foreach foo $x bar $y zoo $z {
  puts "$foo $bar $zoo"
}
a A 1
b B 2
c C 3

TODO Adding & Deleting members of a list #

set b [list a b {c d e} {f {g h}}]
puts "Treated as a list: $b\n"

set b [split "a b {c d e} {f {g h}}"]
puts "Transformed by split: $b\n"

set a [concat a b {c d e} {f {g h}}]
puts "Concated: $a\n"

lappend a {ij K lm}                        ;# Note: {ij K lm} is a single element
puts "After lappending: $a\n"

set b [linsert $a 3 "1 2 3"]               ;# "1 2 3" is a single element
puts "After linsert at position 3: $b\n"

set b [lreplace $b 3 5 "AA" "BB"]
puts "After lreplacing 3 positions with 2 values at position 3: $b\n"
Treated as a list: a b {c d e} {f {g h}}

Transformed by split: a b \{c d e\} \{f \{g h\}\}

Concated: a b c d e f {g h}

After lappending: a b c d e f {g h} {ij K lm}

After linsert at position 3: a b c {1 2 3} d e f {g h} {ij K lm}

After lreplacing 3 positions with 2 values at position 3: a b c AA BB f {g h} {ij K lm}

TODO More list commands - lsearch, lsort, lrange #

TODO Simple pattern matching - “globbing” #

TODO String Subcommands - length index range #

TODO String comparisons - compare match first last wordend #

TODO Modifying Strings - tolower, toupper, trim, format #

Regular Expressions 101 #

set sample "Where there is a will, There is a way."

#
# Match the first substring with lowercase letters only
#
set result [regexp {[a-z]+} $sample match]
puts "Result: $result match: $match"

#
# Match the first two words, the first one allows uppercase
set result [regexp {([A-Za-z]+) +([a-z]+)} $sample match sub1 sub2 ]
puts "Result: $result Match: $match 1: $sub1 2: $sub2"

#
# Replace a word
#
regsub "way" $sample "lawsuit" sample2
puts "New: $sample2"

#
# Use the -all option to count the number of "words"
#
puts "Number of words: [regexp -all {[^ ]+} $sample]"
Result: 1 match: here
Result: 1 Match: Where there 1: Where 2: there
New: Where there is a will, There is a lawsuit.
Number of words: 9
proc get_type {str} {
  ## Returns the string parsed between "_" and "_result_1234.log".
  set result [regexp {_([a-z]+)_result_[0-9]+\.log} $str match sub1]
  puts [format "%s -> match=%s, sub1=%s" $str $match $sub1]
  return $sub1
}
puts [get_type "foo_test_bar_aaa_bar_result_123.log"]
puts [get_type "foo_test_bar_aaa_zoo_result_123.log"]
foo_test_bar_aaa_bar_result_123.log -> match=_bar_result_123.log, sub1=bar
bar
foo_test_bar_aaa_zoo_result_123.log -> match=_zoo_result_123.log, sub1=zoo
zoo

TODO More Examples Of Regular Expressions #

String replacement using regular expressions #

set str "foo1foo2foo3"
puts "str = $str"
set str [regsub -all foo $str bar]
puts "str = $str"
regsub -all bar $str zoo str
puts "str = $str"
set numMatches [regsub -all zoo $str tar str]
puts "str = $str, numMatches = $numMatches"
str = foo1foo2foo3
str = bar1bar2bar3
str = zoo1zoo2zoo3
str = tar1tar2tar3, numMatches = 3

TODO More Quoting Hell - Regular Expressions 102 #

TODO Associative Arrays #

TODO More On Arrays - Iterating and use in procedures #

TODO Dictionaries #

TODO File Access 101 #

TODO Information about Files - file, glob #

TODO Invoking Subprocesses from Tcl - exec, open #

TODO Learning the existence of commands and variables ? - info #

TODO State of the interpreter - info #

TODO Information about procs - info #

TODO Modularization - source #

TODO Building reusable libraries - packages and namespaces #

Below snippet shows that just declaring a variable in a namespace does not cause its info exists status to become true – It needs to be explicitly set to a value.

namespace eval ::foo {
  variable bar
}
puts [info exists ::foo::bar]
if {![info exists ::foo::bar]} { puts empty }
set ::foo::bar zoo
puts [info exists ::foo::bar]
0
empty
1

TODO Creating Commands - eval #

TODO More command construction - format, list #

TODO Substitution without evaluation - format, subst #

TODO Changing Working Directory - cd, pwd #

TODO Debugging & Errors - errorInfo errorCode catch error return #

TODO More Debugging - trace #

Command line arguments and environment strings #

  • The number of command line arguments to a Tcl script is passed as the global variable argc.
  • The name of a Tcl script is passed to the script as the global variable argv0.
  • The rest of the command line arguments are passed as a list in argv.
puts "There are $argc arguments to this script"
puts "The name of this script is $argv0"
if {$argc > 0} {puts "The other arguments are: $argv" }
There are 0 arguments to this script
The name of this script is tclsh

Below outputs the full path of the executable that runs the Tcl scripts; usually the path to tclsh:

puts [info nameofexecutable]

Environment variables are available in Tcl via a global associative array env. The index into env is the name of the environment variable.

Below snippet dumps the names and values of all the set environment variables:

puts "You have these environment variables set:"
foreach index [array names env] {
  puts "$index: $env($index)"
}
puts $env(EDITOR)

if {[info exists env(FOO)]} { ;# https://stackoverflow.com/a/7712763/1219634
  puts "env var FOO is set"
} else {
  puts "env var FOO is not set"
  set env(FOO) abc
  if {[info exists env(FOO)]} {
    puts "Now env var FOO is set"
  }
}
puts $env(FOO)
emacsclient
env var FOO is not set
Now env var FOO is set
abc

TODO Leftovers - time, unset #

TODO Channel I/O: socket, fileevent, vwait #

TODO Time and Date - clock #

TODO More channel I/O - fblocked & fconfigure #

TODO Child interpreters #

Reference #