Beginner

Operators in Tcl

Learn arithmetic, comparison, logical, and string operators in Tcl and how the expr command evaluates expressions, with Docker-ready examples

In most languages, 2 + 2 is an expression the parser understands directly. Tcl is different. Because everything is a command and everything is a string, Tcl has no built-in arithmetic in its core syntax. Instead, operators live inside a single command: expr. When you want to add, compare, or apply logic, you hand an expression to expr, and it parses and evaluates the operators for you.

This design is a direct consequence of Tcl’s philosophy. The interpreter doesn’t know that + means addition—it just sees commands and arguments. The expr command is where a small, C-like expression language is embedded. Understanding this split is the single most important idea for using operators in Tcl correctly.

Two habits will save you endless trouble. First, always brace your expressions: write [expr {$a + $b}], not [expr $a + $b]. Bracing lets expr do the variable substitution itself, which is faster (it compiles to bytecode) and safer (it avoids double-substitution bugs). Second, remember that Tcl has separate operators for numbers and strings== compares numerically while eq compares as strings. This tutorial walks through arithmetic, comparison, logical, string, and assignment operators, all the way to precedence.

Arithmetic Operators

Arithmetic operators only have meaning inside expr. Note that division between two integers produces an integer (it truncates), while introducing a single floating-point operand switches the whole expression to floating-point math.

Create a file named arithmetic.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Arithmetic operators all live inside the expr command
set a 17
set b 5

puts "Addition:       $a + $b  = [expr {$a + $b}]"
puts "Subtraction:    $a - $b  = [expr {$a - $b}]"
puts "Multiplication: $a * $b  = [expr {$a * $b}]"
puts "Division:       $a / $b  = [expr {$a / $b}]"
puts "Modulo:         $a % $b  = [expr {$a % $b}]"
puts "Power:          $a ** $b = [expr {$a ** $b}]"

# Integer division truncates; one float operand makes it floating-point
puts "Integer div:    17 / 5   = [expr {17 / 5}]"
puts "Float div:      17.0 / 5 = [expr {17.0 / 5}]"

The ** operator is exponentiation (17 to the 5th power). The % operator is the remainder after integer division.

Comparison Operators

Comparison operators return 1 for true and 0 for false—Tcl has no separate boolean type, so truth is just an integer. The numeric operators (==, !=, <, >, <=, >=) coerce their operands to numbers. For comparing strings, Tcl provides dedicated word operators (eq, ne, lt, gt, le, ge) that compare lexically without any numeric coercion.

Create a file named comparison.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Numeric comparisons return 1 (true) or 0 (false)
set x 10
set y 20

puts "x == y: [expr {$x == $y}]"
puts "x != y: [expr {$x != $y}]"
puts "x <  y: [expr {$x < $y}]"
puts "x >  y: [expr {$x > $y}]"
puts "x <= y: [expr {$x <= $y}]"
puts "x >= y: [expr {$x >= $y}]"

# String comparison operators compare lexically, not numerically
set s1 "apple"
set s2 "banana"
puts "s1 eq s2: [expr {$s1 eq $s2}]"
puts "s1 ne s2: [expr {$s1 ne $s2}]"
puts "s1 lt s2: [expr {$s1 lt $s2}]"

Using eq/ne instead of ==/!= for strings avoids a classic Tcl surprise: the string "10" and the string "10.0" are equal numerically (== gives 1) but different as strings (eq gives 0).

Logical Operators and the Ternary

Logical operators combine boolean results: && (and), || (or), and ! (not). Like C, Tcl’s && and || short-circuit. Tcl also supports the C-style ternary operator condition ? a : b, which is the idiomatic way to choose a value based on a test.

Create a file named logical.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Logical operators: && (and), || (or), ! (not)
set age 25
set hasLicense 1

puts "Can drive:     [expr {$age >= 18 && $hasLicense}]"
puts "Is minor:      [expr {$age < 18}]"
puts "Not licensed:  [expr {!$hasLicense}]"
puts "Senior or OK:  [expr {$age > 65 || $hasLicense}]"

# The ternary operator chooses one of two values
set score 72
set grade [expr {$score >= 60 ? "Pass" : "Fail"}]
puts "Score $score: $grade"

The ternary expression returns a string here ("Pass" or "Fail"), showing that expr results aren’t limited to numbers.

String and List Operators

Tcl has no + operator for joining strings—that would be ambiguous in a language where every value is a string. Concatenation is done through variable substitution inside double quotes, or with the append command, which mutates a variable in place. Within expr, the in and ni (not-in) operators test list membership.

Create a file named string_ops.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# There is no "+" for strings; substitution joins them
set first "John"
set last "Ousterhout"
set full "$first $last"
puts "Full name: $full"

# The append command builds a string in place
set greeting "Hello"
append greeting ", " $first "!"
puts $greeting

# The in and ni operators test list membership inside expr
set fruits {apple banana cherry}
set hasBanana [expr {"banana" in $fruits}]
set noGrape   [expr {"grape" ni $fruits}]
puts "banana in list?    $hasBanana"
puts "grape not in list? $noGrape"

The append command takes a variable name followed by any number of values to tack onto it—an efficient way to build up strings without repeated reassignment.

Operator Precedence and Assignment

Precedence in expr follows the same rules you know from C and ordinary math: * and / bind tighter than + and -, and parentheses override the defaults. The ** power operator is right-associative, so 2 ** 3 ** 2 means 2 ** (3 ** 2).

Tcl has no += or ++ operators. For integer counters, use the incr command. For anything else, reassign with set and expr.

Create a file named precedence.tcl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Precedence: * and / bind tighter than + and -
puts "2 + 3 * 4   = [expr {2 + 3 * 4}]"
puts "(2 + 3) * 4 = [expr {(2 + 3) * 4}]"
# ** is right-associative: 2 ** (3 ** 2) = 2 ** 9
puts "2 ** 3 ** 2 = [expr {2 ** 3 ** 2}]"

# incr changes an integer variable in place (no ++ or += in Tcl)
set counter 0
incr counter        ;# add 1 by default
incr counter 5      ;# add 5
puts "Counter: $counter"
incr counter -2     ;# subtract by adding a negative
puts "After -2: $counter"

# For non-integer updates, reassign with set + expr
set total 100
set total [expr {$total * 2}]
puts "Total doubled: $total"

Running with Docker

The efrecon/tcl:latest image uses tclsh as its entrypoint, so you pass the script path directly.

1
2
3
4
5
6
7
8
9
# Pull the official Tcl image
docker pull efrecon/tcl:latest

# Run each example
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/arithmetic.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/comparison.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/logical.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/string_ops.tcl
docker run --rm -v $(pwd):/app -w /app efrecon/tcl:latest /app/precedence.tcl

If you have Tcl installed locally, run any file with tclsh arithmetic.tcl.

Expected Output

Running arithmetic.tcl:

Addition:       17 + 5  = 22
Subtraction:    17 - 5  = 12
Multiplication: 17 * 5  = 85
Division:       17 / 5  = 3
Modulo:         17 % 5  = 2
Power:          17 ** 5 = 1419857
Integer div:    17 / 5   = 3
Float div:      17.0 / 5 = 3.4

Running comparison.tcl:

x == y: 0
x != y: 1
x <  y: 1
x >  y: 0
x <= y: 1
x >= y: 0
s1 eq s2: 0
s1 ne s2: 1
s1 lt s2: 1

Running logical.tcl:

Can drive:     1
Is minor:      0
Not licensed:  0
Senior or OK:  1
Score 72: Pass

Running string_ops.tcl:

Full name: John Ousterhout
Hello, John!
banana in list?    1
grape not in list? 1

Running precedence.tcl:

2 + 3 * 4   = 14
(2 + 3) * 4 = 20
2 ** 3 ** 2 = 512
Counter: 6
After -2: 4
Total doubled: 200

Key Concepts

  • Operators only work inside expr — Tcl’s core syntax has no arithmetic. set x [expr {$a + $b}] is the pattern; bare set x $a + $b just stores three separate words.
  • Always brace expressions[expr {$a + $b}] lets expr substitute variables itself, compiling to faster bytecode and avoiding double-substitution security bugs. Unbraced expr is a common source of errors.
  • Numbers and strings have different operators — use ==, !=, <, > for numeric comparison and eq, ne, lt, gt for string comparison. Mixing them leads to surprises like "10" == "10.0" being true.
  • Booleans are just integers — comparison and logical operators return 1 (true) or 0 (false); expr also accepts true/false/yes/no as boolean literals.
  • No string + operator — join strings with "$a$b" substitution or the append command; there is no overloaded +.
  • No ++ or += — increment integers with incr var ?amount?; for other updates, reassign with set var [expr {...}].
  • ** is right-associative2 ** 3 ** 2 evaluates as 2 ** (3 ** 2), matching mathematical convention but unlike most binary operators.
  • Bitwise operators exist too&, |, ^, ~, <<, and >> work on integers inside expr, just like in C, plus in/ni for list membership.

Running Today

All examples can be run using Docker:

docker pull efrecon/tcl:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining