Skip to content
VLSI Mentor

Tcl Part I · Module 9 · Loops and Iteration

foreach With Several Variables per Pass

A great deal of data in Tcl arrives as a flat list in which the elements alternate — a key, then its value, then the next key. Dictionaries are exactly that, array get produces exactly that, and so does a lot of tool output. You can convert such a list into something structured before walking it, and often you do not need to: foreach will take two, three or any number of elements per pass and bind them to separate variables. The syntax for that sits one brace away from the parallel-lists syntax of the previous chapter, the two are easy to confuse, and this chapter puts them side by side so you do not.

Foundation10 min readTclPart ILoopsforeachKey-value data

Part I · Module 9 · Chapter 9.3 · foreach With Several Variables per Pass

1. Two at a time

Put several variable names in one brace group:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {key value} {mode fast runs 3 width 8} {
    puts "$key -> $value"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mode -> fast
runs -> 3
width -> 8

Six elements, three passes, two variables bound on each. The brace group is a list of variable names, so {key value} means "take two per pass".

Any group size works:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {name dir width} {clk in 1  rst in 1  data out 32} {
    puts [format "%-6s %-4s %s" $name $dir $width]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clk    in   1
rst    in   1
data   out  32

2. This is how you walk key-value data in place

Chapter 7.1 established that a dictionary is a flat list of alternating keys and values. So this works directly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set cfg [dict create mode fast runs 3]

foreach {k v} $cfg {
    puts "$k = $v"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
mode = fast
runs = 3

No conversion step. The same applies to array get output from Chapter 7.9:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
array set limits {retries 3 timeout 60}

foreach {k v} [array get limits] {
    puts "$k = $v"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
retries = 3
timeout = 60

(The order is the array's, which Chapter 7.10 showed is not defined — sort the keys if the order matters.)

3. When the list does not divide evenly

The same padding rule as Chapter 9.2, now inside a single list:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {k v} {a 1 b} {
    puts "k='$k' v='$v'"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
k='a' v='1'
k='b' v=''

Two passes. The list had three elements, the last group was short, and v was padded with an empty string. No error.

With a group of three:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {x y z} {1 2 3 4 5} {
    puts "x=$x y=$y z='$z'"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x=1 y=2 z='3'
x=4 y=5 z=''

4. The collision worth knowing

These two lines look alike and mean entirely different things:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {a b} $one      { ... }   ;# several variables, ONE list
foreach a $one b $two   { ... }   ;# one variable each, TWO lists

Same data, both forms, side by side:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set flat {1 2 3 4}

puts "grouped — one list, two per pass:"
foreach {p q} $flat {
    puts "  p=$p q=$q"
}

puts "parallel — two lists, one each per pass:"
foreach p {1 2} q {3 4} {
    puts "  p=$p q=$q"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grouped — one list, two per pass:
  p=1 q=2
  p=3 q=4
parallel — two lists, one each per pass:
  p=1 q=3
  p=2 q=4

Both produced two passes and two variables, and the pairings are completely different. The distinguishing feature is the braces: one brace group means grouping; separate name-and-list pairs mean parallel.

You can combine them, though it is rarely worth the reader's effort:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {a b} {1 2 3 4} {c d} {5 6 7 8} {
    puts "$a $b | $c $d"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 2 | 5 6
3 4 | 7 8

5. A worked example

Reading a flat settings list, applying defaults, and reporting:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set raw {mode fast runs 3 width 8 mode deep}

if {[llength $raw] % 2 != 0} {
    error "settings list must have an even number of elements"
}

set settings [dict create runs 1 verbose 0]
foreach {k v} $raw {
    dict set settings $k $v
}

foreach k [lsort [dict keys $settings]] {
    puts [format "  %-8s %s" $k [dict get $settings $k]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mode     deep
  runs     3
  verbose  0
  width    8

Note mode appears twice in the input and the later value won, because each pass overwrites. That is usually what you want from a settings list, and it is worth knowing that it happens silently — a duplicated key is not an error here.

6. Grouping with lassign instead

For a list of records rather than a flat run, Chapter 6.14 is the better fit:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# flat: foreach groups it
foreach {n w} {clk 1 rst 1} { puts "$n $w" }

# records: foreach + lassign
foreach rec {{clk 1} {rst 1}} {
    lassign $rec n w
    puts "$n $w"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
clk 1
rst 1
clk 1
rst 1

Same output, different input shapes. Records are more robust — a missing field cannot shift everything after it, which is exactly what happens to a flat list when one element goes missing.

7. Exercises

Exercise 1. How many passes does foreach {a b c} {1 2 3 4 5 6 7} {...} make, and what are the variables on the last one?

Exercise 2. Write a loop printing key=value for the flat list {x 1 y 2 z 3}.

Exercise 3. Explain the difference between foreach {a b} $l {...} and foreach a $l1 b $l2 {...}.

Exercise 4. Given set flat {1 2 3 4}, what does each print?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {p q} $flat { puts "$p-$q" }
foreach p {1 2} q {3 4} { puts "$p-$q" }

Exercise 5. Add a guard rejecting a flat list whose length is not a multiple of three.

Exercise 6. When would you use dict for rather than foreach {k v} over the same dictionary?

Answers

1. Three passes. Seven elements with a group of three gives two full groups and a short one, so the last pass has a=7, b empty and c empty.

2.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach {key value} {x 1 y 2 z 3} {
    puts "$key=$value"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
x=1
y=2
z=3

3. The first takes two elements per pass from one list. The second takes one element per pass from each of two lists. The braces are what distinguish them.

4. The first prints 1-2 then 3-4; the second prints 1-3 then 2-4.

5.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc triples {l} {
    if {[llength $l] % 3 != 0} {
        error "expected triples, got [llength $l] elements"
    }
    foreach {a b c} $l { puts "$a $b $c" }
}
triples {1 2 3 4 5 6}
if {[catch {triples {1 2 3 4}} m]} { puts "rejected: $m" }
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 2 3
4 5 6
rejected: expected triples, got 4 elements

6. Whenever the value really is a dictionary. dict for states the intent, and it validates the structure — a malformed dictionary is reported rather than quietly mis-paired. foreach {k v} is for flat lists that are not dictionaries.

8. Remember

  • foreach {a b} $list {body} takes several elements per pass from one list; the brace group is a list of variable names.
  • Any group size works — two, three or more.
  • A dictionary is a flat list, so foreach {k v} $dict walks it directly, as does foreach {k v} [array get a].
  • Prefer dict for for real dictionaries — it states the intent and validates the structure.
  • A short final group is padded with empty strings, silently. Check llength % n first when complete records are expected.
  • One brace group means grouping; separate name-and-list pairs mean parallel iteration. They look alike and pair the data differently.
  • Duplicate keys in a flat settings list silently overwrite, last one winning.
  • For records rather than a flat run, use foreach plus lassign — a missing field cannot shift the rest.

Where this fits

Part of the Tcl curriculum.