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:
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]" 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 : 0Four 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 thanif {[catch {set x}]}. - It is true for an array name, so
info exists arrdoes not tell you whetherarris a scalar or an array.array existsis 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 differentcan't readfailures 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:
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 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 local2info 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:
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]'" 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 putsis empty butinfo commands putsis not.putsis a built-in written in C, not a Tcl procedure. Soinfo commandsis what you want for "can I call this", andinfo procsfor "is this something defined in Tcl that I could read withinfo body".- A namespace pattern returns fully qualified names —
::report::detail— while a bare pattern returns bare names. Do not compare the two forms witheq; usestring matchor qualify both. info commands tryis empty on this interpreter. That is Chapter 14.5's warning, reduced to a value you can test.
The idiom to remember:
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:
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"
}
} 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 requiredinfo 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:
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" 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 oneThe addressing has two modes and both are useful:
- Negative is relative.
-1is your caller,-2its 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'sdbglabels its output. - Positive is absolute.
1is 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 0is 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:
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 backtrace from c:
a
b
c argNote 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
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]" info tclversion : 8.5
info patchlevel : 8.5.9
info nameofexecutable : /usr/bin/tclsh
info sharedlibextension: .dylib
info script : p4.tclinfo 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:
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:
catch {info bogus} e
puts $eunknown 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 varsThat 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:
catch {string bogus} e ;# lists every string subcommand
catch {file bogus} e ;# lists every file subcommandThree 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:
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:
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 setAnd with the environment provided:
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.envis 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.
