Einfach Tcl

Richard Suchenwirth - This page ("simply Tcl") is in German for even more i18n;-) It is the draft of a planned presentation. First thought of ppt or Word, but somehow I preferred the Wiki - plus allowing at least German speakers from Tclworld to discuss this while I'm writing it - best place your comments etc. at bottom of page! For a French course, see [L1 ] See also Einfach man Tcl - Tcl/Tk: Programmieren auf dem PocketPC.


Tcl (Tool command Language, ausgesprochen "tickel") ist eine einfache, aber maechtige Programmiersprache. Da Turing-vollstaendig, kann im Prinzip jede Aufgabe in Tcl geloest werden, jedoch nicht immer optimal.

Tcl wird (zusammen mit Perl, Python, VB) auch als Scriptingsprache bezeichnet. Zu "System-Programmiersprachen" wie C, Java, Assembler bestehen folgende Unterschiede:

  • nahezu typfrei ("Everything is a string" / intern schon getypt)
  • Variablen muessen nicht deklariert werden - automatische Speicherverwaltung
  • Ausfuehrung durch Interpreter (byte-kompilierte Prozeduren)
  • Interaktive Nutzung moeglich, reichhaltige Introspektionsmoeglichkeiten
  • Aussagekraeftige Fehlermeldungen (fast "Online-Hilfe"); Weiterarbeit moeglich
  • Compile-Link-Zyklus entfaellt, daher schnelle Entwicklung
  • Wartbarkeit durch Script-Editieren bis zur Zielmaschine
  • Laufzeitverhalten teilweise schlechter als vollkompilierter Code

Tcl kann mit C/C++/Java-Code fuer laufzeitkritische Teile auf verschiedene Weisen verbunden werden:

  • Einbettung (Applikation enthaelt Tcl-Interpreter)
  • Erweiterung (statischer oder dynamisch gelinkter Code wird fuer Tcl zugaenglich gemacht)
  • Tcl_LinkVar: Bindung von Tcl-Variablen an C-Variablen (int, double, boolean, string)
  • Tcl_CreateObjCommand: Bindung eines Tcl-Kommandos an eine C-Implementation
  • externe Server (exec, open: namenlose bidirektionale Pipes)

Tk (ToolKit) ist die wichtigste Tcl-Erweiterung, ein plattformunabhaengiger GUI-Toolkit (X11, Windows, Macintosh):

  • extrem einfache GUI-Spezifikation
  • gleiche Quelldatei laeuft auf mehreren Plattformen mit "native look & feel"
  • Leistungsfaehige "Widgets": canvas, text, menu, scrollbar, Buttons...
  • Einfache aber maechtige Geometrie-Manager: pack, place, grid
  • Tk wird auch mit Perl und Python als GUI-Toolkit verwendet

Weitere bekannte Erweiterungen:

  • [incr Tcl] - OO-Unterstuetzung aehnlich C++
  • Expect - Unterstuetzung von Kommunikation zwischen verschiedenen Rechnern, "Fernsteuerung" von Applikationen
  • BWidget - Erweiterung von Tk um komplexere "Megawidgets" in purem Tcl
  • TclX - Erweiterung um Posix-Systemfunktionalitaeten

Tcl bietet starke Unterstuetzung von Internationalisierung mit Unicode:

  • alle Strings intern in Unicode/UTF-8
  • Stringkonstanten mit \uxxxx koennen jeden Unicode darstellen
  • Hin- und Herkonvertierung in viele andere internationale Encodierungen
  • Tk: automatische Zeichensuche, falls der aktuelle Font ein gewuenschtes Zeichen nicht enthaelt
  • Einfache Lokalisierung mit msgcat

Tcl vereint Konzepte aus verschiedenen Software-Welten:

  • LISP/Scheme: Liste als zentrale Datenstruktur, polnische Notation, Code = Daten
  • C: Kontrollstrukturen (for, while, if); Arithmetik
  • Shells: Trennung von Parsing und Ausfuehrung; cd, pwd, glob...
  • SNOBOL/awk: assoziative Arrays (Hashtables)
  • X/...: Eventmodell, ereignisgesteuerte Verarbeitung
  • ?: Traces - Notifikation bei Lesen/Setzen/Loeschen einer Variablen

Tcl hat die einfachste Syntax aller Scriptingsprachen:

  • keine Schluesselwoerter, alle Kommandos werden gleich behandelt
  • Script: Folge von Kommandos, durch \n oder ";" getrennt
  • Kommando: Folge von Woertern, durch Whitespace getrennt; erstes Wort ist Kommandoname
  • Wort: whitespacelose Zeichenkette, oder gruppiert mit ".." {..}
  • "..": "substituiere und gruppiere mich zu einem Wort"
  • {..}: "gruppiere mich, aber ruehr meinen Inhalt nicht an"
  • Rekursiver Interpreteraufruf fuer [..]-eingebettete Scripts
  • [..]: "evaluiere mich zuerst zu einem String"
  • Variablensubstitution durch $name , Array-Elemente: $name($key)
  • Namensraum-Syntax: ::foo::bar; :: ist der globale Namensraum
  • Alle weitere Syntax liegt in Verantwortung der Kommandos

Kommandos haben folgende Eigenschaften:

  • Implementation in C oder als Tcl-proc
  • Argumente mit Defaultwert oder variabler Anzahl moeglich
  • jedes Kommando parst seinen Input nach eigenem Bedarf
  • Kommandos koennen umbenannt und ueberladen werden
  • Bytecompilation beim ersten Aufruf, danach Wiederverwendung des Bytecodes
  • Neudefinition zur Laufzeit moeglich (proc ist auch nur ein Kommando)
  • auch Kontrollstrukturen sind "nur" Kommandos, koennen ueberladen oder erweitert werden
  • alle Variablen von Procs sind lokal, koennen aber mit globalen Variablen oder Variablen hoeher im Callstack verbunden werden

Strukturierung von Tcl-Applikationen:

  • Auch in Tcl kann man unwartbaren Code schreiben, muss es aber nicht
  • Explizites Laden anderer Scripte mit source (#include zur Laufzeit)
  • Automatisches Laden anderer Scripte bei Bedarf (auto_index)
  • Modularisierung durch ladbare packages (Script und/oder kompiliert)
  • Daten- und Prozedurkapselung durch Namensraeume

Datenhaltung in Tcl:

  • Listen mit automatischer Speicherverwaltung
  • Assoziative Arrays (Hashtables): Schluessel -> Wert
  • Ueber geeignete Schluessel ($i,$j) mehrdimensionale Arrays simulierbar
  • Formatierung von Daten aller Art (auch binaer, mit Nullbytes) in Strings
  • Implizite Datenwandlung Liste <-> String <-> Zahl bei Bedarf

Ablaufsteuerung in Tcl:

  • z.B. konventionell sequentiell bzw. if, for, while, foreach
  • Bindung von Kommandos an GUI-Ereignisse (Maus, Tastatur)
  • Bindung von Kommandos an Datei-Ereignisse (fileevent)
  • Bindung von Kommandos an Timer-Ereignisse (after)
  • Bindung von Kommandos an Daten-Ereignisse (Variablen-Trace)

Die wichtigsten Kommandos:

  • set varname value ;# Wertzuweisung
  • expr mathexpression ;# Arithmetik und Vergleiche
  • proc name arglist {body} ;# Definition einer Prozedur
  • if {condition} {body}
  • while {condition} {body}
  • foreach iterator liste {body} ;# Iteration ueber Liste
  • for {set i 0} {$i<$max} {incr i} {...} ;# analog zu C
  • return ?value? ;# kann weitgehend vermieden werden
  • break ;# aus while/foreach/for-Schleifen
  • catch {body} ?result? ;# faengt Fehler in body auf
  • error "deutliche Fehlerbeschreibung"

Die wichtigsten Listen-Kommandos:

  • list el... ;# Anlage einer Liste aus Einzelelementen
  • lappend listname el... ;# Anhaengen weiterer Elemente
  • lindex list index ;# Extraktion eines Elements anhand Position
  • lrange list from to ;# Extraktion eines zusammenhaengenden Bereichs
  • lsearch list term ;# Suche eines Elements in einer Liste
  • llength list ;# gibt die Laenge der Liste zurueck
  • split string ?splitchars? ;# Zerlegt einen String an splitchars in Listenelemente
  • join list ?joinchars? ;# Fuegt joinchars zwischen je zwei Listenelemente, erzeugt einen String

Die wichtigsten String-Kommandos:

  • string length/is/compare/first...
  • append varname string.. ;# verlaengert den String in varname
  • regexp re string.. ?-> varname1 varname2..?
  • regsub re instring replacement outvarname ;# ersetze re in instring durch replacement, Ergebnis geht nach outvarname

Die wichtigsten I/O-Kommandos:

  • gets channel ?varname? ;# bis Zeilenende (exklusiv)
  • puts channel string ;# mit Zeilenvorschub
  • open filename ?mode? ;# gibt Channel-Id zurueck
  • close channel
  • read channel ?howmuch? ;# ohne Ruecksicht auf Zeilenenden

Welche Probleme gibt es in Tcl nicht?

  • Es gibt nicht einfache Hochkommas und doppelte Anführungszeichen, evtl. auch noch mit verschiedener Bedeutung, sondern nur doppelte Anführungszeichen
  • Es wird nicht je nach Kontext ein einfaches (Zuweisung) und ein doppeltes (Vergleich) Gleichzeitszeichen verwendet, sondern immer nur das doppelte. Zuweisungen geschehen nämlich ohne Gleichzeitszeichen!
  • Bei Unix: Man muss den Pfad zum Tcl-Interpreter nicht in die Script-Datei einbauen. Er wird immer gefunden, sofern er im Pfad (Variable PATH) enthalten ist.

Tcl hat eine kleine, aber qualifizierte weltweite Anwendergemeinschaft, die vor allem auf folgenden oeffentlichen Wegen kommuniziert:


Beispiel: Mittelwert einer Zahlenliste - prozedural oder funktional

 proc mean {L} {
    set sum 0
    foreach element $L {
        set sum [expr {$sum+$element}]
    }
    return [expr {$sum/[llength $L]}]
 }
 #### or: ####
 proc mean L {expr double([join $L +]) / [llength $L]} ;# funktional

Die Elemente der Liste L werden mittels join durch "+"-Zeichen zu einem String verbunden. Dieser wird in expr ausgewertet, durch double in Gleitkommazahl gewandelt (um Integer-Division zu vermeiden) und durch die Laenge der Liste geteilt. Das Ergebnis von expr ist implizit der Rueckgabewert von mean.


Beispiel: deutsche Umlaute normieren

 proc noUmlaut s {string map {Ä AE Ö OE Ü UE ß SS ä ae ö oe ü ue} $s}

string map wird mit einer Liste von alternierend von-nach und einem String aufgerufen nimmt Ersetzungen von von nach nach vor und gibt den geaenderten String zurueck, der auch Rueckgabewert von noUmlaut ist.


Beispiel: Zufaelliges Ziehen ohne Zuruecklegen aus einer Liste

 proc ldraw {_L} {
   upvar 1 $_L L
   set pos [expr {int(rand()*[llength $L])}]
   set res [lindex $L $pos]
   set L [lreplace $L $pos $pos]
   set res
 }

Die rand-Funktion von expr liefert eine Zufallszahl zwischen 0 und <1, die mit der Listenlaenge multipliziert und abgerundet einen gueltigen Listenindex pos ergibt. Das dortige Element wird geholt und mit lreplace aus der Liste entfernt, die deshalb ueber Name und upvar uebergeben werden muss.


Beispiel: Spiegeln einer Liste

 proc lrevert L {
    set i [llength $L]
    set res {}
    while {$i} {lappend res [lindex $L [incr i -1]]}
    set res
 }
 % lrevert {foo bar grill test}
 test grill bar foo

Die Schleife ueber die Elemente der Liste beginnt mit N-1 (N als Laenge der Liste), da String- und Listenindizes ab 0 zaehlen, und endet mit 0. Die entsprechenden Listenelemente werden mit lindex geholt und mit lappend an die Ergebnisliste res gehaengt. set res ist als letztes Kommando einer proc aequivalent zu return $res.


roho Statt "incr $i -1" muss es "incr i -1" heissen hae 2008.09.18: korrigiert; Beispiel funktioniert jetzt


Beispiel: Introspektion des eigenen Proc-Namens

 proc myProcName {} {lindex [info level -1] 0}

info level gibt Zugriff auf den Aufrufstapel (callstack), info level -1 das Kommando, mit der der Aufrufer dieser Prozedur aufgerufen wurde. Das erste Element des Kommandos (lindex ... 0) ist definitionsgemaess der Name.


Beispiel: Zahlengenerator Diese Prozedur gibt bei jedem Aufruf eine andere Zahl zurueck, aufsteigend 1,2,3,.... Bei jedem Aufruf definiert sie sich neu, wobei der Body unveraendert bleibt (sie "schaut in sich selbst", aber der Defaultwert fuer den optionalen Parameter seed inkrementiert wird.

 proc intgen {{seed 0}} {
      proc intgen "{seed [incr seed]}" [info body intgen]
      set seed
 }

Beispiel: Konstanthalter Die Variable name wird auf den Wert value gesetzt, und zwar im Skopus des Aufrufers (uplevel 1). Dann wird ebenfalls dort ein Variablentrace gesetzt, der bei schreibenden Zugriffen auf name den urspruenglichen Wert zuruecksetzt:

 proc const {name value} {
   uplevel 1 [list set $name $value]
   uplevel 1 [list trace add variable $name write "set $name [list $value];#" ]
 }
 % const Pi 3.14159
 % set Pi 4
 3.14159

Beispiel: Ueberladen eines Kommandos Jedes Tcl-Kommando, auch das elementarste set, kann ueberladen werden, um z.B. Zusatzfunktionalitaet hinzuzufuegen (etwa auf stdout die Aktivitaet anzuzeigen). Dazu wird das Original zunaechst umbenannt, um es nicht zu verlieren:

 rename set _set
 proc set {name args} {
    if [info level]>1 {puts -nonewline [info level -1]:}
    puts [info level 0]
    uplevel 1 _set $name $args
 }

So entsteht mit wenig Aufwand Debug-Funktionalitaet, die sich wieder ausschalten laesst mit

 rename set _; rename _set set; rename _ "" ;# loeschen

Beispiel: Klagloser Incrementor Das Tcl-Kommando incr leistet schnelle Integer-Addition bzw. Subtraktion, vorausgesetzt die Variable existiert bereits. Damit sie im anderen Fall automatisch angelegt wird (wie bei awk ueblich), faktorisieren wir die sonst erforderliche Existenzpruefung an diese proc aus:

 proc inc {varname {amount 1}} {
    upvar 1 $varname var
    if {![info exist var]} {set var 0}
    incr var $amount
 }

Hier wird die lokale Variable var an den in varname uebergebenen Namen einer (moeglichen) Variablen (kann auch Array-Element sein) im Skopus des Aufrufers gebunden. Damit kann var geprueft, intialisiert, inkrementiert werden.. und fuer den Aufrufer ist das gleiche mit $varname geschehen.


Beispiel: Zeichenhaeufigkeit

 proc charFreq string {
    foreach char [split $string ""] {
       inc t($char)
    }
    foreach {char count} [array get t] {
       lappend pairs [list $char $count]
    }
    lsort -integer -decreasing -index 1 $pairs
 }

Der string wird in Einzelzeichen gesplittet, ueber die iteriert und ein Element in dem temporaeren Array t klaglos inkrementiert wird. Der Inhalt des Arrays, eine ungeordnete flache Liste von alternierend Zeichen und Anzahl, wird in eine Liste von Paaren gewandelt, die dann bequem nach absteigender Haeufigkeit sortiert werden kann. String kann Megabytes lang und natuerlich auch koreanisch, griechisch oder arabisch sein...

 % charFreq Tennessee
 {e 4} {n 2} {s 2} {T 1}

Beispiel: Fehlertolerantes Lesen einer Textdatei

 proc readfile {filename} {
    if ![catch {open $filename} fp] {
        set res [read $fp [file size $filename]]
        close $fp
    } else {set res {}}
    return $res 
 }

Wenn das Oeffnen der Datei filename gelingt (der catch nicht anschlaegt), wird sie in ganzer Laenge in die Variable res gelesen und zurueckgegeben. Andernfalls wird ein leerer String zurueckgegeben. Moegliche Anwendung als Zeilenzaehler:

 proc wc-l filename {llength [split [readfile $filename] \n] }

Beispiel: Textausgabe in Datei, mit Sicherheitskopie

 proc text2file {text filename} {
    if [file exists $filename] {
        file rename -force $filename $filename.bak
    }
    set    fp [open $filename w]
    puts  $fp $text
    close $fp
 }

Existiert eine Datei namens filename, so wird sie in (filename).bak umbenannt. Dann wird eine neue Datei des Namens zum Schreiben geoeffnet, der uebergebene String (kann beliebig lang sein) mit abschliessendem Zeilenvorschub hineingeschrieben, und die Datei geschlossen.


Beispiel: Internet-Dateiherunterlader

 package require http
 http::config -proxyhost proxy -proxyport 80
 puts [http::data [http::geturl [lindex $argv 0]]]

Verwendet wird das mit Tcl mitgelieferte package http. Mit http::config wird der hier uebliche Proxy-Rechner eingestellt. Die bei Aufruf des Scripts angegebene URL wird geladen und ihr Datenanteil (der eigentliche Inhalt) auf stdout ausgegeben, kann jedoch in eine Datei umgeleitet werden:

 $ tclsh ~/tcl/wwwget.tcl www.bahn.de/img/agb.gif > t.gif

Beispiel: ein kleines Zeichenprogramm

 proc doodle {w {color black}} {
    bind $w <1>         [list doodle'start %W %x %y $color]
    bind $w <B1-Motion> {doodle'move %W %x %y}
 }
 proc doodle'start {w x y color} {
        set ::_id [$w create line $x $y $x $y -fill $color]
 }
 proc doodle'move {w x y} {
        eval $w coords $::_id [concat [$w coords $::_id] $x $y]
 }
 pack [canvas .c]
 doodle       .c

Fuer den Canvas werden Bindungen fuer "Mausklick links" (neue Linie beginnen, deren Anfang und Ende zusammenfallen: doodle'start) und "Mausbewegung mit gedrueckter linker Taste" (aktuelle Linie, durch globale Variable ::_id zugaenglich, zum Zielort der Bewegung verlaengern: doodle'move) definiert.


Beispiel: Digitaluhr in 6 Zeilen

 proc every {ms body} {
     eval $body
     after $ms [list every $ms $body]
 }
 pack [label .clock -textvar time]
 every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]}

Zunaechst wird ein einfacher, aber wiederverwendbarer Timer, every, definiert, der den uebergebenen body (Tcl-Kommandos) ausfuehrt und anschliessend veranlasst, dass er nach ms Millisekunden erneut aufgerufen wird. Dies ist keine Rekursion! UI: Ein Label wird angelegt, an die Textvariable time gebunden, und in das Hauptfenster "gepackt". Mit every 1000 ... wird der Wert von time sekuendlich auf die aktuelle Zeit gebracht und automatisch im Label angezeigt.


Beispiel: Analoguhr in etwas mehr Zeilen

 proc drawhands w {
     $w delete hands
     set secSinceMidnight [expr {[clock seconds] - [clock scan 00:00:00]}]
     foreach divisor {60 3600 43200} length {45 40 30} width {1 3 7} {
         set angle [expr {$secSinceMidnight * 6.283185 / $divisor}]
         set x [expr {50 + $length * sin($angle)}]
         set y [expr {50 - $length * cos($angle)}]
         $w create line 50 50 $x $y -width $width -tags hands
     }
 }
 pack [canvas .c -width 100 -height 100 -bg white]
 every 1000 {drawhands .c}

Ein canvas (Zeichenflaeche) mit 100*100 Pixeln Groesse wird angelegt. Jede Sekunde werden die Uhrzeiger weggeworfen und als drei line-Items neu gezeichnet, wobei der Zielpunkt des Zeigers ueber Winkelfunktionen bestimmt wird. Man beachte die Mehr-Listen-Form von foreach, die in drei Durchlaeufen die drei Iterator-Variablen aus den konstanten Listen belegt. Der Timer every aus der Digitaluhr wird wiederverwendet.


Beispiel: eine umschaltbare Uhr Zuletzt eine Uhr, die durch Mausklick von analog zu Digital und zurueck wechselt. Dazu ein wiederverwendbarer Umschalter, der das sichtbare von zwei Widgets unsichtbar (pack forget) und das andere sichtbar macht:

 proc toggle {w1 w2} {
     if [winfo ismapped $w2] {
         foreach {w2 w1} [list $w1 $w2] break ;# swap
     }
     pack forget $w1
     pack $w2
 }

Man beachte das Idiom zum Vertauschen zweier Variablenwerte (w1,w2) mit foreach. Schliesslich legen wir die beiden Uhren an und setzen sie in Gang, "packen" zunaechst die Analoguhr und binden den linken Mausklick (<1>) im Hauptfenster (.) an die Umschaltung:

 canvas .analog -width 100 -height 100 -bg white
 every 1000 {drawhands .analog}
 label .digital -textvar time -font {Courier 24}
 every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]}
 pack .analog
 bind . <1> {toggle .analog .digital}

Diskussion: JCW - I'd like to make a few comments:

  • When you say everything is a string, please do mention that this is only at the conceptual level. Internally, things run at the speed of typed data.
  • The first example, Mittelwert/average, is of course very clever, but are you sure you want to bring this across as first example? How about pack [button -text "Hello world"]?
  • No mention of how trivial networking is? TclHttpd? AOL?

But above all... it's great to see Tcl summarized this way!


RS (1) fixed. (2) Added procedural counter-example. (3) Know too little... ;-)


Sorry, I messed up all Umlauts wdb: repaired, don't worry


MSW: but I do know how easy networking is. (back to german)

Beispiel: Ein einfacher Zeitserver

 proc server_handle {chan client port} {
    puts stderr "Neue Verbindung von $client:$port"
    lappend ::clients $chan
    fconfigure $chan -blocking 0 -buffering line
 }
 set clients [list]
 every 1000 { foreach c $::clients { puts $c [clock format [clock seconds] -format %H:%M:%S] } }
 socket -server server_handle 15000
 vwait banzai

Zuerst wird ein Kommando definiert, das immer dann aufgerufen wird, wenn auf einem socket neue Verbindungen anstehen. Die entsprechenden, benoetigten Daten werden dem Kommando uebergeben. Der server_handle haengt daraufhin den geoeffneten Socket an eine globale Liste; Jede Sekunde schickt der Server dann an alle sockets in dieser Liste einen Zeitstempel. Das vwait wird benoetigt um Tcl in die Eventloop zu versetzen, d.h. das tcl auf Ereignisse warten & reagieren kann.

Zu diesem Zeitpunkt laeuft der Server und ist bereit - man kann ihn ausprobieren mit z.B. telnet:

 $ telnet localhost 15000
 Trying 127.0.0.1...
 Connected to localhost.
 Escape character is '^]'.
 23:11:10
 23:11:11
 ... usw.

Alternativ kann man natuerlich auch einen kleinen client bauen:

Beispiel: Eine Netzwerkuhr

 proc get_time_net {server port lab} {
    set chan [socket $server $port]
    fconfigure $chan -buffering line
    fileevent $chan readable "trans_time from $chan to $lab"
 }
 proc trans_time {from chan to lab} {
    $lab configure -text [gets $chan]
    if {[eof $chan]} { $lab configure -text {Keine Verbindung zum Server} ; catch { close $chan } }
 }
 pack [set lab [label .l]]
 get_time_net localhost 15000 $lab

get_time_net oeffnet einen socket zu besagtem server/port, und richtet einen callback ein, der aufgerufen wird, sobald Daten auf dem socket anstehen. Der callback wiederum liest die Daten, und passt den Text des labels an. Hier braucht man kein vwait - das Beispiel benutzt die wish, welche automatisch in der eventloop ist.

Anmerkung: Der Server vertraegt es nicht, wenn ein client seine Verbindung wieder verliert. Um den Server vor solchen Problemen zu beschuetzen baut man...

Beispiel: Ein stabiler Zeitserver

 proc server_handle {chan client port} {
    puts stderr "Neue Verbindung von $client:$port."
    lappend ::clients $chan
    fconfigure $chan -blocking 0 -buffering line
    fileevent $chan readable "client_handle $chan $client $port"
 }
 proc client_handle {chan client port} {
    gets $chan
    if {[eof $chan]} { 
        catch { close $chan }
        set ::clients [lreplace $::clients [set idx [lsearch $::clients $chan]] $idx]
        puts stderr "Verbindung zu $client:$port geschlossen."
    }
 }
 set clients [list]
 every 1000 { foreach c $::clients { puts $c [clock format [clock seconds] -format %H:%M:%S] } }
 socket -server server_handle 15000
 vwait banzai

Dieser Server liest alle Anfragen vom client, nur um sie wegzuschmeissen - und das tut er nur, um herauszufinden, ob die Verbindung zum Client unterbrochen ist (via eof) . Falls ja, loescht er ihn aus seiner Verbindungsliste.

Beispiel: Ein tcl server

 rename exit __exit__

 proc server_handle {chan client port} {
    append ::buf($client) [gets $chan]
    if {[info complete $::buf($client)]} {
        rename puts _puts; proc puts args "_puts $chan \[lindex \$args end\]" ;# damit alle ausgaben beim client landen
        proc exit args "catch { close $chan}" ;# damit der client sich, und nicht den Server mit 'exit' beendet
        catch {set reply [uplevel $::buf($client)]} reply
        rename puts {} ; rename _puts puts ; rename exit {}
        set ::buf($client) ""
    } else { puts -nonewline $chan ">> "; flush $chan }
    if {[catch {eof $chan}] || [eof $chan]} {
        catch { close $chan }
        set ::clients [lreplace $::clients [set idx [lsearch $::clients $chan]] $idx]
        puts stderr "Verbindung zu $client:$port geschlossen."
    } elseif {[info exists reply]} {
        puts -nonewline $chan " --> $reply\n% "; flush $chan
    }
 }
 set clients [list]
 socket -server server_handle 15000
 vwait chaos

Ein komplexeres Beispiel - der Server liest Anfragen von den clients, und prueft (mit info complete) ob diese Anfragen vollstaendige tcl Kommandos sind. Falls ja, fuehrt er sie aus, faengt etwaige Fehler (catch) ab, und schickt die Antwort an den client - wobei er auch gleich prueft ob die Verbindung zum client noch besteht. Die Funktion exit, die normalerweise den Server beenden wuerde, beendet nun den client; puts wurde geaendert (via rename) , automatisch nur auf den socket zu schreiben, und eventuelle vorherigen argumente (wie einen anderen channel oder ein -nonewline) zu ignorieren.

Wie gehabt, der Server laeuft zu diesem Zeitpunkt, und kann mit z.B. telnet getestet werden.

Wie man an diesen Beispielen sehen kann, ist die Kommunikation ueber sockets (fast) genauso einfach wie ueber stdout oder in Dateien. Die gezeigten Beispiele sind jedoch sehr low-level, es gibt zahlreiche Bibliotheken, mit denen man auch auf einem hoeheren Niveau uebers Netzwerk kommunizieren kann.


MSW: Noch ein Kommentar zu den vorgestellten Erweiterungen: Viele Tcl Programmierer (die ich kenne) kennen Tk schon aus scheme (z.B. aus STK), und suchen in Tcl oft nach einer CLOS/STKlos aehnlichen Objektumgebung. Otcl als CLOS-like OO Erweiterung zu erwaehnen (nach incr tcl) waer glaub ich noch fein :)


maidquellrasen - 2016-01-26 16:03:14

Muesste beim for-Kommando nicht statt

 for {set i 0} {$i<$max} {incr i} {...} ;# analog zu C

besser mit expr verglichen werden, weil "3">"11" wenn ich Strings betrachte?

 for {set i 0} {expr {$i<$max}} {incr i} {...} ;# analog zu C