freebsdzine.org
Never commit yourself! Let someone else commit you.

[ Home  · Contribute  · Mailing Lists  · Contact Us  · Site Statistics  · Latest BSD News  · Submit an Article  ]

Search freebsdzine.org

FreeBSD 'zine Polls

I like...

men
women
men and women
pie
beer
uhhh, what?
moses

Results  · More polls


Sections
· Wanted articles
· About the site
· The staff
· Copyright info
· Privacy policy
· Change log

Resources
· The FreeBSD Project
· The FreeBSD Diary
· BSD Today
· Daemon News
· Daily Daemon News
· Slashdot BSD
· FreshPorts
· The FreeBSD Mall
· BSDVault
· The FreeBSD Browser

FreeBSD Books
· Complete FreeBSD
· FreeBSD Handbook
· FreeBSD Corporate
Networker's Guide


Runs on FreeBSD
Why You Should Know Tcl/Tk
Maurice Castro <[email protected]>

Introduction

Tck/Tk (Tool Command Language / Toolkit) is a scripting language bundled with a collection of graphical libraries. The language and libraries are supported across many platforms, including UNIX and Windows. I have successfully used the language in three roles:

  • Desktop utility applications.

  • Rapid application development.

  • Prototyping of graphical aplications.

The language and libraries have been very successful in these roles.

From a system administrator's point of view, the ability to provide a custom graphical user interface to the user rapidly is highly attractive since users have come to expect that interfaces will incorporate graphical elements such as buttons, lists, and forms rather than simple text interfaces. Like WWW applications, Tcl/Tk provides a cheap and portable interface option. The essential difference between Tcl/Tk and a web interface is that Tcl/Tk is typically used on the local machine rather than in a client/server environment.

This article will provide a brief description of the flavor of the language and will provide examples of its use.

Tcl/Tk

Tcl

Unlike C, Java, Pascal, and other programming languages, Tcl has an unusual structure. In most other programming languages, a parser recognizes punctuation elements such as braces (`{' and `}'), and uses these to determine the blocks of the program. This allows the programmer considerable freedom to format their program as they wish. For example, in C:

if (a > b) {
    printf("a > b");
}

if (a > b)
{
    printf("a > b");
}

if (a > b) { printf("a > b"); }

if (a > b)
    {
        printf("a > b");
    }

The structure of Tcl is simple. A script is composed of a series of commands separated by either semicolons or newlines. A command consists of a command name followed by a series of arguments. Before a command is parsed, the interpreter performs a series of substitutions on the command. These substitutions are controlled by braces, square brackets, double quotation marks, and backslashes. The values of variables are substituted when the variable name is prefixed by a dollar sign.

A direct consequence of this simple structure is that the programmer is restricted in how they format their code. Breaking into long lines requires the use of a backslash. Braces must be placed to control the parsing of the command forcing the programmer to either place the brace before a new line, or finish a line with a backslash. Many programmers regard the language as "ugly" because of these restrictions and the lack of "syntactic sugar" found in other programming languages.

I would encourage you to look beyond the constraints of the simple structure of the language and observe the power of the product it lets you produce with minimal effort.

The rules of the language parser are:

  • If any other character follows the backslash, the backslash is dropped and the next character is incorporated into the current word. This characteristic allows special characters such as $ and [ to be included into words.

  • Double quotes disable word and command separators.

  • Braces disable most special characters.

  • Command substitution -- characters in square brackets are treated as commands and are replaced with the result of the command.

  • Variable substitution -- names prefixed with dollar sign are replaced with the contents of the variable.

  • Lists -- a list consists of items and sublists which are separated by spaces. Sublists are enclosed in braces.

Some of the commands provided by Tcl are:

Command Description
append Append to variable
break Abort looping command
case Evaluate one of several scripts depending on a given value
close Close to an open channel
concat Join lists together
continue Skip to the next iteration of a loop
eof Returns 1 if end of file, otherwise 0
exit Terminate the process
expr Evaluate an expression
flush Flush stream
for A looping command similar in structure to the C "for" statement
foreach Iterate over all elements in one or more lists
gets Read a line from a channel
if Execute scripts conditionally
incr Increment variable
lappend Append to list
lindex Return element from list
linsert Insert into list
llength Return length of list
lreplace Replace elements in list
lsearch Find element in list
lsort Sort list
open Open stream
puts Output to stream
proc Create a procedure
read Read from stream
regexp Match regular expression
regsub Substitute using regular expression
scan Parse string using conversion specifiers in the style of sscanf
set Set a variable to a value
split Split a string into a proper Tcl list
switch Evaluate one of the several scripts depending on a given value
unset Destroy a variable
while Execute script repeatedly as long as a condition is met

The following program implements part of the functionality of wc(1) -- the UNIX word count program:

#!/usr/local/bin/tclsh8.0

foreach file $argv {
  set lines 0
  set chars 0
  set filevar [open $file]
  set charcnt [gets $filevar line]
  while {$charcnt >= 0} {
      set chars [expr ($chars + $charcnt +1)]
      incr lines
      set charcnt [gets $filevar line]
  }
  puts "\t$lines\t$chars\t$file"
  close $filevar
}

The program accepts a list of filenames on the command line and counts the number of lines and characters in each file. The number of lines and characters is output after each file is processed.

Tk

Tk is a windowing toolkit accessed through Tcl commands. Commands are available to produce windows, buttons, labels, text boxes, menus, and other graphical items. In general, a command generates a graphical object with a name called a widget. This widget can then be used as a command to manipulate the object and as an argument to commands that control the placement and actions of graphical objects. For example, the following code produces a button that can be used to finish an Xsession if it is included as the last command in your .Xsession file:

#!/usr/local/bin/wish8.0

button .logout -text "Logout" -command exit
pack .logout

The command button generates a button object which is packed into the top level window (note that objects must be packed to become visible). The command exit is bound to the button. This binding causes the command exit to be executed when the button is pressed. When run, the program generates the following window:

Figure 1.0
Figure 1.0

Some of the commands provided by Tk are:

Command Description
bind Bind a script to an even in a graphical object
button Create a button
canvas Create a drawing space
checkbutton Create a checkbutton
destroy Destroy a graphical object
entry Create a text entry field
frame Create a frame which may hold graphical objects
label Create a text label
listbox Create a listbox
menu Create a menu
menubutton Create a button in a menu
pack Geometry manager for objects
place Geometry manager for objects
radiobutton Create a radio button
scrollbar Create a scrollbar
text Create a multi-line text display
tk_dialog A dialog box
tk_getOpenFile A file open dialog box
tk_getSaveFile A file save dialog box
tk_messageBox A message box
toplevel A new top level window
winfo Get information about a window
wm Control the window manager
Example Applications

System Monitor

One of the network I maintain has a collection of poorly-behaved switches. These switches are notorious for their poor spped auto-sensing and appear to sometimes forget their manual port configuration. After a particularly annoying incident where a machine sent over a thousand emails to our mail server because it was disconnected by the switch for an extended period of time and then became reconnected, I was driven to write the following application.

This application pings each machine in the .machines file once every ten minutes to determine if the connection is alive. The machine name changes color to indicate if the machine responds. A count of the number of minutes since the last successful ping is displayed next to failed entries.

This example illustrates:

  • Reading a file.

  • Generating a simple text display.

  • Generating a timed update of a display.

  • The application of exec to execute an external program and read the results from it.

#!/usr/local/bin/wish8.0

# Configuration section

set checkinterval 10
set machinefile [join "$env(HOME) .machines" "/"]

# Derived variables

# Configure frame info
wm title . "xchks"

# Configure toplevel window
. configure -background white

# Read machine list
catch {
	set n 1
	set f [open $machinefile]
	set l [gets $f]
	while {! [eof $f]} {
		label .m$n -text $l -background white
		pack .m$n
		lappend machinelist .m$n
		set n [ expr $n + 1 ]
		set l [gets $f]
	}
	close $f
}

update idletasks

proc checkmachs {} {
	upvar #0 machinelist machinelist
	upvar #0 checkinterval checkinterval
	set ping /sbin/ping
	foreach i $machinelist {
		set l [$i cget -text]
		set m [lindex $l 0]
		set c [lindex $l 1]
		$i configure -foreground black
		update idletasks
		$i configure -foreground orange
		catch {exec $ping -c 1 -q $m} r
		if {[lindex $r 14] == "1"} {
			$i configure -text $m -foreground green
		}
		if {[lindex $r 14] == "0"} {
			$i configure -text [list $m $c] -foreground red
		}
		update idletasks
	}
	update idletasks
	after [expr $checkinterval * 60 * 1000] checkmachs
}

checkmachs

Shown below is the output of the program with two machines that have answered successfully, one machine in failure, and one machine whose domain does not resolve.

Figure 2.0
Figure 2.0

The application took less than half a morning to write and debug.

mpg123 Graphical Shell

mpg123 is a popular command line mp3 player. The player supports of number of unusual features, including the ability to play mp3s of the Network Audio System (NAS). We use NAS to provide sound on our X-terminals. One of our users suggested they would like a graphical shell to control mpg123... an afternoon's work later, it was done.

This shell exploits mpg123's built-in remote protocol. The protocol is not as clean as that used by other front-ends to mpg123, but it works. Also, we were not forced to rebuild the player and redistribute the binaries.

This example illustrates:

  • Generating an interactive text display.

  • Using open to execute an external program, and writing commands to the external program and reading results from it.

  • Using listboxes and scrollbars.

  • Using the file open dialog.

#!/usr/local/bin/wish8.0

set MPG123 "/usr/local/bin/mpg123 -R -"

set Player ""
set NextTrack ""
set PlayerStarted 0

frame .playlist
label .playlist.label -text "Play List"
scrollbar .playlist.yscroll -command ".playlist.list yview"
scrollbar .playlist.xscroll -orient horizontal \
    -command ".playlist.list xview"
listbox .playlist.list -width 20 -height 10 -setgrid 1 \
    -yscroll ".playlist.yscroll set" -xscroll ".playlist.xscroll set" \
    -selectmode multiple
pack .playlist.label -side top
pack .playlist.xscroll -fill x -side bottom
pack .playlist.list -fill both -side left -expand yes
pack .playlist.yscroll -fill y -side right

frame .buttons1
button .buttons1.start -text "Start" -command StartProc
button .buttons1.stop -text "Stop" -command StopProc
button .buttons1.skip -text "Skip" -command SkipProc
button .buttons1.pause -text "Pause"
pack .buttons1.start .buttons1.stop .buttons1.skip .buttons1.pause \
    -fill x -side left -pady 2 -padx 2

frame .buttons2
button .buttons2.add -text "Add" -command AddProc
button .buttons2.delete -text "Delete" -command DeleteProc
pack .buttons2.add .buttons2.delete -fill x -side left -pady 2 -padx 2

button .quit -pady 2 -padx 2 -text "Quit" -command QuitProc

frame .info
label .info.title -width 30 -justify left -anchor w
label .info.artist -width 30 -justify left -anchor w
label .info.album -width 30 -justify left -anchor w
pack .info.title
pack .info.artist
pack .info.album

label .status

pack .playlist -expand yes -fill both
pack .button1 .buttons2 .quit .info .status

proc AddProc {} {
	set types {
		{{MP3}		{.mp3 .MP3} }
		{{Play List}	{.m3u .M3U} }
	}
	set file [tk_getOpenFile -filetypes $types]
	if {"$file" == ""} {
		return
	}
	if {[regexp -nocase "^.*\.m3u" $file]} {
		regsub {[^/\]*$} $file "" prefix
		set f [open $file r]
		while {[ eof $f ] != 1} {
			set l [gets $f]
			if {"$l" == ""} {
				continue
			}
			.playlist.list insert end "$prefix$l"
		}
		return
	}
	.playlist.list insert end $file
}

proc QuitProc {} {
	upvar #0 Player Player
	catch {StopPlayer}
	catch {close $Player}
	exit
}

proc StartPlayer {} {
	upvar #0 PlayerStarted PlayerStarted
	upvar #0 Player Player
	upvar #0 MPG123 MPG123
	if {!$PlayerStarted} {
		set Player [open "|MPG123" w+]
		fconfigure $Player -blocking 0
		set PlayerStarted 1
	}
	.status configure -text ""
}

proc StopPlayer {} {
	upvar #0 Player Player
	upvar #0 PlayerStarted PlayerStarted
	if {$PlayerStarted} {
		puts $Player "QUIT"
		flush $Player
	}
	set PlayerStarted 0
}

proc DoPlayList {} {
	upvar #0 NextTrack NextTrack
	set nlist [.playlist.list curselection]
	if {"$nlist" == ""} {
		set tracks [.playlist.list get 0 end]
	} else {
		foreach n $nlist {
			lappend tracks [.playlist.list get $n]
		}
	}
	set NextTrack ""
	if {"$tracks" != ""} {
		set t [lindex $tracks 0]
		Command "LOAD $t"
		set NextTrack [lrange $tracks 1 end]
	}
}

proc DeleteProc {} {
	set nlist [.playlist.list curselection]
	while {"$nlist" != ""} {
		set n [lindex $nlist 0]
		.playlist.list delete $n
		set nlist [.playlist.list curselection]
	}
}

proc StopProc {} {
	upvar #0 NextTrack NextTrack
	set NextTrack ""
	Command "STOP"
}

proc SkipProc {} {
	Command "STOP"
}

bind .buttons1.pause <ButtonPress-1> PauseProc

proc PauseProc {} {
	Command "PAUSE"
}

proc StartProc {} {
	StartPlayer
	DoPlayList
}

proc Command {Cmd} {
	upvar #0 Player Player
	upvar #0 PlayerStarted PlayerStarted
	if {[catch {DoCommand $Cmd}] != 0} {
		.status configure -text "Player Error"
		catch {close Player}
		set PlayerStarted 0
	}
}

proc DoCommand {Cmd} {
	upvar #0 Player Player
	upvar #0 PlayerStarted PlayerStarted
	if {$PlayerStarted} {
		puts $Player $Cmd
		flush $Player
	}
}

proc StatusMsgNextItem {} {
	upvar #0 NextTrack NextTrack
	upvar #0 Player Player
	upvar #0 PlayerStarted PlayerStarted
	while 1 {
		if {$PlayerStarted} {
			set m [gets $Player]
			if {[regexp "^@I ID3:" $m]} {
				.info.title configure \
				    -text [string range $m 7 36]
				.info.artist configure \
				    -text [string range $m 37 66]
				.info.album configure \
				    -text [string range $m 67 96]
			} elseif {[regexp "^@I " $m]} {
				.info.title configure \
				    -text [string range $m 2 end]
				.info.artist configure -text ""
				.info.album configure -text ""
			} elseif {[regexp "^@P 0" $m]} {
				.info.title configure -text ""
				.info.artist configure -text ""
				.info.album configure -text ""
				.status configure -text ""
				if {"$NextTrack" !- ""} {
					set t [lindex $NextTrack 0]
					Command "LOAD $t"
					set NextTrack \
					    [lrange $NextTrack 1 end]
				}
			} elseif {[regexp "^@P 1" $m]} {
				.status configure -text "Paused"
			} elseif {[regexp "^@P 2" $m]} {
				.status configure -text ""
			} elseif {[regex[ "^@E " $m]} {
				.status configure \
				    -text [string] range $m 2 end]
			}
		}
		update
	}
}

StatusMsgNextItem

Shown below is the output of the program:

Figure
		  3.0
Figure 3.0 (click to enlarge)

Summary

This article provided a brief description of the language and examples of simple graphical tools. Tcl/Tk has made possible the rapid development of functional, useful tools. Development of similar tools in other environments would have been both slower and more complex.

Further Information

For more information, you can consult:

  • The Tcl/Tk Core web site. Many useful links can be found off this page.

  • Tcl and the Tk Toolkit by John Ousterhout, Addison-Wesley, ISBN 0-201-63337-X

  • Tcl/Tk: Pocket Reference by Paul Raines, O'Reilly & Associates, ISBN 1-56592-498-3

- Maurice

Return to the February 2001 Issue



Issues
2001
· March
· February
· January

2000
· December
· August
· July
· June
· May
· April
· March
· February

1999
· January

Other issues from 1999 are available in the attic for now.

Other News
· Slashdot
· FreeBSD Diary
· BSD Today
· FreshPorts
· Daemon News
· OS Online
· Rootprompt
· Maximum BSD

Miscellaneous
· Jim's site

IRC
#freebsdzine
If you'd like to hang out with us and talk about the site, join us in #freebsdzine on Undernet.

Backend
You can add a list of our latest issue's articles to your site by using our RDF/RSS file. You can also add it to your My Netscape page, or add our slashbox once you log in over at Slashdot.

[ Home  · Contribute  · Mailing Lists  · Contact Us  · Site Statistics  · Latest BSD News  · Submit an Article  ]

Copyright © 1998-2001 · The FreeBSD 'zine · All rights reserved.