Skip to content
VLSI Mentor

Tcl · Module 14

Asking Tcl What It Knows: the info Commands

Every question you would otherwise guess at — does this variable exist, does this command exist here, who called me, what does this procedure do — Tcl will answer directly.

Intermediate18 min readtclinfointrospectiondebugginginfo-levelreflection

Part I · Module 14 · Chapter 14.10 · Asking Tcl What It Knows: the info Commands

Tcl keeps almost everything about a running script available for inspection: which variables exist, which commands are defined, what a procedure's body is, how deep the call stack is and who called you. The info command is the door to all of it.

This matters more in Tcl than in most languages for one specific reason: you usually do not control the interpreter. A script you write will run inside some tool's embedded Tcl, of some version, with some set of extra commands and some set of missing ones. Guessing what is there is how scripts break in production. Asking is a one-liner.

1. info exists — the guard everything else is built on

info exists is the most-used member of the family, and it covers more cases than people expect:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set scalar 5
array set arr {x 1}

puts "  info exists scalar       : [info exists scalar]"
puts "  info exists arr          : [info exists arr]"
puts "  info exists arr(x)       : [info exists arr(x)]"
puts "  info exists arr(y)       : [info exists arr(y)]"
puts "  info exists nothing      : [info exists nothing]"
puts "  array exists arr         : [array exists arr]"
puts "  array exists scalar      : [array exists scalar]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  info exists scalar       : 1
  info exists arr          : 1
  info exists arr(x)       : 1
  info exists arr(y)       : 0
  info exists nothing      : 0
  array exists arr         : 1
  array exists scalar      : 0

Four things to take from this:

  • It never raises. That is the whole point — it is the test you use instead of catch, and it is why it reads so much better than if {[catch {set x}]}.
  • It is true for an array name, so info exists arr does not tell you whether arr is a scalar or an array. array exists is the one that answers that.
  • It indexes. info exists arr(y) distinguishes "the array exists but not this key" from "no array at all" — the two different can't read failures from 14.7 §1.
  • It is the fix for all three of those errors. Guard the read and you never see them.

2. info vars, locals, globals — what is in scope right now

These three answer "what variables can I see from here", and the difference between the first two is the interesting part:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc inspect {a b} {
    set local1 "x"
    set local2 "y"
    puts "  info vars    : [lsort [info vars]]"
    puts "  info locals  : [lsort [info locals]]"
    puts "  info vars l* : [lsort [info vars l*]]"
    global gcount
    puts "  after 'global gcount', info vars: [lsort [info vars]]"
    puts "  info locals now                 : [lsort [info locals]]"
}
set gcount 0
inspect 1 2
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  info vars    : a b local1 local2
  info locals  : a b local1 local2
  info vars l* : local1 local2
  after 'global gcount', info vars: a b gcount local1 local2
  info locals now                 : a b local1 local2

info vars counts everything visible; info locals counts only what is genuinely local. Before the global declaration they agree. After it, gcount appears in info vars and not in info locals — because it is visible here but lives in the global scope.

That difference is a diagnostic. If you suspect a procedure is reaching outside itself, info vars minus info locals is the list of names it has imported, with no need to read the code for global and variable declarations.

Note also that parameters count as locals — a and b are in both lists — and that all three take a glob pattern, which is what makes them usable in a procedure with thirty variables.

3. info commands, info procs — does this exist here?

This is the one that saves production scripts. info commands matches against everything callable; info procs only against Tcl procedures:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
namespace eval report {}
proc report::summary {} {}
proc report::detail {} {}
proc helper {} {}

puts "  info procs report::*  : [lsort [info procs report::*]]"
puts "  info procs h*         : [lsort [info procs h*]]"
puts "  info commands ls*     : [lsort [info commands ls*]]"
puts "  is 'puts' a proc?     : '[info procs puts]'"
puts "  is 'puts' a command?  : '[info commands puts]'"
puts "  does 'try' exist?     : '[info commands try]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  info procs report::*  : ::report::detail ::report::summary
  info procs h*         : helper
  info commands ls*     : lsearch lset lsort
  is 'puts' a proc?     : ''
  is 'puts' a command?  : 'puts'
  does 'try' exist?     : ''
  • info procs puts is empty but info commands puts is not. puts is a built-in written in C, not a Tcl procedure. So info commands is what you want for "can I call this", and info procs for "is this something defined in Tcl that I could read with info body".
  • A namespace pattern returns fully qualified names — ::report::detail — while a bare pattern returns bare names. Do not compare the two forms with eq; use string match or qualify both.
  • info commands try is empty on this interpreter. That is Chapter 14.5's warning, reduced to a value you can test.

The idiom to remember:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc have_command {name} { return [expr {[llength [info commands $name]] > 0}] }

4. info args, body, default — reading a procedure you did not write

When you inherit a script, or want to know what a tool's helper procedure actually does, you do not need the source file:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc scale {value {factor 2.0} args} {
    set out [expr {$value * $factor}]
    return $out
}

puts "  info args scale    : [info args scale]"
puts "  info body scale    :"
puts [info body scale]
puts "  info default:"
foreach p [info args scale] {
    if {[info default scale $p d]} {
        puts "    $p has default '$d'"
    } else {
        puts "    $p is required"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  info args scale    : value factor args
  info body scale    :

    set out [expr {$value * $factor}]
    return $out

  info default:
    value is required
    factor has default '2.0'
    args is required

info body returns the body text exactly as written, including its leading newline and indentation — which is why the output above has a blank line before set out.

info default proc param varName is the odd one: it returns 1 or 0 for "has a default", and writes the default into the variable you name. That is why it reads naturally as an if condition.

5. info level — where am I, and who called me?

info level with no argument is the current depth. With an argument it returns an entire invocation — command name and arguments:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc depth3 {} {
    puts "  inside depth3:"
    puts "    info level       (depth) : [info level]"
    puts "    info level 0  (this call): [info level 0]"
    puts "    info level -1   (caller) : [info level -1]"
    puts "    info level -2 (caller^2) : [info level -2]"
    puts "    info level 1  (outermost): [info level 1]"
}
proc depth2 {b} { depth3 }
proc depth1 {a} { depth2 "$a-two" }

puts "  at the top level, info level = [info level]"
depth1 "one"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  at the top level, info level = 0
  inside depth3:
    info level       (depth) : 3
    info level 0  (this call): depth3
    info level -1   (caller) : depth2 one-two
    info level -2 (caller^2) : depth1 one
    info level 1  (outermost): depth1 one

The addressing has two modes and both are useful:

  • Negative is relative. -1 is your caller, -2 its caller. This is what a logging helper wants — [lindex [info level -1] 0] is the name of whoever called you, which is how 14.9's dbg labels its output.
  • Positive is absolute. 1 is the outermost procedure call, counting in from the top level. This is what a backtrace wants, because it walks in a fixed direction regardless of where you started.
  • info level 0 is the current invocation with its arguments — the single most useful debug print you can put at the top of a procedure, because it shows exactly how you were called.

A complete backtrace is then five lines:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc backtrace {} {
    for {set i 1} {$i < [info level]} {incr i} {
        puts "    [string repeat {  } $i][info level $i]"
    }
}
proc c {x} { puts "  backtrace from c:" ; backtrace }
proc b {} { c "arg" }
proc a {} { b }
a
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  backtrace from c:
      a
        b
          c arg

Note the loop stops at [info level] - 1, excluding backtrace itself, and that c arg shows the argument it received. Compared with the errorInfo trace from 14.6, this is available without an error having occurred — which is what you want when the bug is "this procedure is being called and I cannot see from where".

6. Finding out what interpreter you are in

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "  info tclversion        : [info tclversion]"
puts "  info patchlevel        : [info patchlevel]"
puts "  info nameofexecutable  : [info nameofexecutable]"
puts "  info sharedlibextension: [info sharedlibextension]"
puts "  info script            : [info script]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  info tclversion        : 8.5
  info patchlevel        : 8.5.9
  info nameofexecutable  : /usr/bin/tclsh
  info sharedlibextension: .dylib
  info script            : p4.tcl

info tclversion is the two-part version for feature decisions; info patchlevel is the full one for a bug report. info script is the file currently being evaluated, which is how a script finds files relative to itself rather than relative to wherever it was run from.

For a version test, prefer package vsatisfies over string comparison on these:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if {[package vsatisfies [info patchlevel] 8.6-]} { ... }

String-comparing version numbers gives you 8.10 < 8.9, which is wrong and will bite exactly once.

7. Making Tcl list its own subcommands

The fastest way to learn what an unfamiliar interpreter supports is to ask for something that does not exist:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
catch {info bogus} e
puts $e
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unknown or ambiguous subcommand "bogus": must be args, body, cmdcount, commands, complete, default, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, patchlevel, procs, script, sharedlibextension, tclversion, or vars

That is the complete, authoritative list for this interpreter, which is better than documentation because it cannot be out of date. The same trick works on any ensemble command — string, file, array, dict, namespace, chan, and a tool's own ensembles:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
catch {string bogus} e   ;# lists every string subcommand
catch {file bogus} e     ;# lists every file subcommand

Three more worth knowing from that list: info complete tests whether a string is a syntactically complete command (how an interactive prompt decides whether to keep reading); info functions lists the functions available inside expr; info loaded lists loaded extension packages, which in a vendor tool tells you what the host has added.

8. Worked example: a preflight check

Everything above exists to replace a guess with a test. The payoff is a script that refuses to start rather than failing halfway through:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc have_command {name} { return [expr {[llength [info commands $name]] > 0}] }

proc preflight {} {
    set problems {}

    foreach name {lsort dict lassign try lmap} {
        if {[have_command $name]} {
            puts [format "  %-10s %s" $name "available"]
        } else {
            puts [format "  %-10s %s" $name "MISSING"]
            lappend problems "this interpreter has no '$name'"
        }
    }

    foreach v {DESIGN_NAME REPORT_DIR} {
        if {[info exists ::env($v)]} {
            puts [format "  \$%-9s %s" $v "set"]
        } else {
            puts [format "  \$%-9s %s" $v "NOT SET"]
            lappend problems "environment variable $v is not set"
        }
    }

    return $problems
}

puts "  interpreter: [info patchlevel] at [info nameofexecutable]"
set problems [preflight]
puts "  ---"
if {[llength $problems] == 0} {
    puts "  preflight passed"
} else {
    puts "  preflight found [llength $problems] problem(s):"
    foreach p $problems { puts "    - $p" }
}

Run with nothing set up:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  interpreter: 8.5.9 at /usr/bin/tclsh
  lsort      available
  dict       available
  lassign    available
  try        MISSING
  lmap       MISSING
  $DESIGN_NAME NOT SET
  $REPORT_DIR NOT SET
  ---
  preflight found 4 problem(s):
    - this interpreter has no 'try'
    - this interpreter has no 'lmap'
    - environment variable DESIGN_NAME is not set
    - environment variable REPORT_DIR is not set

And with the environment provided:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  interpreter: 8.5.9 at /usr/bin/tclsh
  lsort      available
  dict       available
  lassign    available
  try        MISSING
  lmap       MISSING
  $DESIGN_NAME set
  $REPORT_DIR set
  ---
  preflight found 2 problem(s):
    - this interpreter has no 'try'
    - this interpreter has no 'lmap'

try and lmap really are absent here — this is a genuine 8.5.9 interpreter, and those are 8.6 commands. A script that used either would have run happily until the first time that line was reached.

Three design points:

  • It collects problems rather than dying on the first one. One run tells you everything that is wrong, which matters when the fix involves asking somebody else for an environment.
  • info exists ::env(NAME) is the idiomatic environment check. env is an ordinary Tcl array, so the whole of §1 applies to it.
  • It prints the interpreter identity first. When somebody sends you a failing log, that one line answers the first question you would have asked.

Chapter 14.11 takes this further — the same discipline applied to the data a script is given, not just the environment it runs in.

9. Exercises

Exercise 1. Which info subcommand tells a scalar from an array, and why is it not info exists?

Exercise 2. Why must info exists ::DEBUG come before $::DEBUG in the same condition?

Exercise 3. A procedure's info vars lists five names and info locals lists three. What are the other two?

Exercise 4. Why is info commands puts non-empty while info procs puts is empty?

Exercise 5. Write the one-line test for whether a command is callable in this interpreter.

Exercise 6. What is the difference between info level -1 and info level 1?

Exercise 7. You are dropped into an unfamiliar tool's Tcl prompt. How do you find out what its string command supports?

Where this fits

Part of the Tcl curriculum.