Flow control in Tcl

In this part of the Tcl tutorial, we will talk about the flow control. We will define several commands that enable us to control the flow of a Tcl script.

In Tcl language there are several commands that are used to alter the flow of a program. When a program is run, its commands are executed from the top of the source file to the bottom. One by one. This flow can be altered by specific commands. Commands can be executed multiple times. Some commands are conditional. They are executed only if a specific condition is met.

The if command

The if command has the following general form:

if expr1 ?then? body1 elseif expr2 ?then? body2 elseif ... ?else? ?bodyN?

The if command is used to check if an expression is true. If it is true, a body of command(s) is then executed. The body is enclosed by curly brackets.

The if command evaluates an expression. The expression must return a boolean value. In Tcl, 1, yes, true mean true and 0, no, false mean false.

!/usr/bin/tclsh

if yes {
    puts "This message is always shown"
}

In the above example, the body enclosed by { } characters is always executed.

#!/usr/bin/tclsh

if true then {
    puts "This message is always shown"
}

The then command is optional. We can use it if we think, it will make the code more clear.

We can use the else command to create a simple branch. If the expression inside the square brackets following the if command evaluates to false, the command following the else command is automatically executed.

#!/usr/bin/tclsh

set sex female

if {$sex == "male"} {

    puts "It is a boy"
} else {

    puts "It is a girl"
}

We have a sex variable. It has "female" string. The boolean expression evaluates to false and we get "It is a girl" in the console.

$ ./girlboy.tcl 
It is a girl

We can create multiple branches using the elseif command. The elseif command tests for another condition, if and only if the previous condition was not met. Note that we can use multiple elseif commands in our tests.

#!/usr/bin/tclsh

# nums.tcl

puts -nonewline "Enter a number: "
flush stdout
set a [gets stdin]

if {$a < 0} {

    puts "the number is negative"
} elseif { $a == 0 } {

    puts "the numer is zero"
} else {

    puts "the number is positive"
}

In the above script we have a prompt to enter a value. We test the value if it is a negative number or positive or if it equals to zero. If the first expression evaluates to false, the second expression is evaluated. If the previous conditions were not met, then the body following the else command would be executed.

$ ./nums.tcl 
Enter a number: 2
the number is positive
$ ./nums.tcl 
Enter a number: 0
the numer is zero
$ ./nums.tcl 
Enter a number: -3
the number is negative

Running the example multiple times.

The switch command

The switch command matches its string argument against each of the pattern arguments in order. As soon as it finds a pattern that matches the string it evaluates the following body argument by passing it recursively to the Tcl interpreter and returns the result of that evaluation. If the last pattern argument is default then it matches anything. If no pattern argument matches string and no default is given, then the switch command returns an empty string.

#!/usr/bin/tclsh

# switch_cmd.tcl

puts -nonewline "Select a top level domain name:"
flush stdout

gets stdin domain

switch $domain {

    us { puts "United States" }
    de { puts Germany }
    sk { puts Slovakia }
    hu { puts Hungary }
    default { puts "unknown" }
}

In our script, we prompt for a domain name. There are several options. If the value equals for example to us the "United States" string is printed to the console. If the value does not match to any given value, the default body is executed and unknown is printed to the console.

$ ./switch_cmd.tcl 
Select a top level domain name:sk
Slovakia

We have entered sk string to the console and the program responded with Slovakia.

The while command

The while command is a control flow command that allows code to be executed repeatedly based on a given boolean condition.

The while command executes the commands inside the block enclosed by curly brackets. The commands are executed each time the expression is evaluated to true.

#!/usr/bin/tclsh

# whileloop.tcl

set i 0
set sum 0

while { $i < 10 } {

    incr i
    incr sum $i
}

puts $sum

In the code example, we calculate the sum of values from a range of numbers.

The while loop has three parts: initialization, testing, and updating. Each execution of the command is called a cycle.

set i 0

We initiate the i variable. It is used as a counter.

while { $i < 10 } {
...
}

The expression inside the curly brackets following the while command is the second phase, the testing. The commands in the body are executed, until the expression is evaluated to false.

incr i

The last, third phase of the while loop is the updating. The counter is incremented. Note that improper handling of the while loops may lead to endless cycles.

The for command

When the number of cycles is know before the loop is initiated, we can use the for command. In this construct we declare a counter variable, which is automatically increased or decreased in value during each repetition of the loop.

#!/usr/bin/tclsh

for {set i 0} {$i < 10} {incr i} {
    puts $i
}

In this example, we print numbers 0..9 to the console.

for {set i 0} {$i < 10} {incr i} {
    puts $i
}

There are three phases. First, we initiate the counter i to zero. This phase is done only once. Next comes the condition. If the condition is met, the command inside the for block is executed. Then comes the third phase; the counter is increased. Now we repeat phases 2 and 3 until the condition is not met and the for loop is left. In our case, when the counter i is equal to 10, the for loop stops executing.

$ ./forloop.tcl 
0
1
2
3
4
5
6
7
8
9

Here we see the output of the forloop.tcl script.

The foreach command

The foreach command simplifies traversing over collections of data. It has no explicit counter. It goes through a list element by element and the current value is copied to a variable defined in the construct.

#!/usr/bin/tclsh

set planets { Mercury Venus Earth Mars Jupiter Saturn
    Uranus Neptune }

foreach planet $planets {
    puts $planet
}

In this example, we use the foreach command to go through a list of planets.

foreach planet $planets {
    puts $planet
}

The usage of the foreach command is straightforward. The planets is the list that we iterate through. The planet is the temporary variable that has the current value from the list. The foreach command goes through all the planets and prints them to the console.

$ ./planets.tcl 
Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune

Running the above Tcl script gives this output.

#!/usr/bin/tclsh

set actresses { Rachel Weiss Scarlett Johansson Jessica Alba \
    Marion Cotillard Jennifer Connelly}

foreach {first second} $actresses {
    puts "$first $second"
}

In this script, we iterate througn pairs of values of a list.

foreach {first second} $actresses {
    puts "$first $second"
}

We pick two values from the list at each iteration.

$ ./actresses.tcl 
Rachel Weiss
Scarlett Johansson
Jessica Alba
Marion Cotillard
Jennifer Connelly

This is the output of actresses.tcl script.

#!/usr/bin/tclsh

foreach i { one two three } item {car coins rocks} {
    puts "$i $item"
}

We can iterate over two lists in parallel.

$ ./parallel.tcl 
one car
two coins
three rocks

This is the output of the parallel.tcl script.

The break and continue commands

The break command can be used to terminate a block defined by while, for, or switch commands.

#!/usr/bin/tclsh

while true {

    set r [expr 1 + round(rand()*30)]
    puts -nonewline "$r "

    if {$r == 22} { break }
}

puts ""

We define an endless while loop. We use the break command to get out of this loop. We choose a random value from 1 to 30 and print it. If the value equals to 22, we finish the endless while loop.

set r [expr 1 + round(rand()*30)]

Here we calculate a random number between 1..30. The rand() is a built-in Tcl procedure. It returns a random number from 0 to 0.99999. The rand()*30 returns a random number between 0 to 29.99999. The round() procedure rounds the final number.

$ ./breakcommand.tcl 
28 20 8 8 12 22 

We might get something like this.

The continue command is used to skip a part of the loop and continue with the next iteration of the loop. It can be used in combination with for and while commands.

In the following example, we will print a list of numbers that cannot be divided by 2 without a remainder.

#!/usr/bin/tclsh

set num 0

while { $num < 100 } {

    incr num

    if {$num % 2 == 0} { continue }
 
    puts "$num "
}

puts ""

We iterate through numbers 1..99 with the while loop.

if {$num % 2 == 0} { continue }

If the expression num % 2 returns 0, the number in question can be divided by 2. The continue command is executed and the rest of the cycle is skipped. In our case, the last command of the loop is skipped and the number is not printed to the console. The next iteration is started.

In this part of the Tcl tutorial, we were talking about control flow structures.