This chapter describes the Tcl commands that implement control structures: if
, switch
, foreach
, while
, for
, break
, continue
, catch
, error
, and return
.
Control structure in Tcl is achieved with commands, just like everything else. There are looping commands: while
, foreach
, and for
. There are conditional commands: if
and switch
. There is an error handling command: catch
. Finally, there are some commands to fine-tune control structures: break
, continue
, return
, and error
.
A control structure command often has a command body that is executed later, either conditionally or in a loop. In this case, it is important to group the command body with curly braces to avoid substitutions at the time the control structure command is invoked. Group with braces, and let the control structure command trigger evaluation at the proper time. A control structure command returns the value of the last command it chose to execute.
Another pleasant property of curly braces is that they group things together while including newlines. The examples use braces in a way that is both readable and convenient for extending the control structure commands across multiple lines.
Commands like if
, for
, and while
involve boolean expressions. They use the expr
command internally, so there is no need for you to invoke expr
explicitly to evaluate their boolean test expressions.
The if
command is the basic conditional command. If an expression is true, then execute one command body; otherwise, execute another command body. The second command body (the else
clause) is optional. The syntax of the command is:
The then
and else
keywords are optional. In practice, I omit then
but use else
as illustrated in the next example. I always use braces around the command bodies, even in the simplest cases:
Example 6-1. A conditional if then else
command
if {$x == 0} { puts stderr "Divide by zero!" } else { set slope [expr $y/$x] }
The style of this example takes advantage of the way the Tcl interpreter parses commands. Recall that newlines are command terminators, except when the interpreter is in the middle of a group defined by braces or double quotes. The stylized placement of the opening curly brace at the end of the first and third lines exploits this property to extend the if
command over multiple lines.
The first argument to if
is a boolean expression. As a matter of style this expression is grouped with curly braces. The expression evaluator performs variable and command substitution on the expression. Using curly braces ensures that these substitutions are performed at the proper time. It is possible to be lax in this regard, with constructs such as:
if $x break continue
This is a sloppy, albeit legitimate, if
command that will either break out of a loop or continue with the next iteration depending on the value of variable x
. This style is fragile and error prone. Instead, always use braces around the command bodies to avoid trouble later when you modify the command. The following is much better (use then
if it suits your taste):
if {$x} { break } else { continue }
When you are testing the result of a command, you can get away without using curly braces around the command, like this:
if [command] body1
However, it turns out that you can execute the if
statement more efficiently if you always group the expression with braces, like this:
if {[command]} body1
You can create chained conditionals by using the elseif
keyword. Again, note the careful placement of curly braces that create a single if
command:
Example 6-2. Chained conditional with elseif
if {$key < 0} { incr range 1 } elseif {$key == 0} { return $range } else { incr range -1 }
Any number of conditionals can be chained in this manner. However, the switch
command provides a more powerful way to test multiple conditions.
The switch
command is used to branch to one of many command bodies depending on the value of an expression. The choice can be made on the basis of pattern matching as well as simple comparisons. Pattern matching is discussed in more detail in Chapter 4 and Chapter 11. The general form of the command is:
switch flags value pat1 body1 pat2 body2 ...
Any number of pattern-body pairs can be specified. If multiple patterns match, only the body of the first matching pattern is evaluated. You can also group all the pattern-body pairs into one argument:
switch flags value { pat1 body1 pat2 body2 ... }
The first form allows substitutions on the patterns but will require backslashes to continue the command onto multiple lines. This is shown in Example 6-4 on page 78. The second form groups all the patterns and bodies into one argument. This makes it easy to group the whole command without worrying about newlines, but it suppresses any substitutions on the patterns. This is shown in Example 6-3. In either case, you should always group the command bodies with curly braces so that substitution occurs only on the body with the pattern that matches the value.
There are four possible flags that determine how value
is matched.
| Matches the |
| |
| Uses regular expression pattern matching. See page 144. |
| No flag (or end of flags). Necessary when |
The switch
command raises an error if any other flag is specified or if the value
begins with -
. In practice I always use the --
flag before value
so that I don't have to worry about that problem.
If the pattern associated with the last body is default
, then this command body is executed if no other patterns match. The default
keyword works only on the last pattern-body pair. If you use the default
pattern on an earlier body, it will be treated as a pattern to match the literal string default
:
Example 6-3. Using switch
for an exact match
switch -exact -- $value { foo { doFoo; incr count(foo) } bar { doBar; return $count(foo)} default { incr count(other) } }
If you have variable references or backslash sequences in the patterns, then you cannot use braces around all the pattern-body pairs. You must use backslashes to escape the newlines in the command:
Example 6-4. Using switch
with substitutions in the patterns
switch -regexp -- $value ^$key { body1 } ### { body2 } {[0-9]*} { body3 }
In this example, the first and second patterns have substitutions performed to replace $key
with its value and
with a tab character. The third pattern is quoted with curly braces to prevent command substitution; square brackets are part of the regular expression syntax, too. (See page Chapter 11.)
If the body associated with a pattern is just a dash, -
, then the switch
command “falls through” to the body associated with the next pattern. You can tie together any number of patterns in this manner.
The while
command takes two arguments, a test and a command body:
while booleanExpr body
The while
command repeatedly tests the boolean expression and then executes the body if the expression is true (nonzero). Because the test expression is evaluated again before each iteration of the loop, it is crucial to protect the expression from any substitutions before the while
command is invoked. The following is an infinite loop (see also Example 1-13 on page 12):
set i 0 ; while $i<10 {incr i}
The following behaves as expected:
set i 0 ; while {$i<10} {incr i}
It is also possible to put nested commands in the boolean expression. The following example uses gets
to read standard input. The gets
command returns the number of characters read, returning -1 upon end of file. Each time through the loop, the variable line
contains the next line in the file:
The foreach
command loops over a command body assigning one or more loop variables to each of the values in one or more lists. Multiple loop variables, which were introduced in Tcl 7.5, are a very useful feature. The syntax for the simple case of a single variable and a single list is:
foreach loopVar valueList commandBody
The first argument is the name of a variable, and the command body is executed once for each element in the list with the loop variable taking on successive values in the list. The list can be entered explicitly, as in the next example:
Example 6-8. Looping with foreach
set i 1
foreach value {1 3 5 7 11 13 17 19 23} {
set i [expr $i*$value]
}
set i
=> 111546435
It is also common to use a list-valued variable or command result instead of a static list value. The next example loops through command-line arguments. The variable argv
is set by the Tcl interpreter to be a list of the command-line arguments given when the interpreter was started:
Example 6-9. Parsing command-line arguments
# argv is set by the Tcl shells # possible flags are: # -max integer # -force # -verbose set state flag set force 0 set verbose 0 set max 10 foreach arg $argv { switch -- $state { flag { switch -glob -- $arg { -f* {set force 1} -v* {set verbose 1} -max {set state max} default {error "unknown flag $arg"} } } max { set max $arg set state flag } } }
The loop uses the state
variable to keep track of what is expected next, which in this example is either a flag or the integer value for -max
. The --
flag to switch
is required in this example because the switch
command complains about a bad flag if the pattern begins with a -
character. The -glob
option lets the user abbreviate the -force
and -verbose
options.
The loop variable x
will take on the value of a
, the value of b
, and the result of the foo
command, regardless of any special characters or whitespace in those values.
You can have more than one loop variable with foreach
. Suppose you have two loop variables x
and y
. In the first iteration of the loop, x
gets the first value from the value list and y
gets the second value. In the second iteration, x
gets the third value and y
gets the fourth value. This continues until there are no more values. If there are not enough values to assign to all the loop variables, the extra variables get the empty string as their value.
Example 6-11. Multiple loop variables with foreach
foreach {key value} {orange 55 blue 72 red 24 green} { puts "$key: $value" } orange: 55 blue: 72 red: 24 green:
If you have a command that returns a short list of values, then you can abuse the foreach
command to assign the results of the commands to several variables all at once. For example, suppose the command MinMax
returns two values as a list: the minimum and maximum values. Here is one way to get the values:
set result [MinMax $list] set min [lindex $result 0] set max [lindex $result 1]
The foreach
command lets us do this much more compactly:
foreach {min max} [MinMax $list] {break}
The break
in the body of the foreach
loop guards against the case where the command returns more values than we expected. This trick is encapsulated into the lassign
procedure in Example 10-4 on page 139.
The foreach
command has the ability to loop over multiple value lists in parallel. In this case, each value list can also have one or more variables. The foreach
command keeps iterating until all values are used from all value lists. If a value list runs out of values before the last iteration of the loop, its corresponding loop variables just get the empty string for their value.
The for
command is similar to the C for
statement. It takes four arguments:
for initial test final body
The first argument is a command to initialize the loop. The second argument is a boolean expression that determines whether the loop body will execute. The third argument is a command to execute after the loop body:
You could use for
to iterate over a list, but you should really use foreach
instead. Code like the following is slow and cluttered:
for {set i 0} {$i < [llength $list]} {incr i} { set value [lindex $list $i] }
This is the same as:
foreach value $list { }
You can control loop execution with the break
and continue
commands. The break
command causes immediate exit from a loop, while the continue
command causes the loop to continue with the next iteration. There is no goto
command in Tcl.
Until now we have ignored the possibility of errors. In practice, however, a command will raise an error if it is called with the wrong number of arguments, or if it detects some error condition particular to its implementation. An uncaught error aborts execution of a script.[*] The catch
command is used to trap such errors. It takes two arguments:
catch command ?resultVar?
The first argument to catch
is a command body. The second argument is the name of a variable that will contain the result of the command, or an error message if the command raises an error. catch
returns zero if there was no error caught, or a nonzero error code if it did catch an error.
You should use curly braces to group the command instead of double quotes because catch
invokes the full Tcl interpreter on the command. If double quotes are used, an extra round of substitutions occurs before catch
is even called. The simplest use of catch
looks like the following:
catch { command }
A more careful catch
phrase saves the result and prints an error message:
Example 6-14. A standard catch
phrase
if {[catch { command arg1 arg2 ... } result]} {
puts stderr $result
} else {
# command was ok, result contains the return value
}
A more general catch
phrase is shown in the next example. Multiple commands are grouped into a command body. The errorInfo
variable is set by the Tcl interpreter after an error to reflect the stack trace from the point of the error:
Example 6-15. A longer catch
phrase
if {[catch { command1 command2 command3 } result]} { global errorInfo puts stderr $result puts stderr "*** Tcl TRACE ***" puts stderr $errorInfo } else { # command body ok, result of last command is in result }
These examples have not grouped the call to catch
with curly braces. This is acceptable because catch
always returns an integer, so the if
command will parse correctly. However, if we had used while
instead of if
, then curly braces would be necessary to ensure that the catch
phrase was evaluated repeatedly.
The catch
command catches more than just errors. If the command body contains return
, break
, or continue
commands, these terminate the command body and are reflected by catch
as nonzero return codes. You need to be aware of this if you try to isolate troublesome code with a catch
phrase. An innocent looking return
command will cause the catch
to signal an apparent error. The next example uses switch
to find out exactly what catch
returns. Nonerror cases are passed up to the surrounding code by invoking return
, break
, or continue
:
Example 6-16. There are several possible return values from catch
switch [catch { command1 command2 ... } result] { 0 { # Normal completion } 1 { # Error case } 2 { return $result ;# return from procedure} 3 { break ;# break out of the loop} 4 { continue ;# continue loop} default { # User-defined error codes } }
The error
command raises an error condition that terminates a script unless it is trapped with the catch
command. The command takes up to three arguments:
error message ?info? ?code?
The message
becomes the error message stored in the result variable of the catch
command.
If the info
argument is provided, then the Tcl interpreter uses this to initialize the errorInfo
global variable. That variable is used to collect a stack trace from the point of the error. If the info
argument is not provided, then the error
command itself is used to initialize the errorInfo
trace.
Example 6-17. Raising an error
proc foo {} { error bogus } foo => bogus set errorInfo => bogus while executing "error bogus" (procedure "foo" line 2) invoked from within "foo"
In the previous example, the error
command itself appears in the trace. One common use of the info
argument is to preserve the errorInfo
that is available after a catch
. In the next example, the information from the original error is preserved:
Example 6-18. Preserving errorInfo
when calling error
if {[catch {foo} result]} { global errorInfo set savedInfo $errorInfo # Attempt to handle the error here, but cannot... error $result $savedInfo }
The code
argument specifies a concise, machine-readable description of the error. It is stored into the global errorCode
variable. It defaults to NONE
. Many of the file system commands return an errorCode
that has three elements: POSIX
, the error name (e.g., ENOENT
), and the associated error message:
In addition, your application can define error codes of its own. Catch phrases can examine the code in the global errorCode
variable and decide how to respond to the error.
The return
command is used to return from a procedure. It is needed if return is to occur before the end of the procedure body, or if a constant value needs to be returned. As a matter of style, I also use return
at the end of a procedure, even though a procedure returns the value of the last command executed in the body.
Exceptional return conditions can be specified with some optional arguments to return
. The complete syntax is:
The -code
option value is one of ok
, error
, return
, break
, continue
, or an integer. ok
is the default if -code
is not specified.
The -code error
option makes return
behave much like the error
command. The -errorcode
option sets the global errorCode
variable, and the -errorinfo
option initializes the errorInfo
global variable. When you use return -code error
, there is no error
command in the stack trace. Compare Example 6-17 with Example 6-19:
Example 6-19. Raising an error with return
proc bar {} { return -code error bogus } catch {bar} result => 1 set result => bogus set errorInfo => bogus while executing "bar"
The return
, break
, and continue
code options take effect in the caller of the procedure doing the exceptional return. If -code return
is specified, then the calling procedure returns. If -code break
is specified, then the calling procedure breaks out of a loop, and if -code continue
is specified, then the calling procedure continues to the next iteration of the loop. These -code
options to return
enable the construction of new control structures entirely in Tcl. The following example implements the break
command with a Tcl procedure:
proc break {} { return -code break }
You can return integer-valued codes of your own with return -code
, and trap them with catch
in order to create your own control structures. There are also a number of exception packages available on the net that provide Java-like try-catch-except
structures for Tcl, although the Tcl exception mechanism strikes a nice balance between simplicity and power.
[*] More precisely, the Tcl script unwinds and the current Tcl_Eval
procedure in the C runtime library returns TCL_ERROR
. There are three cases. In interactive use, the Tcl shell prints the error message. In Tk, errors that arise during event handling trigger a call to bgerror
, a Tcl procedure you can implement in your application. In your own C code, you should check the result of Tcl_Eval
and take appropriate action in the case of an error.
3.22.27.45