Troubleshooters.Com®, Code Corner and Tcl Town Present:

Tcl Secret Sauce

CONTENTS

Introduction

Like every other technology, Tcl has gotchas and unexpected situations that are inconsistently documented across the Internet. These gotchas can keep you from getting to first base in learning Tcl. This document enables you to get past these gotchas, in code you can easily understand, so you can get on with the business of learning Tcl.

This document was written in early 2020 and has been tech-edited for

Hello World

Here's the simplest Hello World Tcl program:

#!/usr/bin/tclsh
puts "Hello world!"

Name the preceding file hello.tcl, and perform the following command on it:

chmod a+x hello.tcl

When you run the preceding code, you should get the following output:

[slitt@mydesk tut]$ ./hello.tcl
Hello world!
[slitt@mydesk tut]$

If you get a different result, troubleshoot. Did you mark the file as executable? Is tclsh really in /usr/bin, or is it somewhere else on your computer? Did you put doublequotes around the Hello World! string? Take note of all error messages, and carefully get this script so it does the right thing. Don't continue with this document until you get this right, because you can't do Tcl without being able to get it running in your environment.

Enhanced Hello World

Create the following file, call it hello_e.tcl:

#!/usr/bin/tclsh
puts "Hello world!"
puts -nonewline {It's a nice "day" };
puts {for programming}
puts -nonewline "in Tcl"
puts {.}

The preceding code has the following output:

[slitt@mydesk tut]$ ./hello.tcl
Hello world!
It's a nice "day" for programming
in Tcl.
[slitt@mydesk tut]$

The code in this section is a tale of five puts calls:

  1. Just output a standard string ("Hello World")
  2. Use curly braces to contain the string. Double quotes are nested and print as-is. The -nonewline outputs the string without appending a newline. The space before the closing brace enables the following puts to have a space between itself and this puts.
  3. A normal puts with the string delineated by curly braces
  4. A string delineated by doublequotes, with -noappend. No space precedes the closing doublequote because the next character to be printed is a period, and those need to be flush against the sentence they end.
  5. Print a period to end the sentence.

Study this until you're comfortable with it and have committed these two string forms and the covered puts calls to memory.

Add Command Line History to tclsh With rlwrap

Tcl has an interactive development environment available by running tclsh with no command line arguments. Unfortunately, as it ships from the factory, tclsh doesn't recall earlier commands with handy up and down arrows.

GNU puts out a program called rlwrap. Most distros package it, so install it. From then on, you can have Bash like command line history and editing within tclsh by starting it in the following manner:

rlwrap tclsh

NOTE:

tclsh ships with bang style history using the exclamation point, but that's not half as quick and easy as what rlwrap arrow key history and command line editing give you.

tclsh ships with bang style history using the exclamation point, but that's not half as quick and easy as what rlwrap arrow key history and command line editing give you.

Never Assign With =

From Fortran on down, most languages use a single equal sign (=) to perform assignment. Well, except Pascal that used ":=". Tcl uses none of that: It uses a set statement as follows:

set myvarname myvalue

If you forget this, you'll waste a lot of time. Never use a single equal sign.

Dollar Sign When a Variable is Used

As mentioned before:

set myvarname myvalue

The preceding code assigned myvalue to variable name myvarname. When you use a variable name, as opposed to assigning to it, you need to tack a dollar sign on the front. Thus:

set myvarname myvalue
puts $myvarname

When you forget to use the dollar sign, and if you're used to other languages you will, you'll either get a literal (like the literal myvarname, or worse, a cryptic error message.

When things don't go right, always check new code to make sure you haven't left out a dollar sign when you use a variable.

Find Tcl Version

To find your tclsh version, perform the following within tclsh:

info patchlevel

View the Environment and Library Path

Copy and paste the following code exactly into a file, permission the file executable by all:

#!/usr/bin/tclsh

foreach evar [array names ::env] {
    puts "$evar ===> $::env($evar)"
}

Careful: the word "names" is a reserved word: One of many commands you can perform on an array. The double colon before every instance of env puts env, the environment array, in the global namespace, which is usually what you want to do with the environmental namespace. When run, the preceding program shows you all the environment variables.

Now expand the preceding so it shows off the directories in the special $auto_path variable, and also has headers and tailers to let you know what's what:

#!/usr/bin/tclsh

puts "====== BEGIN ENV VAR ARRAY ======"
foreach evar [array names ::env] {
    puts "$evar ===> $::env($evar)"
}
puts "======  END  ENV VAR ARRAY ======"

puts "====== BEGIN \$auto_path LIST ======"
foreach dir $auto_path {
    puts $dir
}
puts "======  END  \$auto_path LIST ======"

NOTE:

You've probably noticed that the syntax of the two loops in the precednig code are different from each other. This is because $env is a Tcl Array, but $auto_path is a Tcl List. The difference is discussed in the next section.

Brackets, Braces and Parens

CONFUSION ALERT:

Few Tcl behaviors are as confusing as its use of square brackets, curly braces and parentheses.

It's easy to mix up the functions of square brackets, curly braces, and parentheses in Tcl. This is especially true because Tcl's use of these punctuations is very different from most other computer languages. This section gives you needed rules of the road.

Square Bracket Usage

Square brackets have exactly one use in Tcl: To delineate a command and expose its return value. If you have a three word command, and you want to use it as an argument to a different command, then you must put braces around the first command. The following is an example:

puts [lindex $mylist 3]

The preceding is a puts command that uses the return value of the multi-word lindexcommand. The puts command expects exactly one argument, so to facilitate that, square brackets surround the lindex command and its arguments.

The preceding is the only function of square brackets in Tcl.

Parentheses Usage

Tcl uses parentheses for two distinct purposes:

The following code illustrates the former usage: Specification of a specific key in an array:

set favecolors(Mae) Gray
puts $favecolors(Mae)

CONFUSION ALERT:

In the set command, you might think favecolors should start with a dollar sign because the array is a variable and you're just adding an element. That makes perfect sense, but it's not how Tcl works: If you add an element to an array, you don't use the dollar sign.

The following code illustrates the latter usage: Holding arguments for expr commands:

set mycosine [expr cos(3.14/4)]
  • To group and protect an expression like those in the conditional part of an if, while, or foreach statement.
  • Braces Usage

    Tcl uses curly braces to fill the following needs:

    1. Construct a literal string with no substitution.
    2. Delineate and protect expressions such as tests for , , if, ifelse, while, for and foreach.
    3. Delineate scripts run by the likes of , , if, ifelse, while, for and foreach. A script is simply a series of commands, and a command is simply a string whose first word is the name of the command.

    Lists and Arrays

    Tcl has a data type called List, and another called Array. For the person who fluent in other computer languages, or who has taken courses in Computer Science, Tcl's use of the term Array could be very misleading, and could stop learning dead in its tracks. Hence this section.

    Two Common Collections

    There are two common types of collections in almost every evolved language:

    1. An ordered collection of items, usually referenced by item number. Often called an array, these include Perl arrays, Python lists, Ruby arrays, and Tcl lists.
    2. An unordered collection of key-<value pairs with unique keys, usually referenced by the key. These include Perl hashes, Python dicts, Ruby hashes, Lua tables, and very surprisingly, Tcl arrays.

    That's right: What's called an array in Tcl is really an unordered collection of key-<value pairs, just like Perl hashes, Python dicts, Ruby hashes and Lua tables. And unlike Lua tables, Tcl array keys are never integers, and instead always strings.

    Unless you consciously know and remember this, it could act as slippery mud causing you to slide down the learning curve. Arrays in Tcl are quite different from traditional arrays.

    List Intro

    The following code introduces the creation and listing of a Tcl list and selection of list item 3:

    #!/usr/bin/tclsh
    
    set top5 { list
      {Wilkes}
      {Jackson}
      {Van Dorn}
      {Khan}
      {d'Angeles}
    }
    
    foreach champ $top5 {
      puts $champ
      incr ss 
    }
    
    puts ""
    puts [lindex $top5 3]
    

    The preceding code prints the word "list", then prints out the list items in order, and then prints out list item 3, which is "Van Dorn". The word "list" prints because it's the 0th item in the list. So the 0th item is the type of the variable. That makes the actual list items 1 based: The earliest item is #1, not #0 (like in C).

    The puts [lindex $top5 3] line retrieves and prints item #3.

    The "list" printout can be eliminated by not printing item # 0, as shown in the following code:

    #!/usr/bin/tclsh
    
    set top5 { list
      {Wilkes}
      {Jackson}
      {Van Dorn}
      {Khan}
      {d'Angeles}
    }
    
    set ss 0
    foreach champ $top5 {
      if {$ss > 0} {
        puts "$ss $champ"
      }
      incr ss 
    }
    
    puts ""
    puts [lindex $top5 3]
    

    Philosophical Tidbits

    Tcl is built around a philosophy that shouldn't be studied until you're more experienced with Tcl. But there are a few tidbits of that philosophy that you can use to push away confusion right now...

    Everything is a String

    A List is a String, and Vice Versa

    A Command is a List Whose First Element is the Command's Name

    A Script is a Series of Commands

    Reading a File

    Here's how you read a file:

    #!/usr/bin/tclsh
    
    set f [open "/etc/fstab" "r"]
    set data [read $f]
    puts $data
    

    In the preceding, you set f to the return of the open statement, which is in brackets so it's taken as one argument to the set command. The open command takes two arguments: The filename, and the read/write mode, which is "r" for read.

    Writing a File

    Here's how you write a file:

    #!/usr/bin/tclsh
    
    # NOTE: 3RD arg to open, the permission, is crazy
    # Experiments failed to indicate how it works.
    set f [open "./steve.litt" "w"]
    puts $f "Steve Litt's file.\n"
    close $f
    
    Same thing except since we're writing the file, "w" is used for the read/write mode. There are actually several other modes: They're the same as in the C language and several others, so you can look them up in Tcl docs or elsewhere. There's actually an optional third argument to open, representing the permissions of a newly written file, but I've never been able to get that third argument to work properly.

    Running Another Program

    You'll often want to run another program from Tcl. The simplest case is running a GUI program, as follows:

    #!/usr/bin/tclsh
    open |gimp
    puts "Continuing on ..."
    

    The preceding Tcl program runs gimp and then continues on its way, independent of what Gimp's doing. If you want the Tcl program to stop (block) until gimp finishes, you need to assign the return of the open to a file variable, as follows:

    #!/usr/bin/tclsh
    
    set temp [open |gimp]
    catch {close $temp} err
    
    puts "After close."
    if {$err != ""} {
       puts "err is $err."
    }
    puts "Continuing on..."
    

    The preceding stops until Gimp terminates, then closes the file assigned by the open. Notice the following line:

    catch {close $temp} err

    The preceding runs the close of filevar $temp, but catches any error to prevent the entire script from terminating. This is handy when running other programs, because you don't (necessarily) want the non-zero return from the program that got run to abort your entire Tcl program. If the program returns non-zero, $err contains a text error message from the program that was run.

    Running a Simple Command

    You'll often want to run a simple command from Tcl. If your Tcl program doesn't need to feed the spawned command's stdin nor capture its stdout, it's pretty easy:

    #!/usr/bin/tclsh
    set temp [open "|touch junk.jnk"]
    catch {close $temp} err
    
    puts "After close."
    if {$err != ""} {
       puts "err is $err."
    }
    puts "Continuing on..."
    

    Running a Command With Pipes and Redirects

    It's pretty easy to run a command with pipes and redirects. Consider the following program, which puts all output of the mount program that begins with /dev/ into file junk.jnk:

    #!/usr/bin/tclsh
    
    set pgm [open {|mount | grep ^/dev/ > junk.jnk} "w" ] 
    catch {close $pgm} err
    if {$err != ""} {
    	puts $err
    }
    

    If you're going to shell out at all, sometimes it's best to do everything there, including piping and redirection.

    NOTE:

    If you don't know what piping and redirection are, see https://ryanstutorials.net/linuxtutorial/piping.php

    Simulating Backticking a Program

    Sometimes you need to capture the stdout from the program you run. Here's how you do it:

    #!/usr/bin/tclsh
    
    set pgm [open {|/bin/ls -ltr} "r" ] 
    set data [read $pgm]
    catch {close $pgm} err
    if {$err != ""} {
    	puts $err
    } else {
    	puts $data
    }
    

    Inputting and Outputting Through a Filter Program

    A filter program is a program that takes its stdin, modifies it, and puts the modified content out its stdout. For instance, the command cat -n takes in a series of lines in its stdin, and sends out those same lines in the same order, but numbered, out its stdout.

    Sometimes you want your Tcl program to send a series of lines to a filter program and get back a processed series of lines. This is a little tricking in Tcl, but only a little. Consider the following program:

    #!/usr/bin/tclsh
    
    set pgm [open {|cat -n} "r+" ] 
    puts $pgm "one"
    puts $pgm "two"
    puts $pgm "three"
    
    # Next two lines required
    # So program doesn't stall
    flush $pgm
    chan close $pgm write
    
    set data [read $pgm]
    catch {close $pgm}
    puts $data
    
    

    Creating, Enabling and Using Your Own Tcl package

    Creating, enabling and using your own Tcl package is more complicated than it is in most other interpreters. I vigorously advise you to do it with a "Hello World" setup before doing it with your real package, in order to drastically limit the number of moving parts obfuscating the situation. This section walks you through a minimum-moving-parts hello world in its own directory.

    1. Create a new directory for the hello world.
    2. cd to that new directory.
    3. Create package mypkg.tcl with the following code:
      package provide mypkg 1.0
      package require Tcl
      
      namespace eval mypkg {
         proc id {} {
            puts "This is mypkg"
         }
      }
      
    4. Create mwe.tcl to exercise the package. The mwe.tcl program is as follows:
      #!/usr/bin/tclsh
      
      puts $auto_path
      set auto_path [linsert $auto_path 0 /d/at/tcl/test/mwe] 
      puts $auto_path
      package require mypkg
      ::mypkg::id
      
    5. chmod a+x mwe.tcl
    6. Type tclsh to get into the Tcl interactive environment.
    7. Type pkg_mkIndex -direct to index all possible packages in the current directory.
    8. Verify that a file named pkgIndex.tcl now exists
    9. ./mwe.tcl
    10. If everything's working OK, the output is three lines:
      1. The original contents of $auto_path, a built-in Tcl variable.
      2. The new contents of $auto_path after prepending the full pathname of the current directory
      3. The phrase "This is mypkg", proving that you can run proc id which is defined in mypkg.tcl from within mwe.tcl. This proves that you successfully required mypkg.

    What Just Happened

    You made a tiny package file, mypkg, and imported it via a require command into mwe.tcl, then proved the import by running mwe.tcl and seeing the "This is mypkg" string at the bottom.

    The mypkg.tcl package file creates a namespace named "mypkg", that includes the procedure id, which prints "This is mypkg". Then, within tclsh, you run pkg_mkIndex -direct in order to create necessary file pkgIndex.tcl. Application file mwe.tcl prepends the full pathname of the current directory to $auto_path so mwe.tcl can reach out to the prepended directory (in this case the current directory, consult file pkgIndex in that directory, and import the package that implements mypkg.

    Once you've gone that far, you can do things like putting the package file in a different directory and rerunning pkg_mkIndex from ttclsh in that directory adding things to it, and filling out the application that required it.

    Using a String as a Command

    It's well known that everything is a string in Tcl, but just try to make a string perform as a command. It's pretty hard. Consider a situation where I want to run, as a command, a string called cmd, which contains a puts command. All sorts of attempts fail, a few succeed, but here's one that handles most of the cases:

    #!/usr/bin/tclsh
    set cmd {puts "Hello World!"}
    puts $cmd
    eval $cmd
    

    The preceding first prints the string that will be used as the command, and then the result of running that command via eval. This method also handles the more complex case in which the ""World" is replaced by a variable called city:

    #!/usr/bin/tclsh
    set city "Los Angeles"
    set cmd {puts "Hello $city!"}
    puts $cmd
    eval $cmd
    

    The preceding code outputs the following:

    [slitt@mydesk tut]$ ./test.tcl 
    puts "Hello $city!"
    Hello Los Angeles!
    [slitt@mydesk tut]$
    

    Here's the logic of the preceding code. The eval command evaluates a string, as long as the string is set up as a proper list. First city is set to "Los Angeles": Nothing unusual there.

    Next we set cmd to the do-not-substitute string {puts "Hello $city!"}. The purpose of the doublequotes is to keep together the argument string for puts while also making sure that puts has exactly one argument. The purpose of the curly braces is to make sure that no substitutions take place, so the value of cmd is literally:

    puts "Hello $city!"

    Then the eval runs that command, and outputs "Hello Los Angeles".

    There's another way to do it. The preceding method left the variable replacement of city until the eval command. The following does the substitution when the cmd string is set:

    #!/usr/bin/tclsh
    set city "Los Angeles"
    set cmd "puts \"Hello $city!\""
    puts $cmd
    eval $cmd
    

    The preceding code produces the following output:

    [slitt@mydesk tut]$ ./test.tcl 
    puts "Hello Los Angeles!"
    Hello Los Angeles!
    [slitt@mydesk tut]$
    

    The output of the eval command is the same, but the value of cmd is literally:

    puts "Hello Los Angeles!"

    So this time, the actual city name is in cmd rather than a variable representing the city. There are probably several ways to use a string as a command, but there are probably ten times as many ways such an effort can fail.

    CONFUSION RISK:

    It's very difficult to correctly use a string as a command, because there are so many right-looking but wrong ways to do it. Remember to look back in this document when you need to use a string as a command.

    Wrapup

    If you've read and understood all the sections of this document, and hopefully performed the exercises therein, you're in an excellent position to learn Tcl, a great language with many unexpected situations that can scuttle learning if not approached right. Now, equipped with this knowledge, go back to the Tcl Town homepage to continue your Tcl education, including both Troubleshooters.Com hosted material and some carefully curated offsite links.

    It will be worth it. Tcl is a good language with lots of libraries that develops lightning fast.


    [ Training | Troubleshooters.Com | Email Steve Litt ]