My path to Tcl started with a recent need to automate a difficult Java-based command-line configuration utility. I do a bit of automation programming using Ansible, and I occasionally use the expect module. Frankly, I find this module has limited utility for a number of reasons including: difficulty with sequencing identical prompts, capturing values for use in additional steps, limited flexibility with control logic, and so on. Sometimes you can get away with using the shell module instead. But sometimes you hit that ill-behaving and overly complicated command-line interface that seems impossible to automate.
In my case, I was automating the installation of one of my company's programs. The last configuration step could only be done through the command-line, through several ill-formed, repeating prompts and data output that needed capturing. The good old traditional Expect was the only answer. A deep understanding of Tcl is not necessary to use the basics of Expect, but the more you know, the more power you can get from it. This is a topic for a follow-up article. For now, I explore the basic language constructs of Tcl, which include user input, output, variables, conditional evaluation, looping, and simple functions.
Install Tcl
On a Linux system, I use this:
# dnf install tcl
# which tclsh
/bin/tclsh
On macOS, you can use Homebrew to install the latest Tcl:
$ brew install tcl-tk
$ which tclsh
/usr/local/bin/tclsh
Guess the number in Tcl
Start by creating the basic executable script numgame.tcl
:
$ touch numgame.tcl
$ chmod 755 numgame.tcl
And then start coding in your file headed up by the usual shebang script header:
#!/usr/bin/tclsh
Here are a few quick words about artifacts of Tcl to track along with this article.
The first point is that all of Tcl is considered a series of strings. Variables are generally treated as strings but can switch types and internal representations automatically (something you generally have no visibility into). Functions may interpret their string arguments as numbers ( expr
) and are only passed in by value. Strings are usually delineated using double quotes or curly braces. Double quotes allow for variable expansion and escape sequences, and curly braces impose no expansion at all.
The next point is that Tcl statements can be separated by semicolons but usually are not. Statement lines can be split using the backslash character. However, it's typical to enclose multiline statements within curly braces to avoid needing this. Curly braces are just simpler, and the code formatting below reflects this. Curly braces allow for deferred evaluation of strings. A value is passed to a function before Tcl does variable substitution.
Finally, Tcl uses square brackets for command substitution. Anything between the square brackets is sent to a new recursive invocation of the Tcl interpreter for evaluation. This is handy for calling functions in the middle of expressions or for generating parameters for functions.
Procedures
Although not necessary for this game, I start with an example of defining a function in Tcl that you can use later:
proc used_time {start} {
return [expr [clock seconds] - $start]
}
Using proc
sets this up to be a function (or procedure) definition. Next comes the name of the function. This is then followed by a list containing the parameters; in this case 1 parameter {start}
and then followed by the function body. Note that the body curly brace starts on this line, it cannot be on the following line. The function returns a value. The returned value is a compound evaluation (square braces) that starts by reading the system clock [clock seconds]
and does the math to subtract out the $start
parameter.
Setup, logic, and finish
You can add more details to the rest of this game with some initial setup, iterating over the player's guesses, and then printing results when completed:
set num [expr round(rand()*100)]
set starttime [clock seconds]
set guess -1
set count 0
puts "Guess a number between 1 and 100"
while { $guess != $num } {
incr count
puts -nonewline "==> "
flush stdout
gets stdin guess
if { $guess < $num } {
puts "Too small, try again"
} elseif { $guess > $num } {
puts "Too large, try again"
} else {
puts "That's right!"
}
}
set used [used_time $starttime]
puts "You guessed value $num after $count tries and $used elapsed seconds"
The first set
statements establish variables. The first two evaluate expressions to discern a random number between 1 and 100, and the next one saves the system clock start time.
The puts
and gets
command are used for output to and input from the player. The puts
I've used imply standard out for output. The gets
needs the input channel to be defined, so this code specifies stdin
as the source for terminal input from the user.
The flush stdout
command is needed when puts
omits the end-of-line termination because Tcl buffers output and it might not get displayed before the next I/O is needed.
From there the while
statement illustrates the looping control structure and conditional logic needed to give the player feedback and eventually end the loop.
The final set
command calls our function to calculate elapsed seconds for gameplay, followed by the collected stats to end the game.
Play it!
$ ./numgame.tcl
Guess a number between 1 and 100
==> 100
Too large, try again
==> 50
Too large, try again
==> 25
Too large, try again
==> 12
Too large, try again
==> 6
Too large, try again
==> 3
That's right!
You guessed value 3 after 6 tries and 20 elapsed seconds
Continue learning
When I started this exercise, I doubted just how useful going back to a late 1990s fad language would be to me. Along the way, I found a few things about Tcl that I really enjoyed — my favorite being the square bracket command evaluation. It just seems so much easier to read and use than many other languages that overuse complicated closure structures. What I thought was a dead language was actually still thriving and supported on several platforms. I learned a few new skills and grew an appreciation for this venerable language.
Check out the official site over at https://www.tcl-lang.org. You can find references to the latest source, binary distributions, forums, docs, and information on conferences that are still ongoing.
Comments are closed.