Tcl - Programmstrukturen

Sequenzen

Befehlsfolgen werden durch eine Zeilenschaltung oder ein Semikolon getrennt. Werden mehrere Befehle benötigt, wenn nur einer erlaubt ist, können diese durch { } geklammert werden:
  if {$a < $b} {
    cmd_1
    cmd_2
  }
Hinweis: Die Zeilenstruktur muß genauso übernommen werden.

Verzweigungen

Einfache Verzweigungen werden mit einer if-Anweisung realisiert:
  if expression body [else body]
Die if-Anweisung besteht aus 3 oder 5 Argumenten. Enthält eines der Argumente Leerzeichen, ist eine Gruppierung erforderlich (mittels " " oder wie oben im Beispiel). Auch müssen alle Argumente auf einer Zeile stehen, es sei denn, die Zeilenschaltung erfolgt innerhalb einer Gruppierung.

Der Ausdruck wird durch einen internen exp-Aufruf ausgewertet, i.e. trotz geschweifter Klammern erfolgt die $- und []-Auflösung. Ebenso erfolgt für die Bodies ein zweiter Auflösungsschritt (mittels eval), so daß immer geschweifte Klammern genutzt werden können.

Ist der Wert von expression ungleich 0, bedeutet das den booleschen Wert true, 0 dagegen false.

Beispiel:

  set jahr 1999
  if {$jahr % 4 == 0 && ($jahr % 100 != 0 || $jahr % 400 == 0)} {
    puts "Schaltjahr"
  } else {
    puts "Kein Schaltjahr"
  }
Die Position der geschweiften Klammern ist wichtig: So muß die öffnende Klammer des then-Zweiges noch auf der gleichen Zeile wie if stehen, dann kommt jedes Kommando auf eine eigene Zeile. Die schließende Klammer des if-Zweiges, das Wort else und die öffnende Klammer des else-Zweiges müssen ebenfalls gemeinsam auf einer Zeile stehen. Das erste Kommando nach der if-Anweisung kommt auf eine neue Zeile.

Für geschachtelte if-Anweisungen existiert zur Vereinfachung der Schreibweise das Schlüsselwort elseif:

  if {$cmd == "add"} {
    ...
  } elseif {$cmd == "del"} {
    ...
  } else {
    ...
  }
Für Mehrfachverzweigungen benutzt man die switch-Anweisung:
  switch flags value { pat1 body1 pat2 body2 ... }
Der Wert wird der Reihe nach mit den angegebenen Patterns verglichen und der zugehörige Body ausgeführt. Das Pattern default paßt immer, muß aber das letzte Pattern sein. Im Gegensatz zu den meisten Programmiersprachen arbeitet switch auch mit Strings:
  switch $tag {
    Montag {set tagnr 1}
    Dienstag {set tagnr 2}
    ...
    Sonnabend -
    Samstag {set tagnr 6}
    Sonntag {set tagnr 0}
    default {set tagnr -1}
  }
Durch Flags kann gesteuert werden, was Übereinstimmung bedeutet (global style pattern matching, exact oder reguläre Ausdrücke).

Schleifen

Es stehen zwei abweisende Schleifentypen zur Verfügung:
  while expression body
  for initialisierung expression reinit body
Wichtig ist hier, daß expression und reinit unbedingt in geschweifte Klammern (und nicht in Hochkommas) eingeschlossen werden. Die Auswertung erfolgt sonst vor der Ausführung der Schleifenanweisung, diese erhält statt der Ausdrücke u.U. schon feste Werte zugewiesen, statt die Auswertung bei jedem Durchlauf selbst zu machen. Das passiert z.B. dann, wenn der Testausdruck einfach eine Variable ist.
  # Beispiel while-Schleife

  set betrag 1000
  set zinssatz 0.03
  set jahr 0
  while {$betrag < 2000} {
    set betrag [expr $betrag*(1+$zinssatz)]
    incr jahr       # jahr wird um 1 erhöht, hier ohne $ schreiben
  }
  puts "$jahr $betrag"

  # Beispiel for-Schleife
  
  for {set i 0} {$i < 20} {incr i} {
    puts "$i: [expr sqrt($i)]"
  }
Eine weitere praktische Schleife läßt eine Variable über eine vorgegebene Liste (s.u.) von Werten laufen:
  foreach tag {Montag Dienstag Mittwoch Donnerstag Freitag} {
    puts "Am $tag muß ich zeitig aufstehen :-("
  }
Die Liste kann auch in einer Variable gespeichert sein, oder man kann so alle Elemente eines assoziativen Array abarbeiten (s.u.).

Prozeduren

Mit dem Kommando
proc
wird eine neue Prozedur (Funktion) definiert:
  proc name argumente body
Die Argumente werden durch Leerzeichen getrennt und mit {} gruppiert. Der Body enthält meist eine return-Anweisung für die Rückgabe eines Wertes:
  proc umfang {r} {
    set u [expr 2*$r*3.1416]
    return $u
  }
Diese Prozedur kann nach ihrer Definition genutzt werden:
  puts "Umfang bei Radius 4: [umfang 4]"
Ein etwas umfangreicheres Beispiel zur Berechnung des Zinseszins bei festem Zinssatz:
  proc zins {betrag zinssatz laufzeit} {
    set j 1
    while {$j <= $laufzeit} {
      set zinsen [expr $betrag*$zinssatz]
      set betrag [expr $betrag+$zinsen]
      set j [expr $j+1]
    }
    return $zinsen
  }
Um Prozeduren nicht immer interaktiv eingeben zu müssen, ist es sinnvoll, diese in einer Datei abzulegen und diese Datei einzubinden:
  source scriptfile
Alle Variablen in Prozeduren sind lokal, Parameter werden immer als Werte übergeben. Der Zugriff auf globale Variablen sowie die Parameterübergabe als Referenz sind durch spezielle Befehle möglich.

Zeitsteuerung und Ereignisse

Kommandos können zeitverzögert ausgeführt werden:
  after millisec command
Das Kommando ist meist eine Prozedur. Enthält sie als letzte Zeile den gleichen after-Befehl, erfolgt eine periodische Ausführung. Das funktioniert aber nur solange, wie der Tcl-Interpreter läuft. Besonders bei Scriptfiles muß eine vorzeitige Beendigung verhindert werden.

Zum Warten auf Ereignisse existiert der Befehl

  vwait variable
Damit wartet das Programm, bis sich der Wert der angegeben Variable ändert. Das kann durch einen mit after verzögerten Befehl oder durch einen Befehl, der durch ein Datei- bzw. Netzereignis ausgelöst wird, erfolgen.

Wird die Variable nie verändert, wartet das Programm ewig und arbeitet nur Ereignisroutinen ab (z.B. als Dämon).