Originally Published: Thursday, 21 December 2000 Author: Mark Stone
Published to: featured_articles/Featured Articles Page: 1/1 - [Printable]

Tk: The Forgotten Language (part 2)

In the first installment of our series on Tcl and its graphical extension, Tk, we introduced you to the concepts and some of the basic syntax required to code a small application. It's likely that you were surprised by the simplicity and flexibility that the Tcl/Tk approach offered, as you saw just how easy it was to rapidly develop a prototype. Today, we'll finish where we left off - coding the backend and completing our network configuration tool.

   Page 1 of 1  

Building an Application: Procedures

So let's look at how we use this script to bring up an ethernet connection. This work gets done by the procedure startNet, which gets passed one parameter: a text string with a value of either "osdn", "dsl", or "va".

Tcl is a full featured scripting language with all of the control structures and data structures one would expect. We have relatively little control flow to worry about in this script; mainly the script is a front end to bash shell commands. Let's look at the whole startNet procedure, and then walk through it:

proc startNet {netHost} {

switch $netHost {
osdn { set netList [osdnNet] }
dsl { set netList [dslNet] }
va { set netList [vaNet] }
}
set ipNum [lindex $netList 0]
set netM [lindex $netList 1]
set gateW [lindex $netList 2]
exec ifconfig eth0 $ipNum up netmask $netM
exec route add default gw $gateW eth0
}

Our goal here is simple: we need correct settings for IP number, netmask, and gateway for each of three connection locations. Once we have those values, we'll insert them into the ifconfig and route commands to bring up an ethernet connection.

A word about Tcl variable notation and variable assignments. In Tcl you refer to a variable directly simply by using the variable name. You refer to the value assigned to that variable by preceding the variable name with a "$". The command to assign a value to a variable is the set command, which has the format "set variable value". You can send the value of a variable to standard output using the puts command. In other words:

set foo bar
puts $foo

would print the string "bar" to standard output.

Tcl has a very sophisticated set of commands for manipulating lists, so lists are the fundamental data structure in Tcl. Once you become adept at handling lists, you'll find you seldom need to use arrays.

Finally, note that command execution can be nested within a Tcl command line, using brackets for delineation; commands nested within the innermost brackets are executed first.

Now let's look at what we've done. Our first command is the switch command:

switch $netHost {

osdn { set netList [osdnNet] }
dsl { set netList [dslNet] }
va { set netList [vaNet] } }

Switch is a convenience that's equivalent to a series of "if... then... elseif" statements. The switch command says to check a value, $netHost in our case, and to execute a command sequence flagged by a string match to that value. In other words, if the value of netHost is "osdn", execute the command sequence following the string "osdn", etc.

The command sequence, in our case, is just an instance of the set command. We're going to assign a value to the netList variable. As the variable name implies, the value assigned to netList is a list, in our case a three element list consisting of an IP number, an netmask value, and a gateway address. We'll get this list by another procedure call.

In other words, when we execute the command:

set netList [osdnNet]

We are calling a procedure named osdnNet, passing no parameters to it, and asking it to return a list that will be assigned to netList.

The three procedures osdnNet, dslNet, and vaNet couldn't be simpler. Here they are:

proc osdnNet {} {

set e0 "192.168.254.200"
set nm "255.255.255.0"
set gw "192.168.254.1"
lappend nList $e0 $nm $gw
return $nList
}

proc dslNet {} {

set e0 "192.168.1.124"
set nm "192.168.255.0"
set gw "192.168.1.1"
lappend nList $e0 $nm $gw
return $nList }

proc vaNet {} {

set e0 "192.168.4.209"
set nm "255.255.255.0"
set gw "192.168.4.1"
lappend nList $e0 $nm $gw
return $nList }

The lappend command stands for list append. It takes a list and elements to the end of the list. If the list does not already exist, then it creates a list with the indicated elements. So the value of nList will look something like this:

{192.168.254.200 255.255.255.0 192.168.254.}

Technically, this use of procedures violates good coding practice. You should only use a procedure for code that will be invoked more than once in a program, or that could be called from more than one place in the program. The proper way to set this up would be to store this information in a file rather than in a procedure, and then read from the file.

Proper, yes. But this approach was quick and easy, and is still simple to read, update and modify. It would be trivial to add a fourth network location to this script.

Once our list has been constructed, the actual remaining program exeuction is almost anticlimactic:

set ipNum [lindex $netList 0]
set netM [lindex $netList 1]
set gateW [lindex $netList 2]
exec ifconfig eth0 $ipNum up netmask $netM
exec route add default gw $gateW eth0

Here we take the values from our list and pass them on to ifconfig, which brings up the ethernet interface with the correct IP number and netmask, and pass them to route, which adds eth0 as a route with the proper gateway assignment. Note that the commands are set up this way for purely cosmetic reasons. It would be just as effective, but less readable, to simply use the following two commands:

exec ifconfig eth0 [lindex $netList 0] up netmask [lindex $netList 1]
exec route add default [lindex $netList 2] eth0

The lindex command is used to extract one element from a list. List numbering begins with 0, hence "lindex $netList 0" returns the first element of netList.

The exec (stands for execute) command is what makes Tcl a real system administrator's friend. I've actually never learned how to do bash shell scripting, because it's just as easy for me to set up all the control structures in Tcl and then call any needed shell commands with exec.

When making a dial-up connection, we call the startPpp procedure. This is an even simpler procedure. I need a few different variations on my wvdial.conf file for these reasons: sometimes I'm calling an 800 number to connect; sometimes I'm calling a number in my local 650 area code. I may also be calling from a hotel, in which case I need to precede the phone number with a "9." You'd think I'd only need the 800 number for hotel connections, but in my experience when calling from hotels with older phone systems, I've had better luck making the connection to the 650 number; I have no idea why.

I have four different wvdial.conf files for these four scenarios. All that startPpp has to do is copy the correct file into the correct place, and then call wvdial. Here's the procedure:

proc startPpp {dialFrom} {

switch $dialFrom {
h800 { exec cp /etc/ldh.wvdial.conf /etc/wvdial.conf }
d800 { exec cp /etc/ldd.wvdial.conf /etc/wvdial.conf }
h650 { exec cp /etc/pah.wvdial.conf /etc/wvdial.conf }
d650 { exec cp /etc/pad.wvdial.conf /etc/wvdial.conf }
}
exec wvdial

}

Conclusion

That's all there is to it. We've built a complete graphical utility for making a network connection. It's one that's specific to my particular network configuration and my particular situation, but that's what scripting is all about: rolling your own solution when a canned solution isn't good enough.

What's attractive about Tk is the simplicity with which this kind of graphical programming can be done. This entire script involves only 64 lines of code:

#!/usr/bin/wish

proc osdnNet {} {

set e0 "192.168.254.200"
set nm "255.255.255.0"
set gw "192.168.254.1"
lappend nList $e0 $nm $gw
return $nList
}

proc dslNet {} {

set e0 "192.168.1.124"
set nm "192.168.255.0"
set gw "192.168.1.1"
lappend nList $e0 $nm $gw
return $nList
}

proc vaNet {} {

set e0 "192.168.4.209"
set nm "255.255.255.0"
set gw "192.168.4.1"
lappend nList $e0 $nm $gw
return $nList
}

proc startNet {netHost} {

switch $netHost {
osdn { set netList [osdnNet] }
dsl { set netList [dslNet] }
va { set netList [vaNet] }
}
set ipNum [lindex $netList 0]
set netM [lindex $netList 1]
set gateW [lindex $netList 2]
exec ifconfig eth0 $ipNum up netmask $netM
exec route add default gw $gateW eth0
}

proc startPpp {dialFrom} {

switch $dialFrom {
h800 { exec cp /etc/ldh.wvdial.conf /etc/wvdial.conf }
d800 { exec cp /etc/ldd.wvdial.conf /etc/wvdial.conf }
h650 { exec cp /etc/pah.wvdial.conf /etc/wvdial.conf }
d650 { exec cp /etc/pad.wvdial.conf /etc/wvdial.conf }
}
exec wvdial
}

wm title . "Network Configuration"
frame .netchoices -relief groove -borderwidth 3
label .netchoices.title -text "Network Hosts:"
radiobutton .netchoices.osdn -text "OSDN (Acton)" -variable netHost -value "osdn"
radiobutton .netchoices.dsl -text "DSL (from home)" -variable netHost -value "dsl"
radiobutton .netchoices.va -text "VA (Fremont)" -variable netHost -value "va"
button .netchoices.sub -text "Start eth0" -command {

startNet $netHost
}
frame .pppchoices -relief groove -borderwidth 3
label .pppchoices.title -text "Dial-up Locations:"
radiobutton .pppchoices.h800 -text "800 (Hotel)" -variable dialUp -value "h800"
radiobutton .pppchoices.d800 -text "800 (Direct)" -variable dialUp -value "d800"
radiobutton .pppchoices.h650 -text "650 (Hotel)" -variable dialUp -value "h650"
radiobutton .pppchoices.d650 -text "650 (Direct)" -variable dialUp -value "d650"
button .pppchoices.sub -text "Start ppp" -command {
startPpp $dialUp
}

pack .netchoices .pppchoices -side top -fill x
pack .netchoices.title .netchoices.osdn .netchoices.dsl .netchoices.va .netchoices.sub -side top -padx 10 -pady 2
pack .pppchoices.title .pppchoices.h800 .pppchoices.d800 .pppchoices.h650 .pppchoices.d650 .pppchoices.sub -side top -padx 10 -pady 2

Tcl/Tk has never achieved the popularity, or at least the visibility, of other scripting languages like Perl. I'm not sure why that is. It's an easy language to learn, has all the power and sophistication one could want, and it's also cross-platform: many Tcl/Tk scripts will run without modification on all flavors of Unix, all 32-bit flavors of Windows, and MacOS.

Tcl/Tk is getting to be one of the older scripting languages now, but it's still a good one. Anyone interested in graphical applications should think about dusting off this jewel and seeing if they can make it shine.


Mark Stone is Director of Developer Services for OSDN. He wrote this script on a flight from San Francisco to Boston. If OSDN insists that he maintain this kind of travel schedule, they might make a programmer out of him yet.




   Page 1 of 1