Uploaded by uyenle.99112

Practical Programming in Tcl and Tk

advertisement
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
•
Table of Contents
•
Index
•
Examples
Practical Programming in Tcl and Tk, Fourth Edition
By Brent B. Welch, Ken Jones, Jeffrey Hobbs
Publisher: Prentice Hall PTR
Pub Date: June 10, 2003
ISBN: 0-13-038560-3
Pages: 960
Practical Programming in Tcl/Tk is described as the "bible" for Tcl programmers. It is a guide to the Tcl/Tk programming language and GUI
toolkit. This revision includes substantial updates to cover the new version 8.4-giving both an overview of the features, as well as details
about every command in the language. The third edition, written on version 8.2, sold over 30,000 copies. Version 8.4 of Tcl - Tool Command
Language-provides substantial updates to one of the most popular UNIX scripting languages. The latest release, includes the addition of a
virtual filesystem (VFS), many additional programming widgets (spinbox, panedwindow, labelframe),and improved performance of about 20%
over 8.3. The book provides a guide to the best ways to use the tooklit. It not only gives accurate details, but includes extensive examples that
demonstrate the best way to use the toolkit. The authors are experts that have both developed the technology and used it to solve problems,
so they have many valuable insights to relate to the readers.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it.. Thanks
[ Team LiB ]
•
Table of Contents
•
Index
•
Examples
Practical Programming in Tcl and Tk, Fourth Edition
By Brent B. Welch, Ken Jones, Jeffrey Hobbs
Publisher: Prentice Hall PTR
Pub Date: June 10, 2003
ISBN: 0-13-038560-3
Pages: 960
Copyright
List of Examples
List of Tables
Preface
Why Tcl?
Tcl and Tk Versions
Extending Tcl and Tk
Tcl on the World Wide Web
Ftp Archives
Newsgroups
Who Should Read This Book
How to Read This Book
On-line Examples
Typographic Conventions
Hot Tips
Book Organization
What's New in the Fourth Edition
Other Tcl Books
First Edition Thanks
Second Edition Thanks
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Third Edition Thanks
Fourth Edition Thanks
Contact the Author
Part I. Tcl Basics
Chapter 1. Tcl Fundamentals
Tcl Commands
Hello, World!
Variables
Command Substitution
Math Expressions
Backslash Substitution
Grouping with Braces and Double Quotes
Procedures
A Factorial Example
More about Variables
More about Math Expressions
Comments
Substitution and Grouping Summary
Fine Points
Reference
Chapter 2. Getting Started
The source Command
UNIX Tcl Scripts
Windows Start Menu
Macintosh OS 8/9 andResEdit
The console Command
Command-Line Arguments
Predefined Variables
Chapter 3. The Guestbook CGI Application
A Quick Introduction to HTML
CGI for Dynamic Pages
The guestbook.cgi Script
Defining Forms and Processing Form Data
Handling Errors in CGI Scripts
Next Steps
Chapter 4. String Processing in Tcl
The string Command
The append Command
The format Command
The scan Command
The binary Command
Related Chapters
Chapter 5. Tcl Lists
Tcl Lists
Constructing Lists
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Getting List Elements: llength, lindex, and lrange
Modifying Lists: linsert and lreplace
Searching Lists: lsearch
Sorting Lists: lsort
The split Command
The join Command
Related Chapters
Chapter 6. Control Structure Commands
If Then Else
Switch
While
Foreach
For
Break and Continue
Catch
Error
Return
Chapter 7. Procedures and Scope
The proc Command
Changing Command Names with rename
Scope
The global Command
Call by Name Using upvar
Variable Aliases with upvar
Chapter 8. Tcl Arrays
Array Syntax
The array Command
Building Data Structures with Arrays
Chapter 9. Working with Files and Programs
Running Programs with exec
The file Command
Cross-Platform File Naming
Manipulating Files and Directories
File Attributes
Input/Output Command Summary
Opening Files for I/O
Reading and Writing
The Current Directory ? cd and pwd
Matching File Names with glob
The exit and pid Commands
Environment Variables
The registry Command
Part II. Advanced Tcl
Chapter 10. Quoting Issues and Eval
Constructing Code with the list Command
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Exploiting the concat inside eval
The uplevel Command
The subst Command
Chapter 11. Regular Expressions
When to Use Regular Expressions
Regular Expression Syntax
Advanced Regular Expressions
Syntax Summary
The regexp Command
The regsub Command
Transforming Data to Program with regsub
Other Commands That Use Regular Expressions
Chapter 12. Script Libraries and Packages
Locating Packages: The auto_path Variable
Using Packages
Summary of Package Loading
The package Command
Libraries Based on the tclIndex File
The unknown Command
Interactive Conveniences
Tcl Shell Library Environment
Coding Style
Chapter 13. Reflection and Debugging
The clock Command
The info Command
Cross-Platform Support
Tracing Variables and Commands
Interactive Command History
Debugging
Tcl Dev Kit
Other Tools
Performance Tuning
Chapter 14. Namespaces
Using Namespaces
Namespace Variables
Command Lookup
Nested Namespaces
Importing and Exporting Procedures
Callbacks and Namespaces
Introspection
The namespace Command
Converting Existing Packages to use Namespaces
[incr Tcl] Object System
xotcl Object System
Notes
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Chapter 15. Internationalization
Character Sets and Encodings
Message Catalogs
Chapter 16. Event-Driven Programming
The Tcl Event Loop
The after Command
The fileevent Command
The vwait Command
The fconfigure Command
Chapter 17. Socket Programming
Networking Extensions for Tcl
Client Sockets
Server Sockets
The Echo Service
Fetching a URL with HTTP
The http Package
Basic Authentication
Chapter 18. TclHttpd Web Server
Integrating TclHttpd with Your Application
Domain Handlers
Application Direct URLs
Document Types
HTML + Tcl Templates
Form Handlers
Programming Reference
Standard Application Direct URLs
The TclHttpd Distribution
Server Configuration
Chapter 19. Multiple Interpreters and Safe-Tcl
The interp Command
Creating Interpreters
Safe Interpreters
Command Aliases
Hidden Commands
Substitutions
I/O from Safe Interpreters
The Safe Base
Security Policies
Chapter 20. Safe-Tk and the Browser Plugin
Tk in Child Interpreters
The Browser Plugin
Security Policies and Browser Plugin
Configuring Security Policies
Chapter 21. Multi-Threaded Tcl Scripts
What are Threads?
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Thread Support in Tcl
Getting Started with the Thread Extension
Sending Messages to Threads
Preserving and Releasing Threads
Error Handling
Shared Resources
Managing I/O Channels
Shared Variables
Mutexes and Condition Variables
Thread Pools
The Thread Package Commands
Chapter 22. Tclkit and Starkits
Getting Started with Tclkit
Virtual File Systems
Using sdx to Bundle Applications
Exploring the Virtual File System in a Starkit
Creating tclhttpd.kit
Creating a Shared Starkit
Metakit
More Ideas
Part III. Tk Basics
Chapter 23. Tk Fundamentals
Hello, World! in Tk
Naming Tk Widgets
Configuring Tk Widgets
Tk Widget Attributes and the Resource Database
Summary of the Tk Commands
Other Widget Sets
Chapter 24. Tk by Example
ExecLog
The Example Browser
A Tcl Shell
Chapter 25. The Pack Geometry Manager
Packing toward a Side
Horizontal and Vertical Stacking
The Cavity Model
Packing Space and Display Space
Resizing and -expand
Anchoring
Packing Order
Choosing the Parent for Packing
Unpacking a Widget
Packer Summary
Window Stacking Order
Chapter 26. The Grid Geometry Manager
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
A Basic Grid
Spanning Rows and Columns
Row and Column Constraints
The grid Command
Chapter 27. The Place Geometry Manager
place Basics
The Pane Manager
The place Command
Chapter 28. The Panedwindow Widget
Using the Panedwindow
Programming Panedwindow Widgets
Panedwindow Attributes
Chapter 29. Binding Commands to Events
The bind Command
The bindtags Command
Event Syntax
Modifiers
Event Sequences
Virtual Events
Generating Events
Event Summary
Part IV. Tk Widgets
Chapter 30. Buttons and Menus
Button Commands and Scope Issues
Buttons Associated with Tcl Variables
Button Attributes
Button Operations
Menus and Menubuttons
Menu Bindings and Events
Manipulating Menus and Menu Entries
Menu Attributes
A Menu by Name Package
Chapter 31. The Resource Database
An Introduction to Resources
Loading Option Database Files
Adding Individual Database Entries
Accessing the Database
User-Defined Buttons
User-Defined Menus
Chapter 32. Simple Tk Widgets
Frames, Labelframes, and Toplevel Windows
The Label Widget
The Message Widget
The Scale Widget
The bell Command
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Chapter 33. Scrollbars
Using Scrollbars
The Scrollbar Protocol
The Scrollbar Widget
Chapter 34. The Entry and Spinbox Widgets
Using Entry Widgets
Using Spinbox Widgets
Entry and Spinbox Bindings
Entry and Spinbox Attributes
Programming Entry and Spinbox Widgets
Chapter 35. The Listbox Widget
Using Listboxes
The Listbox Widget
Listbox Bindings and Events
Listbox Attributes
Chapter 36. The Text Widget
Text Indices
Text Marks
Text Tags
The Selection
Tag Bindings
Searching Text
Embedded Widgets
Embedded Images
Looking inside the Text Widget
The Undo Mechanism
Text Bindings and Events
Text Operations
Text Attributes
Chapter 37. The Canvas Widget
Canvas Coordinates
Hello, World!
The Min Max Scale Example
Canvas Objects
Canvas Operations
Generating Postscript
Canvas Attributes
Hints
Part V. Tk Details
Chapter 38. Selections and the Clipboard>
The Selection Model
The selection Command
The clipboard Command
Selection Handlers
Chapter 39. Focus, Grabs, and Dialogs
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Standard Dialogs
Custom Dialogs
Animation with the update Command
Chapter 40. Tk Widget Attributes
Configuring Attributes
Size
Borders and Relief
The Focus Highlight
Padding and Anchors
Chapter 41. Color, Images, and Cursors
Colors
Colormaps and Visuals
Bitmaps and Images
The Text Insert Cursor
The Mouse Cursor
Chapter 42. Fonts and Text Attributes
Naming a Font
X Font Names
Font Metrics
The font Command
Text Attributes
Gridding, Resizing, and Geometry
A Font Selection Application
Chapter 43. Send
The send Command
The Sender Script
Communicating Processes
Remote eval through Sockets
Chapter 44. Window Managers and Window Information
The wm Command
The winfo Command
The tk Command
Chapter 45. Managing User Preferences
App-Defaults Files
Defining Preferences
The Preferences User Interface
Managing the Preferences File
Tracing Changes to Preference Variables
Improving the Package
Chapter 46. A User Interface to Bindings
A Pair of Listboxes Working Together
The Editing Interface
Saving and Loading Bindings
Part VI. C Programming
Chapter 47. C Programming and Tcl
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Basic Concepts
Creating a Loadable Package
A C Command Procedure
The blob Command Example
CONST in the Tcl 8.4 APIs
Strings and Internationalization
Tcl_Main and Tcl_AppInit
The Event Loop
Invoking Scripts from C
Chapter 48. Compiling Tcl and Extensions
Standard Directory Structure
Building Tcl from Source
Using Stub Libraries
Using autoconf
The Sample Extension
Chapter 49. Writing a Tk Widget in C
Initializing the Extension
The Widget Data Structure
The Widget Class Command
The Widget Instance Command
Configuring and Reconfiguring Attributes
Specifying Widget Attributes
Displaying the Clock
The Window Event Procedure
Final Cleanup
Chapter 50. C Library Overview
An Overview of the Tcl C Library
An Overview of the Tk C Library
Part VII. Changes
Chapter 51. Tcl 7.4/Tk 4.0
wish
Obsolete Features
The cget Operation
Input Focus Highlight
Bindings
Scrollbar Interface
pack info
Focus
The send Command
Internal Button Padding
Radiobutton Value
Entry Widget
Menus
Listboxes
No geometry Attribute
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Text Widget
Color Attributes
Color Allocation and tk colormodel
Canvas scrollincrement
The Selection
The bell Command
Chapter 52. Tcl 7.5/Tk 4.1
Cross-Platform Scripts
The clock Command
The load Command
The package Command
Multiple foreach loop variables
Event Loop Moves from Tk to Tcl
Network Sockets
Multiple Interpreters and Safe-Tcl
The grid Geometry Manager
The Text Widget
The Entry Widget
Chapter 53. Tcl 7.6/Tk 4.2
More file Operations
Virtual Events
Standard Dialogs
New grid Geometry Manager
Macintosh unsupported1 Command
Chapter 54. Tcl/Tk 8.0
The Tcl Compiler
Namespaces
Safe-Tcl
New lsort
tcl_precision Variable
Year 2000 Convention
Http Package
Serial Line I/O
Platform-Independent Fonts
The tk scaling Command
Application Embedding
Native Menus and Menubars
CDE Border Width
Native Buttons and Scrollbars
Images in Text Widgets
No Errors from destroy
grid rowconfigure
The Patch Releases
Chapter 55. Tcl/Tk 8.1
Unicode and Internationalization
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Thread Safety
Advanced Regular Expressions
New String Commands
The DDE Extension
Miscellaneous
Chapter 56. Tcl/Tk 8.2
The Trf Patch
Faster String Operations
Empty Array Names
Browser Plugin Compatibility
Finer Control of Windows Serial Port Monitoring
Regular Expression Expanded Syntax Option
Chapter 57. Tcl/Tk 8.3
New File Manipulation Commands and Options
New glob Options
Regular Expression Command Enhancements
Direct Return of scan Matches
Removing Duplicate List Elements withlsort
Deleting Elements from an Array
Enhanced clock Features
Support for Delayed Package Loading inpkg_mkIndex
The Img Patch
The Dash Patch
Other New Tk Features
The Patch Releases
Chapter 58. Tcl/Tk 8.4
64-Bit Support
Additional Filesystem Features and Commands
New and Enhanced List Commands
Array Searching and Statistics
Enhanced Support for Serial Communications
New String Comparison Operators
Command Tracing
Additional Introspection Commands
Other Tcl Changes
New Tk Widgets
Text Widget Undo Mechanism and Other Enhancements
New pack and grid Features
Displaying Both Text and an Image in a Widget
New Button Relief Attributes
Controlling the State of Entries and Listboxes
More Window Manager Interaction
Other Tk Changes
Chapter 59. About The CD-ROM
Technical Support
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Index
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Copyright
Library of Congress Cataloging-in-Publication available
Editorial/production supervision: Kathleen M. Caren
Executive Editor: Mark Taub
Editorial Assistant: Noreen Regina
Marketing Manager: Kate Hargett
Manufacturing Manager: Maura Zaldivar
Cover Design Director: Jerry Votta
© 2003 Pearson Education Inc.
Publishing as Prentice Hall PTR
Upper Saddle River, NJ 07458
Prentice Hall books are widely used by corporations and government agencies for training, marketing, and resale.
For information regarding corporate and government bulk discounts, contact:
Corporate and Government Sales: (800) 382-3419 or corpsales@pearsontechgroup.com
All products mentioned herein are trademarks or registered trademarks of their respective owners.
All rights reserved. No part of this book may be reproduced, in any form or by any means, without permission in writing from the publisher.
Printed in the United States of America
10 9 8 7 6 5 4 3 2 1
Pearson Education LTD.
Pearson Education Australia PTY, Limited
Pearson Education Singapore, Pte. Ltd.
Pearson Education North Asia Ltd.
Pearson Education Canada, Ltd.
Pearson Educación de Mexico, S.A. de C.V.
Pearson Education—Japan
Pearson Education Malaysia, Pte. Ltd.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Dedication
to
Jody, Christopher, Daniel, and Michael
—Brent
Dean, for his support and patience
—Ken
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
List of Examples
1. Tcl Fundamentals
1.1 The "Hello, World!" example
1.2 Tcl variables
1.3 Command substitution
1.4 Simple arithmetic
1.5 Nested commands
1.6 Built-in math functions
1.7 Grouping expressions with braces
1.8 Quoting special characters with backslash
1.9 Continuing long lines with backslashes
1.10 Grouping with double quotes vs. braces
1.11 Embedded command and variable substitution
1.12 Defining a procedure
1.13 A while loop to compute factorial
1.14 A recursive definition of factorial
1.15 Using set to return a variable value
1.16 Embedded variable references
1.17 Using info to determine if a variable exists
1.18 Controlling precision with tcl_precision
2. Getting Started
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
2.1 A standalone Tcl script on UNIX
2.2 A standalone Tk script on UNIX
2.3 Using /bin/sh to run a Tcl script
2.4 The EchoArgs script
3. The Guestbook CGI Application
3.1 A simple CGI script
3.2 Output of Example 3-1
3.3 The guestbook.cgi script, version 1
3.4 The Cgi_Header procedure
3.5 The guestbook.cgi script, version 2
3.6 Initial output of guestbook.cgi with no data
3.7 Output of guestbook.cgi with guestbook data
3.8 The newguest.html form
3.9 The newguest.cgi script
3.10 The newguest.cgi script with error handling
4. String Processing in Tcl
4.1 Comparing strings with string compare
4.2 Comparing strings with string equal
4.3 Comparing strings with eq
4.4 Mapping Microsoft World special characters to ASCII
5. Tcl Lists
5.1 Constructing a list with the list command
5.2 Using lappend to add elements to a list
5.3 Using lset to set an element of a list
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
5.4 Using concat to splice lists together
5.5 Double quotes compared to the concat and list commands
5.6 Modifying lists with lreplace
5.7 Deleting a list element by value
5.8 Sorting a list using a comparison function
5.9 Use split to turn input data into Tcl lists
5.10 Implementing join in Tcl
6. Control Structure Commands
6.1 A conditional if then else command
6.2 Chained conditional with elseif
6.3 Using switch for an exact match
6.4 Using switch with substitutions in the patterns
6.5 A switch with "fall through" cases
6.6 Comments in switch commands
6.7 A while loop to read standard input
6.8 Looping with foreach
6.9 Parsing command-line arguments
6.10 Using list with foreach
6.11 Multiple loop variables with foreach
6.12 Multiple value lists with foreach
6.13 A for loop
6.14 A standard catch phrase
6.15 A longer catch phrase
6.16 There are several possible return values from catch
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
6.17 Raising an error
6.18 Preserving errorInfo when calling error
6.19 Raising an error with return
7. Procedures and Scope
7.1 Default parameter values
7.2 Variable number of arguments
7.3 Variable scope and Tcl procedures
7.4 A random number generator
7.5 Print variable by name
7.6 Improved incr procedure
8. Tcl Arrays
8.1 Using arrays
8.2 Referencing an array indirectly
8.3 Referencing an array indirectly using upvar
8.4 ArrayInvert inverts an array
8.5 Using arrays for records, version 1
8.6 Using arrays for records, version 2
8.7 Using arrays for records, version 3
8.8 Using a list to implement a stack
8.9 Using an array to implement a stack
8.10 A list of arrays
8.11 A list of arrays
8.12 A simple in-memory database
9. Working with Files and Programs
9.1 Using exec on a process pipeline
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
9.2 Comparing file modify times
9.3 Determining whether pathnames reference the same file
9.4 Opening a file for writing
9.5 A more careful use of open
9.6 Opening a process pipeline
9.7 Prompting for input
9.8 A read loop using gets
9.9 A read loop using read and split
9.10 Copy a file and translate to native format
9.11 Finding a file by name
9.12 Printing environment variable values
10. Quoting Issues and Eval
10.1 Using list to construct commands
10.2 Generating procedures dynamically with a template
10.3 Using eval with $args
10.4 lassign: list assignment with foreach
10.5 The File_Process procedure iterates over lines in a file
11. Regular Expressions
11.1 Expanded regular expressions allow comments
11.2 Using regular expressions to parse a string
11.3 A pattern to match URLs
11.4 An advanced regular expression to match URLs
11.5 The Url_Decode procedure
11.6 The Cgi_List and Cgi_Query procedures
11.7 Cgi_Parse and Cgi_Value store query data in the cgi array
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
11.8 Html_DecodeEntity
11.9 Html_Parse
12. Script Libraries and Packages
12.1 Maintaining a tclIndex file
12.2 Loading a tclIndex file
13. Reflection and Debugging
13.1 Calculating clicks per second
13.2 Printing a procedure definition
13.3 Mapping form data onto procedure arguments
13.4 Finding built-in commands
13.5 Getting a trace of the Tcl call stack
13.6 A procedure to read and evaluate commands
13.7 Using info script to find related files
13.8 Tracing variables
13.9 Creating array elements with array traces
13.10 Interactive history usage
13.11 Implementing special history syntax
13.12 A Debug procedure
13.13 Time Stamps in log records
14. Namespaces
14.1 Random number generator using namespaces
14.2 Random number generator using qualified names
14.3 Nested namespaces
14.4 The code procedure to wrap callbacks
14.5 Listing commands defined by a namespace
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
15. Internationalization
15.1 MIME character sets and file encodings
15.2 Using scripts in nonstandard encodings
15.3 Three sample message catalog files
15.4 Using msgcat::mcunknown to share message catalogs
16. Event-Driven Programming
16.1 A read event file handler
16.2 Using vwait to activate the event loop
16.3 A read event file handler for a nonblocking channel
17. Socket Programming
17.1 Opening a client socket with a timeout
17.2 Opening a server socket
17.3 The echo service
17.4 A client of the echo service
17.5 Opening a connection to an HTTP server
17.6 Opening a connection through a HTTP proxy
17.7 Http_Head validates a URL
17.8 Using Http_Head
17.9 Http_Get fetches the contents of a URL" endterm="ch17list09.title"/>
17.10 HttpGetText reads text URLs
17.11 HttpCopyDone is used with fcopy
17.12 Downloading files with http::geturl
17.13 Basic Authentication using http::geturl
18. TclHttpd Web Server
18.1 The hello.tcl file implements /hello/world
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
18.2 A simple URL domain
18.3 Application Direct URLs
18.4 Alternate types for Application Direct URLs
18.5 A sample document type handler
18.6 A one-level site structure
18.7 A two-level site structure
18.8 A HTML + Tcl template file
18.9 SitePage template procedure, version 1
18.10 SiteMenu and SiteFooter template procedures
18.11 The SiteLink procedure
18.12 Mail form results with /mail/forminfo
18.13 Mail message sent by /mail/forminfo
18.14 Processing mail sent by /mail/forminfo
18.15 Processing mail sent by /mail/forminfo, Safe-Tcl version
18.16 A self-checking form procedure
18.17 A page with a self-checking form
18.18 Generating a table with html::foreach
18.19 The /debug/source Application Direct URL implementation
19. Multiple Interpreters and Safe-Tcl
19.1 Creating and deleting an interpreter
19.2 Creating a hierarchy of interpreters
19.3 A command alias for exit
19.4 Querying aliases
19.5 Dumping aliases as Tcl commands
19.6 Substitutions and hidden commands
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
19.7 Opening a file for an unsafe interpreter
19.8 The Safesock security policy
19.9 The Tempfile security policy
19.10 Restricted puts using hidden commands
19.11 A safe after command
20. Safe-Tk and the Browser Plugin
20.1 Using EMBED to insert a Tclet
21. Multi-Threaded Tcl Scripts
21.1 Creating a separate thread to perform a lengthy operation
21.2 Initializing a thread before entering its event loop
21.3 Creating several threads in an application
21.4 Using joinable threads to detect thread termination
21.5 Examples of synchronous message sending
21.6 Using a return variable with synchronous message sending
21.7 Executing commands after thread::wait returns
21.8 Creating a custom thread error handler
21.9 A basic implementation of a logging thread
21.10 Deferring socket transfer until after the connection callback
21.11 Working around Tcl's socket transfer bug
21.12 A multi-threaded echo server
21.13 Using a mutex to protect a shared resource
21.14 Standard condition variable use for a signalling thread
21.15 Standard condition variable use for a waiting thread
22. Tclkit and Starkits
22.1 Accessing a Zip file through a VFS
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
22.2 The output of sdx lsk hello.kit
22.3 The main program of a Starkit
22.4 The pkgIndex.tcl in a Starkit
22.5 A Starkit that examines its Virtual File System
22.6 Creating a simple Starkit
22.7 The contents of the tclhttpd.vfs directory, version 1
22.8 The main program for the TclHttpd Starkit, version 1
22.9 Contents of the tclhttpd.vfs directory, version 2
22.10 The main program for the TclHttpd Starkit, version 2
22.11 The Standard Tcl Library Starkit main.tcl file
22.12 The main program for TclHttpd Starkit, version 3
22.13 Examining the views in a Metakit database
22.14 Examining data in a Metakit view
22.15 Selecting data with mk::select
22.16 Creating a new view
22.17 Adding data to a view
22.18 Storing data in a Starkit
23. Tk Fundamentals
23.1 "Hello, World!" Tk program
23.2 Looking at all widget attributes
24. Tk by Example
24.1 Logging the output of a program run with exec
24.2 A platform-specific cancel event
24.3 A browser for the code examples in the book
24.4 A Tcl shell in a text widget
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
24.5 Macintosh look and feel
24.6 Windows look and feel
24.7 UNIX look and feel
25. The Pack Geometry Manager
25.1 Two frames packed inside the main frame
25.2 Turning off geometry propagation
25.3 A horizontal stack inside a vertical stack
25.4 Even more nesting of horizontal and vertical stacks
25.5 Mixing bottom and right packing sides
25.6 Filling the display into extra packing space
25.7 Using horizontal fill in a menu bar
25.8 The effects of internal padding (-ipady)
25.9 Button padding vs. packer padding
25.10 The look of a default button
25.11 Resizing without the expand option
25.12 Resizing with expand turned on
25.13 More than one expanding widget
25.14 Setup for anchor experiments
25.15 The effects of noncenter anchors
25.16 Animating the packing anchors
25.17 Controlling the packing order
25.18 Packing into other relatives
26. The Grid Geometry Manager
26.1 A basic grid
26.2 A grid with sticky settings
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
26.3 A grid with row and column specifications
26.4 A grid with external padding
26.5 A grid with internal padding
26.6 All combinations of -sticky settings
26.7 Explicit row and column span
26.8 Grid syntax row and column span
26.9 Row padding compared to cell padding
26.10 Gridding a text widget and scrollbar
26.11 Uniform column width
27. The Place Geometry Manager
27.1 Centering a window with place
27.2 Covering a window with place
27.3 Combining relative and absolute sizes
27.4 Positioning a window above a sibling with place
27.5 Pane_Create sets up vertical or horizontal panes
27.6 PaneDrag adjusts the percentage
27.7 PaneGeometry updates the layout
28. The Panedwindow Widget
28.1 A panedwindow with complex managed widgets
29. Binding Commands to Events
29.1 Bindings on different binding tags
29.2 Output from the UNIX xmodmap program
29.3 Emacs-like binding convention for Meta and Escape
29.4 Virtual events for cut, copy, and paste
30. Buttons and Menus
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
30.1 A troublesome button command
30.2 Fixing the troublesome situation
30.3 A button associated with a Tcl procedure
30.4 Radiobuttons and checkbuttons
30.5 A command on a radiobutton or checkbutton
30.6 A menu sampler
30.7 A menu bar in Tk 8.0
30.8 Using the <<MenuSelect>> virtual event
30.9 A simple menu by name package
30.10 Using the Tk 8.0 menu bar facility
30.11 MenuGet maps from name to menu
30.12 Adding menu entries
30.13 A wrapper for cascade entries
30.14 Using the menu by name package
30.15 Keeping the accelerator display up to date
31. The Resource Database
31.1 Reading an option database file
31.2 A file containing resource specifications
31.3 Using resources to specify user-defined buttons
31.4 Resource_ButtonFrame defines buttons based on resources
31.5 Using Resource_ButtonFrame
31.6 Specifying menu entries via resources
31.7 Defining menus from resource specifications
31.8 Resource_GetFamily merges user and application resources
32. Simple Tk Widgets
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
32.1 Labelframe example
32.2 Using the labelAnchor option to position a labelframe's anchor
32.3 Associating an existing label widget with a labelframe
32.4 Macintosh window styles
32.5 A label that displays different strings
32.6 The message widget formats long lines of text
32.7 Controlling the text layout in a message widget
32.8 A scale widget
33. Scrollbars
33.1 A text widget and two scrollbars
33.2 Scroll_Set manages optional scrollbars
33.3 Listbox with optional scrollbars
34. The Entry and Spinbox Widgets
34.1 Associating entry widgets with variables and commands
34.2 Restricting entry text to integer values
34.3 Reestablishing validation using an idle task
34.4 A simple spinbox with calculated values
34.5 Formatting numeric values in a spinbox
34.6 Enumerating spinbox values and wrapping
34.7 Using the spinbox readonly state
35. The Listbox Widget
35.1 Using -listvariable to link a listbox and variable
35.2 Choosing items from a listbox
35.3 Using the <<ListboxSelect>> virtual event
36. The Text Widget
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
36.1 Tag configurations for basic character styles
36.2 Line spacing and justification in the text widget
36.3 An active text button
36.4 Delayed creation of embedded widgets
36.5 Using embedded images for a bulleted list
36.6 Finding the current range of a text tag
36.7 Dumping the text widget
36.8 Dumping the text widget with a command callback
37. The Canvas Widget
37.1 A large scrolling canvas
37.2 The canvas "Hello, World!" example
37.3 A min max scale canvas example
37.4 Moving the markers for the min max scale
37.5 Canvas arc items
37.6 Canvas bitmap items
37.7 Canvas image items
37.8 A canvas stroke drawing example
37.9 Canvas oval items
37.10 Canvas polygon items
37.11 Dragging out a box
37.12 Simple edit bindings for canvas text items
37.13 Using a canvas to scroll a set of widgets
37.14 Generating Postscript from a canvas
38. Selections and the Clipboard
38.1 Paste the PRIMARY or CLIPBOARD selection
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
38.2 Separate paste actions
38.3 Bindings for canvas selection
38.4 Selecting objects
38.5 A canvas selection handler
38.6 The copy and cut operations
38.7 Pasting onto the canvas
39. Focus, Grabs, and Dialogs
39.1 Procedures to help build dialogs
39.2 A simple dialog
39.3 A feedback procedure
40. Tk Widget Attributes
40.1 Equal-sized labels
40.2 3D relief sampler
40.3 Padding provided by labels and buttons
40.4 Anchoring text in a label or button
40.5 Borders and padding
41. Color, Images, and Cursors
41.1 Resources for reverse video
41.2 Computing a darker color
41.3 Specifying an image for a widget
41.4 Specifying a bitmap for a widget
41.5 The built-in bitmaps
41.6 The Tk cursors
42. Fonts and Text Attributes
42.1 The FontWidget procedure handles missing fonts
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
42.2 Font metrics
42.3 A gridded, resizable listbox
42.4 Font selection dialog
43. Send
43.1 The sender application
43.2 Hooking the browser to an eval server
43.3 Making the shell into an eval server
43.4 Remote eval using sockets
43.5 Reading commands from a socket
43.6 The client side of remote evaluation
44. Window Managers and Window Information
44.1 Gridded geometry for a canvas
44.2 Telling other applications what your name is
45. Managing User Preferences
45.1 Preferences initialization
45.2 Adding preference items
45.3 Setting preference variables
45.4 Using the preferences package
45.5 A user interface to the preference items
45.6 Interface objects for different preference types
45.7 Displaying the help text for an item
45.8 Saving preferences settings to a file
45.9 Read settings from the preferences file
45.10 Tracing a Tcl variable in a preference item
46. A User Interface to Bindings
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
46.1 A user interface to widget bindings
46.2 Bind_Display presents the bindings for a widget or class
46.3 Related listboxes are configured to select items together
46.4 Controlling a pair of listboxes with one scrollbar
46.5 Drag-scrolling a pair of listboxes together
46.6 An interface to define bindings
46.7 Defining and saving bindings
47. C Programming and Tcl
47.1 The initialization procedure for a loadable package
47.2 The RandomCmd C command procedure
47.3 The RandomObjCmd C command procedure
47.4 The Tcl_Obj structure
47.5 The Plus1ObjCmd procedure
47.6 The Blob and BlobState data structures
47.7 The Blob_Init and BlobCleanup procedures
47.8 The BlobCmd command procedure
47.9 BlobCreate and BlobDelete
47.10 The BlobNames procedure
47.11 \The BlobN and BlobData procedures
47.12 The BlobCommand and BlobPoke procedures
47.13 A canonical Tcl main program and Tcl_AppInit
47.14 A canonical Tk main program and Tk_AppInit
47.15 Calling C command procedure directly with Tcl_Invoke
48. Compiling Tcl and Extensions
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Writing a Tk Widget in C
49.1 The Clock_Init procedure
49.2 The Clock widget data structure
49.3 The ClockCmd command procedure
49.4 The ClockObjCmd command procedure
49.5 The ClockInstanceCmd command procedure
49.6 The ClockInstanceObjCmd command procedure
49.7 ClockConfigure allocates resources for the widget
49.8 ClockObjConfigure allocates resources for the widget
49.9 The Tk_ConfigSpec typedef
49.10 Configuration specs for the clock widget
49.11 The Tk_OptionSpec typedef
49.12 The Tk_OptionSpec structure for the clock widget
49.13 ComputeGeometry computes the widget's size
49.14 The ClockDisplay procedure
49.15 The ClockEventProc handles window events
49.16 The ClockDestroy cleanup procedure
49.17 The ClockObjDelete command
500C Library Overview
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
List of Tables
1. Tcl Fundamentals
1-1 Backslash sequences
1-2 Arithmetic operators from highest to lowest precedence
1-3 Built-in math functions
1-4 Built-in Tcl commands
2. Getting Started
2-1 Wish command line options
2-2 Variables defined by tclsh and wish
3. The Guestbook CGI Application
3-1 HTML tags used in the examples
4. String Processing in Tcl
4-1 The string command
4-2 Matching characters used with string match
4-3 Character class names
4-4 Format conversions
4-5 Format flags
4-6 Binary conversion types
5. Tcl Lists
5-1 List-related commands
5-2 Options to the lsearch command
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
8. Tcl Arrays
8-1 The array command
9. Working with Files and Programs
9-1 Summary of the exec syntax for I/O redirection
9-2 The file command options
9-3 Array elements defined by file stat
9-4 Platform-specific file attributes
9-5 Tcl commands used for file access
9-6 Summary of the open access arguments
9-7 Summary of POSIX flags for the access argument
9-8 glob command options
9-9 The registry command
9-10 The registry data types
11. Regular Expressions
11-1 Additional advanced regular expression syntax
11-2 Backslash escapes in regular expressions
11-3 Character classes
11-4 Embedded option characters used with the (?x) syntax
11-5 Options to the regexp command
11-6 Sample regular expressions
12. Script Libraries and Packages
12-1 Options to the pkg_mkIndex command
12-2 The package command
13. Reflection and Debugging
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
13-1 clock format keywords
13-2 The clock command
13-3 The info command
13-4 The history command
13-5 Special history syntax
14. Namespaces
14-1 The namespace command
15. Internationalization
15-1 The encoding command
15-2 The msgcat package
16. Event-Driven Programming
16-1 The after command
16-2 The fileevent command
16-3 I/O channel properties controlled by fconfigure
16-4 Serial line properties controlled by fconfigure
16-5 End of line translation modes
17. Socket Programming
17-1 Options to the http::geturl command
17-2 The http support procedures
17-3 Elements of the http::geturl state array
18. TclHttpd Web Server
18-1 Httpd support procedures
18-2 Url support procedures
18-3 Doc procedures for configuration
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
18-4 Doc procedures for generating responses
18-5 Doc procedures that support template processing
18-6 Elements of the page array
18-7 Elements of the env array
18-8 Status Application Direct URLs
18-9 Debug Application Direct URLs
18-10 Application Direct URLS that email form results
18-11 Basic TclHttpd parameters
19. Multiple Interpreters and Safe-Tcl
19-1 The interp command
19-2 Commands hidden from safe interpreters
19-3 The safe base master interface
19-4 The safe base slave aliases
20. Safe-Tk and the Browser Plugin
20-1 Tk commands omitted from safe interpreters
20-2 Aliases defined by the browser package
20-3 The browser::getURL callbacks
21. Multi-Threaded Tcl Scripts
21-1 The commands of the thread namespace
21-2 Thread configuration options
21-3 The commands of the tsv namespace
21-4 The commands of the tpool namespace
21-5 Thread pool configuration options
22. Tclkit and Starkits
22-1 Return values of the starkit::startup procedure
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
23. Tk Fundamentals
23-1 Tk widget-creation commands
23-2 Tk widget-manipulation commands
23-3 Tk support procedures
25. The Pack Geometry Manager
25-1 The pack command
25-2 Packing options
26. The Grid Geometry Manager
26-1 The grid command
26-2 Grid widget options
27. The Place Geometry Manager
27-1 The place command
27-2 Placement options
28. The Panedwindow Widget
28-1 Panedwindow operations
28-2 Panedwindow attributes
28-3 Panedwindow managed widget options
29. Binding Commands to Events
29-1 Event types
29-2 Event modifiers
29-3 The event command
29-4 A summary of the event keywords
30. Buttons and Menus
30-1 Resource names of attributes for all button widgets
30-2 Button operations
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
30-3 Menu entry index keywords
30-4 Menu operations
30-5 Menu attribute resource names
30-6 Attributes for menu entries
32. Simple Tk Widgets
32-1 Attributes for frame, labelframe, and toplevel widgets
32-2 Label Attributes
32-3 Message Attributes
32-4 Bindings for scale widgets
32-5 Attributes for scale widgets
32-6 Operations on the scale widget
33. Scrollbars
33-1 Attributes for the scrollbar widget
33-2 Bindings for the scrollbar widget
33-3 Operations on the scrollbar widget
34. The Entry and Spinbox Widgets
34-1 Entry and spinbox validation substitutions
34-2 Entry and spinbox bindings
34-3 Entry and spinbox attribute resource names
34-4 Entry and spinbox indices
34-5 Entry and spinbox operations
35. The Listbox Widget
35-1 Listbox indices
35-2 Listbox operations
35-3 Listbox item configuration options
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
35-4 The values for the selectMode of a listbox
35-5 Bindings for browse selection mode
35-6 Bindings for single selection mode
35-7 Bindings for extended selection mode
35-8 Bindings for multiple selection mode
35-9 Listbox scroll bindings
35-10 Listbox attribute resource names
36. The Text Widget
36-1 Text indices
36-2 Index modifiers for text widgets
36-3 Attributes for text tags
36-4 Options to the search operation
36-5 Window and image alignment options
36-6 Options to the window create operation
36-7 Options to the image create operation
36-8 Bindings for the text widget
36-9 Operations for the text widget
36-10 Text attribute resource names
37. The Canvas Widget
37-1 Common canvas item attributes
37-2 Canvas dash pattern characters
37-3 Arc attributes
37-4 Bitmap attributes
37-5 Image attributes
37-6 Line attributes
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
37-7 Polygon attributes
37-8 Indices for canvas text items
37-9 Canvas operations that apply to text items
37-10 Text attributes
37-11 Window attributes
37-12 Operations on a canvas widget
37-13 Canvas postscript options
37-14 Canvas attribute resource names
38. Selections and the Clipboard
38-1 The selection command
38-2 The clipboard command
39. Focus, Grabs, and Dialogs
39-1 Options to tk_messageBox
39-2 Options to the standard file and directory dialogs
39-3 Options to tk_chooseColor
39-4 The focus command
39-5 The grab command
39-6 he tkwait command
40. Tk Widget Attributes
40-1 Size attribute resource names
40-2 Border and relief attribute resource names
40-3 Highlight attribute resource names
40-4 Layout attribute resource names
41. Color, Images, and Cursors
41-1 Color attribute resource names
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
41-2 Windows system colors
41-3 Macintosh system colors
41-4 Visual classes for displays
41-5 Summary of the image command
41-6 Bitmap image options
41-7 Photo image attributes
41-8 Photo image operations
41-9 Copy options for photo images
41-10 Read options for photo images
41-11 Write options for photo images
41-12 Cursor attribute resource names
42. Fonts and Text Attributes
42-1 Font attributes
42-2 X Font specification components
42-3 Layout attribute resource names
42-4 The font command
42-5 Selection attribute resource names
43. Send
43-1 Options to the send command
44. Window Managers and Window Information
44-1 Size, placement and decoration window manager operations
44-2 Window manager commands for icons
44-3 Session-related window manager operations
44-4 Miscellaneous window manager operations
44-5 send command information
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
44-6 Window hierarchy information
44-7 Window location information
44-8 Window size information
44-9 Virtual root window information
44-10 Atom and window ID information
44-11 Colormap and visual class information
44-12 The tk command operations
47. C Programming and Tcl
47-1 Defines to control the meaning of CONST in the Tcl APIs
48. Compiling Tcl and Extensions
48-1 The Tcl source directory structure
48-2 The installation directory structure
48-3 Standard configure flags
48-4 TEA standard Makefile targets
49. Writing a Tk Widget in C
49-1 Configuration flags and corresponding C types
51. Tcl 7.4/Tk 4.0
51-1 Changes in color attribute names
55. Tcl/Tk 8.1
55-1 The testthread command
55-2 The dde command options
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Preface
Tcl stands for Tool Command Language. Tcl is really two things: a scripting language, and an interpreter for that language that is designed to
be easy to embed into your application. Tcl and its associated graphical user-interface toolkit, Tk, were designed and crafted by Professor
John Ousterhout of the University of California, Berkeley. You can find these packages on the Internet and use them freely in your
application, even if it is commercial. The Tcl interpreter has been ported from UNIX to DOS, PalmOS, VMS, Windows, OS/2, NT, and
Macintosh environments. The Tk toolkit has been ported from the X window system to Windows and Macintosh.
I first heard about Tcl in 1988 while I was Ousterhout's Ph.D. student at Berkeley. We were designing a network operating system, Sprite.
While the students hacked on a new kernel, John wrote a new editor and terminal emulator. He used Tcl as the command language for both
tools so that users could define menus and otherwise customize those programs. This was in the days of X10, and he had plans for an X
toolkit based on Tcl that would help programs cooperate with each other by communicating with Tcl commands. To me, this cooperation
among tools was the essence of Tcl.
This early vision imagined that applications would be large bodies of compiled code and a small amount of Tcl used for configuration and
high-level commands. John's editor, mx, and the terminal emulator,tx, followed this model. While this model remains valid, it has also turned
out to be possible to write entire applications in Tcl. This is because the Tcl/Tk shell, wish, provides access to other programs, the file system,
network sockets, plus the ability to create a graphical user interface. For better or worse, it is now common to find applications that contain
thousands of lines of Tcl script.
This book was written because, while I found it enjoyable and productive to use Tcl and Tk, there were times when I was frustrated. In
addition, working at Xerox PARC, with many experts in languages and systems, I was compelled to understand both the strengths and
weaknesses of Tcl and Tk. Although many of my colleagues adopted Tcl and Tk for their projects, they were also just as quick to point out its
flaws. In response, I have built up a set of programming techniques that exploit the power of Tcl and Tk while avoiding troublesome areas.
This book is meant as a practical guide to help you get the most out of Tcl and Tk and avoid some of the frustrations I experienced.
It has been about 14 years since I was introduced to Tcl, and about eight years since the first edition of this book. During several of those
years I worked under John Ousterhout, first at Sun Microsystems and then at Scriptics Corporation. There I remained mostly a Tcl
programmer while others in our group have delved into the C implementation of Tcl itself. I've built applications like HTML editors, email user
interfaces, Web servers, and the customer database we ran our business on. This experience is reflected in this book. The bulk of the book is
about Tcl scripting, and the aspects of C programming to create Tcl extensions is given a lighter treatment. I have been lucky to remain
involved in the core Tcl development, and I hope I can pass along the insights I have gained by working with Tcl.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Why Tcl?
As a scripting language, Tcl is similar to other UNIX shell languages such as the Bourne Shell (sh), the C Shell (csh), the Korn Shell (ksh),
and Perl. Shell programs let you execute other programs. They provide enough programmability (variables, control flow, and procedures) to
let you build complex scripts that assemble existing programs into a new tool tailored for your needs. Shells are wonderful for automating
routine chores.
It is the ability to easily add a Tcl interpreter to your application that sets it apart from other shells. Tcl fills the role of an extension language
that is used to configure and customize applications. There is no need to invent a configuration file format or a command language for your
new application, or struggle to provide some sort of user-programmability for your tool. Instead, by adding a Tcl interpreter, you structure your
application as a set of primitive operations that can be composed by a script to best suit the needs of your users. It also allows other
programs to have programmatic control over your application, leading to suites of applications that work well together.
The Tcl C library has clean interfaces and is simple to use. The library implements the basic interpreter and a set of core scripting commands
that implement variables, flow control, and procedures (see page 22). There is also a broad set of APIs that access operating system services
to run other programs, access the file system, and use network sockets. Tk adds commands to create graphical user interfaces. The Tcl and
Tk C APIs provide a "virtual machine" that is portable across UNIX, Windows, and Macintosh environments.
The Tcl virtual machine is extensible because your application can define new Tcl commands. These commands are associated with a C or
C++ procedure that your application provides. The result is applications that are split into a set of primitives written in a compiled language
and exported as Tcl commands. A Tcl script is used to compose the primitives into the overall application. The script layer has access to
shell-like capability to run other programs, has access to the file system, and can call directly into the compiled part of the application through
the Tcl commands you define. In addition, from the C programming level, you can call Tcl scripts, set and query Tcl variables, and even trace
the execution of the Tcl interpreter.
There are many Tcl extensions freely available on the Internet. Most extensions include a C library that provides some new functionality, and
a Tcl interface to the library. Examples include database access, telephone control, MIDI controller access, and expect, which adds Tcl
commands to control interactive programs.
The most notable extension is Tk, a toolkit for graphical user interfaces. Tk defines Tcl commands that let you create and manipulate user
interface widgets. The script-based approach to user interface programming has three benefits:
Development is fast because of the rapid turnaround; there is no waiting for long compilations.
The Tcl commands provide a higher-level interface than most standard C library user-interface toolkits. Simple user interfaces
require just a handful of commands to define them. At the same time, it is possible to refine the user interface in order to get every
detail just so. The fast turnaround aids the refinement process.
The user interface can be factored out from the rest of your application. The developer can concentrate on the implementation of
the application core and then fairly painlessly work up a user interface. The core set of Tk widgets is often sufficient for all your
user interface needs. However, it is also possible to write custom Tk widgets in C, and again there are many contributed Tk
widgets available on the network.
There are other choices for extension languages that include Visual Basic, Scheme, Elisp, Perl, Python, Ruby and Javascript. Your choice
between them is partly a matter of taste. Tcl has simple constructs and looks somewhat like C. It is easy to add new Tcl primitives by writing C
procedures. Tcl is very easy to learn, and I have heard many great stories of users completing impressive projects in a short amount of time
(e.g., a few weeks), even though they never used Tcl before.
Java has exploded onto the computer scene since this book was first published. Java is a great systems programming language that in the
long run could displace C and C++. This is fine for Tcl, which is designed to glue together building blocks written in any system programming
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
language. Tcl was designed to work with C, but has been adapted to work with the Java Virtual Machine. Where I say "C or C++", you can
now say "C, C++, or Java," but the details are a bit different with Java. This book does not describe the Tcl/Java interface, but you can find
TclBlend on the CD-ROM. TclBlend loads the Java Virtual Machine into your Tcl application and lets you invoke Java methods. It also lets
you implement Tcl commands in Java instead of C or C++. Jacl is a Tcl interpreter written in Java. It has some limitations compared with the
native C-based Tcl interpreter, but Jacl is great if you cannot use the native interpreter.
Javascript is a language from Netscape that is designed to script interactions with Web pages. Javascript is important because of its use in
HTML user interfaces. However, Tcl provides a more general purpose scripting solution that can be used in a wide variety of applications.
The Tcl/Tk Web browser plugin provides a way to run Tcl in your browser. It turns out to be more of a Java alternative than a JavaScript
alternative. The plugin lets you run Tcl applications inside your browser, while JavaScript gives you fine grain control over the browser and
HTML display. The plugin is described in Chapter 20.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Tcl and Tk Versions
Tcl and Tk continue to evolve. See http://www.beedub.com/book/ for updates and news about the latest Tcl releases. Tcl and Tk have had
separate version numbers for historical reasons, but they are released in pairs that work together. The original edition of this book was based
on Tcl 7.4 and Tk 4.0, and there were a few references to features in Tk 3.6. This fourth edition has been updated to reflect new features
added through Tcl/Tk 8.4:
Tcl 7.5 and Tk 4.1 had their final release in May 1996. These releases feature the port of Tk to the Windows and Macintosh
environments. The Safe-Tcl security mechanism was introduced to support safe execution of network applets. There is also
network socket support and a new Input/Output (I/O) subsystem to support high-performance event-driven I/O.
Tcl 7.6 and Tk 4.2 had their final release in October 1996. These releases include improvements in Safe-Tcl, and improvements
to the grid geometry manager introduced in Tk 4.1. Cross-platform support includes virtual events (e.g.,
<<Copy>> as opposed to
<Control-c>), standard dialogs, and more file manipulation commands.
Tcl 7.7 and Tk 4.3 were internal releases used for the development of the Tcl/Tk plug-in for the Netscape Navigator and Microsoft
Internet Explorer Web browsers. Their development actually proceeded in parallel to Tcl 7.6 and Tk 4.2. The plug-in has been
released for a wide variety of platforms, including Solaris/SPARC, Solaris/INTEL, SunOS, Linux, Digital UNIX, IRIX, HP/UX,
Windows 95, Windows NT, and the Macintosh. The browser plug-in supports Tcl applets in Web pages and uses the sophisticated
security mechanism of Safe-Tcl to provide safety.
Tcl 8.0 features an on-the-fly compiler for Tcl that provides many-times faster Tcl scripts. Tcl 8.0 supports strings with embedded
null characters. The compiler is transparent to Tcl scripts, but extension writers need to learn some new C APIs to take advantage
of its potential. The release history of 8.0 spread out over a couple of years as John Ousterhout moved from Sun Microsystems to
Scriptics Corporation. The widely used 8.0p2 release was made in the fall of 1997, but the final patch release, 8.0.5, was made in
the spring of 1999.
Tk changed its version to match Tcl at 8.0. Tk 8.0 includes a new platform-independent font mechanism, native menus and menu
bars, and more native widgets for better native look and feel on Windows and Macintosh.
Tcl/Tk 8.1 features full Unicode support, a new regular expression engine that provides all the features found in Perl 5, and thread
safety so that you can embed Tcl into multi threaded applications. Tk does a heroic job of finding the correct font to display your
Unicode characters, and it adds a message catalog facility so that you can write internationalized applications. The release history
of Tcl/Tk 8.1 also straddled the Sun to Scriptics transition. The first alpha release was made in the fall of 1997, and the final patch
release, 8.1.1, was made in May 1999.
Tcl/Tk 8.2 is primarily a bug fix and stabilization release. There are a few minor additions to the Tcl C library APIs to support more
extensions without requiring core patches. Tcl/Tk 8.2 went rapidly into final release in the summer of 1999.
Tcl/Tk 8.3 adds a broad collection of enhancements to Tcl and Tk. Tk started to get some long deserved attention with adoption of
the Dash and Image patches from Jan Nijtmans. The 8.3.0 release was in February, 2000, and the last patch release, 8.3.5, was
made in October, 2002.
Tcl/Tk 8.4 features a focus on performance, the addition of the Virtual File System Interface, and 3 new core Tk widgets: spinbox,
labeledframe, and panedwindow. This release was a long time in development. The first beta release was in June, 2000, and the
8.4.2 release was made in March, 2003.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Extending Tcl and Tk
Tcl is designed so that interesting additions can be made as extensions that do not require changes to the Tcl core. Many extensions are
available today: You can find them on the Web at:
http://www.tcl.tk/resource/
However, some changes require changes to Tcl/Tk itself. If you are interested in contributing to the continued improvement of Tcl/Tk, you can
help. There is a Tcl Core Team (TCT) and a formal Tcl Improvement Process (TIP). You can browse the current TIPs or contribute your own
at:
http://www.tcl.tk/cgi-bin/tct/tip/
The Tcl and Tk source code is maintained on a SourceForge project:
http://www.sourceforge.net/projects/tcl
http://www.sourceforge.net/projects/tktoolkit
All bug reports and patch submissions are logged in a database. Source code patches that are made according to the Tcl Engineering
Manual guidelines have the most chance of adoption. These guidelines describe code appearance (e.g., indentation), test suite requirements,
and documentation requirements.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Tcl on the World Wide Web
Start with these World Wide Web pages about Tcl:
http://www.tcl.tk/
http://tcl.activestate.com/
http://www.purl.org/NET/Tcl-FAQ/
The Tcler's Wiki is a very active site that is updated by its users (i.e., by you) with lots of great information about Tcl and its extensions:
http://wiki.tcl.tk/
The home page for this book contains errata for all editions. This is the only URL I control personally, and I plan to keep it up-to-date
indefinitely:
http://www.beedub.com/book/
The Prentice Hall Web site is:
http://www.prenhall.com/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Ftp Archives
These are some of the FTP sites that maintain Tcl archives:
ftp://ftp.tcl.tk/pub/tcl
ftp://src.doc.ic.ac.uk/packages/tcl/
ftp://ftp.luth.se/pub/unix/tcl/
ftp://ftp.sunet.se/pub/lang/tcl
ftp://ftp.cs.columbia.edu/archives/tcl
ftp://ftp.funet.fi/pub/languages/tcl
You can use a World Wide Web browser likeMozilla, Netscape, Internet Explorer, or Lynx to access these sites.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Newsgroups
The comp.lang.tcl newsgroup is very active. It provides a forum for questions and answers about Tcl. Announcements about Tcl extensions
and applications are posted to the comp.lang.tcl.announce newsgroup. The following web service provides a convenient way to read
newsgroups. Enter comp.lang.tcl in the search field on this page:
http://groups.google.com
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Who Should Read This Book
This book is meant to be useful to the beginner in Tcl as well as the expert. For the beginner and expert alike, I recommend careful study of
Chapter 1, Tcl Fundamentals. The programming model of Tcl is designed to be simple, but it is different from many programming languages.
The model is based on string substitutions, and it is important that you understand it properly to avoid trouble in complex cases. The
remainder of the book consists of examples that demonstrate how to use Tcl and Tk productively. For your reference, each chapter has
tables that summarize the Tcl commands and Tk widgets they describe.
This book assumes that you have some programming experience, although you should be able to get by even if you are a complete novice.
Knowledge of UNIX shell programming will help, but it is not required. Where aspects of window systems are relevant, I provide some
background information. Chapter 2 describes the details of using Tcl and Tk on UNIX, Windows, and Macintosh.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
How to Read This Book
This book is best used in a hands-on manner, trying the examples at the computer. The book tries to fill the gap between the terse Tcl and Tk
manual pages, which are complete but lack context and examples, and existing Tcl programs that may or may not be documented or well
written.
I recommend the on-line manual pages for the Tcl and Tk commands. They provide a detailed reference guide to each command. This book
summarizes much of the information from the manual pages, but it does not provide the complete details, which can vary from release to
release. HTML versions of the on-line manual pages can be found on the CD-ROM that comes with this book.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
On-line Examples
The book comes with a CD-ROM that has source code for all of the examples, plus a selection of Tcl freeware found on the Internet. The
CD-ROM is readable on UNIX, Windows, and Macintosh. There, you will find the versions of Tcl and Tk that were available as the book went
to press. You can also retrieve the sources shown in the book from my personal Web site:
http://www.beedub.com/book/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Typographic Conventions
The more important examples are set apart with a title and horizontal rules, while others appear in-line. The examples use courier for Tcl and
C code. When interesting results are returned by a Tcl command, those are presented below in oblique courier. The => is not part of the
return value in the following example.
expr 5 + 8
=> 13
The courier font is also used when naming Tcl commands and C procedures within sentences.
The usage of a Tcl command is presented in the following example. The command name and constant keywords appear in courier. Variable
values appear in courier oblique. Optional arguments are surrounded with question marks.
set varname ?value?
The name of a program is in italics:
xterm
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Hot Tips
The icon in the margin marks a "hot tip" as judged by the reviewers of the book. The visual markers help you
locate the more useful sections in the book. These are also listed in the index under Hot Tip.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Book Organization
The chapters of the book are divided into seven parts. The first part describes basic Tcl features. The first chapter describes the fundamental
mechanisms that characterize the Tcl language. This is an important chapter that provides the basic grounding you will need to use Tcl
effectively. Even if you have programmed in Tcl already, you should review Chapter 1. Chapter 2 goes over the details of using Tcl and Tk on
UNIX, Windows, and Macintosh. Chapter 3 presents a sample application, a CGI script, that illustrates typical Tcl programming. The rest of
Part I covers the basic Tcl commands in more detail, including string handling, data types, control flow, procedures, and scoping issues.
Part I
finishes with a description of the facilities for file I/O and running other programs.
Part II describes advanced Tcl programming. It starts witheval, which lets you generate Tcl programs on the fly. Regular expressions provide
powerful string processing. If your data-processing application runs slowly, you can probably boost its performance significantly with the
regular expression facilities. Namespaces partition the global scope of procedures and variables. Unicode and message catalogs support
internationalized applications. Libraries and packages provide a way to organize your code for sharing among projects. The introspection
facilities of Tcl tell you about the internal state of Tcl. Event driven I/O helps server applications manage several clients simultaneously.
Network sockets are used to implement the HTTP protocol used to fetch pages on the World Wide Web.
The last few chapters in Part II describe platforms and frameworks for application development. Safe-Tcl is used to provide a secure
environment to execute Tcl applets in a Web browser. TclHttpd is an extensible web server built in Tcl. You can build applications on top of
this server, or embed it into your existing applications to give them a web interface. Starkits are an exciting new way to package and deploy
Tcl/Tk applications. They use the new Virtual File System (VFS) facilities to embed a private file system right in the Starkit.
Part III introduces Tk. It gives an overview of the toolkit facilities. A few complete examples are examined in detail to illustrate the features of
Tk. Event bindings associate Tcl commands with events like keystrokes and button clicks. Part III ends with three chapters on the Tk
geometry managers that provide powerful facilities for organizing your user interface.
Part IV describes the Tk widgets. These include buttons, menus, scrollbars, labels, text entries, multiline and multifont text areas, drawing
canvases, listboxes, and scales. The Tk widgets are highly configurable and very programmable, but their default behaviors make them easy
to use as well. The resource database that can configure widgets provides an easy way to control the overall look of your application.
Part V describes the rest of the Tk facilities. These include selections, keyboard focus, and standard dialogs. Fonts, colors, images, and other
attributes that are common to the Tk widgets are described in detail. This part ends with a few larger Tk examples.
Part VI is an introduction to C programming and Tcl. The goal of this part is to get you started in the right direction when you need to extend
Tcl with new commands written in C or integrate Tcl into custom applications.
Part VII provides a chapter for each of the Tcl/Tk releases covered by the book. These chapters provide details about what features were
changed and added. They also provide a quick reference if you need to update a program or start to use a new version.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
What's New in the Fourth Edition
The fourth edition is up-to-date with Tcl/Tk 8.4, which adds many new features. Tcl has a new Virtual File System (VFS) feature that lets you
transparently embed a file system in your application, or make remote resources such as FTP and Web sites visible through the regular file
system interface. Chapter 22 is a new chapter on Tclkit and Starkits that use the Metakit embedded database to store scripts and other files.
The VFS makes these files appear in a private file system. Starkits provide a new way to package and deploy Tcl/Tk applications. Chapter 21
is a new chapter on using the multi-threading support in Tcl. This is very useful when embedding Tcl in threaded server applications. There
are a number of new Tk features, including three new widgets. The spinbox (i.e., combobox) is like an entry widget with a drop-down selection
box. The labeled frame provides a new way to decorate frames. The panedwindow is a specialized geometry manager that provides a new
way to organize your user interfaces.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
.
[ Team LiB ]
Other Tcl Books
This book was the second Tcl book after the original book by John Ousterhout, the creator of Tcl. Since then, many other Tcl books have
been published. The following are just some of the books currently available.
Tcl/Tk: A Developer's Guide, 2nd Ed. (Academic Press, 2003) by Clif Flynt is a good example-oriented book that has been recently updated.
Tcl and the Tk Toolkit (Addison-Wesley, 1994) by John Ousterhout provides a broad overview of all aspects of Tcl and Tk, even though it
covers only Tcl 7.3 and Tk 3.6. The book provides a more detailed treatment of C programming for Tcl extensions.
Exploring Expect (O'Reilly & Associates, Inc., 1995) by Don Libes is a great book about an extremely useful Tcl extension.Expect lets you
automate the use of interactive programs like ftp and telnet that expect to interact with a user. By combiningExpect and Tk, you can create
graphical user interfaces for old applications that you cannot modify directly.
Tcl/Tk in a Nutshell (O'Reilly, 1999) by Paul Raines and Jeff Tranter is a handy reference guide. It covers several popular extensions
including Expect, [incr Tcl], Tix, TclX, BLT, SybTcl, OraTcl, and TclODBC. There is a tiny pocket-reference guide for Tcl/Tk that may eliminate
the need to thumb through my large book to find the syntax of a particular Tcl or Tk command.
Web Tcl Complete (McGraw Hill, 1999) by Steve Ball describes programming with the Tcl Web Server. It also covers Tcl/Java integration using
TclBlend.
[incr Tcl] From The Ground Up (Osborn-McGraw Hill, 1999) by Chad Smith describes the [incr Tcl] object-oriented extension to Tcl.
Tcl/Tk for Programmers (IEEE Computer Society, 1998) by Adrian Zimmer describes Unix and Windows programming with Tcl/Tk. This book
also includes solved exercises at the end of each chapter.
Building Network Management Tools with Tcl/Tk (Prentice Hall, 1998) by Dave Zeltserman and Gerald Puoplo. This describes how to build
SNMP tools.
Graphical Applications with Tcl & Tk (M&T Books, 1997) by Eric Johnson is oriented toward Windows users. The second edition covers Tcl/Tk
8.0.
Tcl/Tk Tools (O'Reilly & Associates, Inc., 1997) by Mark Harrison describes many useful Tcl extensions. These include Oracle and Sybase
interfaces, object-oriented language enhancements, additional Tk widgets, and much more. The chapters were contributed by the authors of
the extensions, so they provide authoritative information on some excellent additions to the Tcl toolbox.
Effective Tcl/Tk Programming (Addison Wesley, 1997) by Michael McLennan and Mark Harrison illustrate Tcl and Tk with examples and
application design guidelines.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
First Edition Thanks
I would like to thank my managers and colleagues at Xerox PARC for their patience with me as I worked on this book. The tips and tricks in
this book came partly from my own work as I helped lab members use Tcl, and partly from them as they taught me. Dave Nichols' probing
questions forced me to understand the basic mechanisms of the Tcl interpreter. Dan Swinehart and Lawrence Butcher kept me sharp with
their own critiques. Ron Frederick and Berry Kerchival adopted Tk for their graphical interfaces and amazed me with their rapid results. Becky
Burwell, Rich Gold, Carl Hauser, John Maxwell, Ken Pier, Marvin Theimer, and Mohan Vishwanath made use of my early drafts, and their
questions pointed out large holes in the text. Karin Petersen, Bill Schilit, and Terri Watson kept life interesting by using Tcl in very
nonstandard ways. I especially thank my managers, Mark Weiser and Doug Terry, for their understanding and support.
I thank John Ousterhout for Tcl and Tk, which are wonderful systems built with excellent craftsmanship. John was kind enough to provide me
with an advance version of Tk 4.0 so that I could learn about its new features well before its first beta release.
Thanks to the Tcl programmers out on the Net, from whom I learned many tricks. John LoVerso and Stephen Uhler are the hottest Tcl
programmers I know.
Many thanks to the patient reviewers of early drafts: Pierre David, Clif Flynt, Simon Kenyon, Eugene Lee, Don Libes, Lee Moore, Joe Moss,
Hador Shemtov, Frank Stajano, Charles Thayer, and Jim Thornton.
Many folks contributed suggestions by email: Miguel Angel, Stephen Bensen, Jeff Blaine, Tom Charnock, Brian Cooper, Patrick D'Cruze,
Benoit Desrosiers, Ted Dunning, Mark Eichin, Paul Friberg, Carl Gauthier, David Gerdes, Klaus Hackenberg, Torkle Hasle, Marti Hearst,
Jean-Pierre Herbert, Jamie Honan, Norman Klein, Joe Konstan, Susan Larson, Håkan Liljegren, Lionel Mallet, Dejan Milojicic, Greg Minshall,
Bernd Mohr, Will Morse, Heiko Nardmann, Gerd Neugebauer, TV Raman, Cary Renzema, Rob Riepel, Dan Schenk, Jean-Guy Schneider,
Elizabeth Scholl, Karl Schwamb, Rony Shapiro, Peter Simanyi, Vince Skahan, Bill Stumbo, Glen Vanderburg, Larry Virden, Reed Wade, and
Jim Wight. Unfortunately, I could not respond to every suggestion, even some that were excellent.
Thanks to the editors and staff at Prentice Hall. Mark Taub has been very helpful as I progressed through my first book. Lynn Schneider and
Kerry Reardon were excellent copy and production editors, respectively.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Second Edition Thanks
I get to thank John Ousterhout again, this time for supporting me as I worked in the Tcl/Tk group at Sun Microsystems. The rest of the group
deserve a lot of credit for turning Tcl and Tk into a dynamite cross-platform solution. Scott Stanton led the Tk port to the PC. Ray Johnson led
the Tk port to the Macintosh. Jacob Levy implemented the event-driven I/O system, Safe-Tcl, and the browser plug-in. Brian Lewis built the
Tcl compiler. Ken Corey worked on Java integration and helped with the SpecTcl user interface builder. Syd Polk generalized the menu
system to work with native widgets on the Macintosh and Windows. Colin Stevens generalized the font mechanism and worked on
internationalization for Tk.
Stephen Uhler deserves special thanks for inspiring many of the cool examples I use in this book. He was the lead on the SpecTcl user
interface builder. He built the core HTML display library on which I based an editor. We worked closely together on the first versions of
TclHttpd. He taught me how to write compact, efficient Tcl code and to use regular expression substitutions in amazing ways. I hope he has
learned at least a little from me.
Thanks again to Mark Taub, Eileen Clark, and Martha Williams at Prentice Hall. George Williams helped me assemble the files for the
CD-ROM.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Third Edition Thanks
John Ousterhout continues his wonderful role as Tcl benefactor, now as founder of Scriptics Corporation. I'd like to thank every one of the
great folks that I work with at Scriptics, especially the pioneering crew of Sarah Daniels, Scott Stanton, Ray Johnson, Bryan Surles, Melissa
Chawla, Lee Bernhard, Suresh Sastry, Emil Scaffon, Pat P., Scott Redman, and Berry Kercheval. The rest of the gang deserves a big thanks
for making Scriptics such an enjoyable place to work. Jerry Peek, who is a notable author himself, provided valuable advice and wonderfully
detailed comments! Ken Jones told me about a great indexing tool.
I'd like to thank all the readers that drop me the encouraging note or probing question via email. I am always interested in new and interesting
uses of Tcl!
Thanks to the editors at Prentice Hall: Mark Taub, Joan McNamara, and Joan Eurell. Mark continues to encourage me to come out with new
editions, and the Joans helped me complete this third edition on time.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Fourth Edition Thanks
I'd like to thank Jeff Hobbs and Ken Jones for making this project happen. Jeff has done a great service to the Tcl community as "The Tcl
Guy". His leadership and hard work have been responsible for the steady pace of new Tcl/Tk releases. Ken is a great Tcl teacher and his
experiences teaching are reflected in his additions to the book for this 4th edition. Again, without these two lending a hand, I just wouldn't
have found the time for this edition.
I'd like to thank the Tcl Core Team and the supporting cast of contributors to the Tcl/Tk code base. The TCT provides a great framework to
keep Tcl a high quality software product that continues to adopt new an interesting features.
I'd like to thank Jean-Claude Wippler and Steve Landers for Metakit, Tclkit, and Starkits. These provide a delightful way to package and
deploy Tcl applications. I expect to see a lot more from these technologies in the future. Several readers provided great feedback on the
Starkit material: Robert Techentin, Steve Blinkhorn, Frank Sergeant, Arjen Markus, Uwe Koloska, Larry Virden, Tom Krehbiel, and Donald
Porter.
I'd like to thank Prentice Hall for their continued support. Mark Taub continues his role as godfather of this book. Kathleen Caren was the able
production editor for this edition.
Finally, I thank my wonderful wife Jody for her love, kindness, patience, wit, and understanding as I worked long hours. Happily, many of
those hours were spent working from home. I now have three sons, Christopher, Daniel, and Michael, who get the credit for keeping me from
degenerating into a complete nerd.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Contact the Author
I am always open to comments about this book. My email address is welch@acm.org. It helps me sort through my mail if you put the word
"book" or the title of the book into the email subject line. Visit my Web site for current news about the book and my other interests. I maintain
an errata page for each edition, so please consult that and feel free to send bug reports!
http://www.beedub.com/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Part I: Tcl Basics
Part I introduces the basics of Tcl. Everyone should read Chapter 1, which describes the fundamental properties of the
language. Tcl is really quite simple, so beginners can pick it up quickly. The experienced programmer should review
Chapter 1 to eliminate any misconceptions that come from using other languages.
Chapter 2 is a short introduction to running Tcl and Tk on UNIX, Windows, and Macintosh systems. You may want to
look at this chapter first so you can try out the examples as you read Chapter 1.
Chapter 3 presents a sample application, a CGI script, that implements a guestbook for a Web site. The example uses
several facilities that are described in detail in later chapters. The goal is to provide a working example that illustrates
the power of Tcl.
The rest of Part I covers basic programming with Tcl. Simple string processing is covered inChapter 4. Tcl lists, which
share the syntax rules of Tcl commands, are explained in Chapter 5. Control structure like loops and if statements are
described in Chapter 6. Chapter 7 describes Tcl procedures, which are new commands that you write in Tcl.Chapter 8
discusses Tcl arrays. Arrays are the most flexible and useful data structure in Tcl. Chapter 9 describes file I/O and
running other programs. These facilities let you build Tcl scripts that glue together other programs and process data in
files.
After reading Part I you will know enough Tcl to read and understand other Tcl programs, and to write simple programs
yourself.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 1. Tcl Fundamentals
This chapter describes the basic syntax rules for the Tcl scripting language. It describes the basic mechanisms used by the Tcl interpreter:
substitution and grouping. It touches lightly on the following Tcl commands: puts, format, set, expr, string, while, incr, and proc.
Tcl is a string-based command language. The language has only a few fundamental constructs and relatively little syntax, which makes it
easy to learn. The Tcl syntax is meant to be simple. Tcl is designed to be a glue that assembles software building blocks into applications. A
simpler glue makes the job easier. In addition, Tcl is interpreted when the application runs. The interpreter makes it easy to build and refine
your application in an interactive manner. A great way to learn Tcl is to try out commands interactively. If you are not sure how to run Tcl on
your system, see Chapter 2 for instructions for starting Tcl on UNIX, Windows, and Macintosh systems.
This chapter takes you through the basics of the Tcl language syntax. Even if you are an expert programmer, it is worth taking the time to
read these few pages to make sure you understand the fundamentals of Tcl. The basic mechanisms are all related to strings and string
substitutions, so it is fairly easy to visualize what is going on in the interpreter. The model is a little different from some other programming
languages with which you may already be familiar, so it is worth making sure you understand the basic concepts.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Tcl Commands
Tcl stands for Tool Command Language. A command does something for you, like output a string, compute a math expression, or display a
widget on the screen. Tcl casts everything into the mold of a command, even programming constructs like variable assignment and
procedure definition. Tcl adds a tiny amount of syntax needed to properly invoke commands, and then it leaves all the hard work up to the
command implementation.
The basic syntax for a Tcl command is:
command arg1 arg2 arg3 ...
The command is either the name of a built-in command or a Tcl procedure. White space (i.e., spaces or tabs) is used to separate the
command name and its arguments, and a newline (i.e., the end of line character) or semicolon is used to terminate a command. Tcl does not
interpret the arguments to the commands except to perform grouping, which allows multiple words in one argument, andsubstitution, which is
used with programming variables and nested command calls. The behavior of the Tcl command processor can be summarized in three basic
steps:
Argument grouping.
Value substitution of nested commands, variables, and backslash escapes.
Command invocation. It is up to the command to interpret its arguments.
This model is described in detail in this Chapter.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Hello, World!
Example 1-1 The "Hello, World!" example
puts stdout {Hello, World!}
=> Hello, World!
In this example, the command is puts, which takes two arguments: an I/O stream identifier and a string
. puts writes the string to the I/O stream
along with a trailing newline character. There are two points to emphasize:
Arguments are interpreted by the command. In the example, stdout is used to identify the standard output stream. The use of
stdout as a name is a convention employed byputs and the other I/O commands. Also,stderr is used to identify the standard error
output, and stdin is used to identify the standard input.Chapter 9 describes how to open other files for I/O.
Curly braces are used to group words together into a single argument. The puts command receives Hello, World! as its second
argument.
The braces are not part of the value.
The braces are syntax for the interpreter, and they get stripped off before the value is passed to the command. Braces group all characters,
including newlines and nested braces, until a matching brace is found. Tcl also uses double quotes for grouping. Grouping arguments will be
described in more detail later.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Variables
The set command is used to assign a value to a variable. It takes two arguments: The first is the name of the variable, and the second is the
value. Variable names can be any length, and case is significant. In fact, you can use any character in a variable name.
It is not necessary to declare Tcl variables before you use them.
The interpreter will create the variable when it is first assigned a value. The value of a variable is obtained later with the dollar-sign syntax,
illustrated in Example 1-2:
Example 1-2 Tcl variables
set var 5
=> 5
set b $var
=> 5
The second set command assigns to variable b the value of variable var. The use of the dollar sign is our first example of substitution. You
can imagine that the second set command gets rewritten by substituting the value ofvar for $var to obtain a new command.
set b 5
The actual implementation of substitution is more efficient, which is important when the value is large.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Command Substitution
The second form of substitution is command substitution. A nested command is delimited by square brackets,[ ]. The Tcl interpreter takes
everything between the brackets and evaluates it as a command. It rewrites the outer command by replacing the square brackets and
everything between them with the result of the nested command. This is similar to the use of backquotes in other shells, except that it has the
additional advantage of supporting arbitrary nesting of commands.
Example 1-3 Command substitution
set len [string length foobar]
=> 6
In Example 1-3, the nested command is:
string length foobar
This command returns the length of the string foobar. The string command is described in detail starting on page 49. The nested command
runs first. Then, command substitution causes the outer command to be rewritten as if it were:
set len 6
If there are several cases of command substitution within a single command, the interpreter processes them from left to right. As each right
bracket is encountered, the command it delimits is evaluated. This results in a sensible ordering in which nested commands are evaluated
first so that their result can be used in arguments to the outer command.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Math Expressions
The Tcl interpreter itself does not evaluate math expressions. Tcl just does grouping, substitutions and command invocations. The expr
command is used to parse and evaluate math expressions.
Example 1-4 Simple arithmetic
expr 7.2 / 4
=> 1.8
The math syntax supported by expr is the same as the C expression syntax. Theexpr command deals with integer, floating point, and boolean
values. Logical operations return either 0 (false) or 1 (true). Integer values are promoted to floating point values as needed. Octal values are
indicated by a leading zero (e.g., 033 is 27 decimal). Hexadecimal values are indicated by a leading0x. Scientific notation for floating point
numbers is supported. A summary of the operator precedence is given on page 20.
You can include variable references and nested commands in math expressions. The following example uses expr to add the value of x to the
length of the string foobar. As a result of the innermost command substitution, theexpr command sees 6 + 7, and len gets the value 13:
Example 1-5 Nested commands
set x 7
set len [expr [string length foobar] + $x]
=> 13
The expression evaluator supports a number of built-in math functions. (For a complete listing, see page 21.) Example 1-6 computes the
value of pi:
Example 1-6 Built-in math functions
set pi [expr 2*asin(1.0)]
=> 3.1415926535897931
The implementation of expr is careful to preserve accurate numeric values and avoid conversions between numbers and strings. However,
you can make expr operate more efficiently by grouping the entire expression in curly braces. The explanation has to do with the byte code
compiler that Tcl uses internally, and its effects are explained in more detail on page 15. For now, you should be aware that these
expressions are all valid and run faster than the examples shown above:
Example 1-7 Grouping expressions with braces
expr {7.2 / 4}
set len [expr {[string length foobar] + $x}]
set pi [expr {2*asin(1.0)}]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
.
[ Team LiB ]
Backslash Substitution
The final type of substitution done by the Tcl interpreter is backslash substitution. This is used to quote characters that have special meaning
to the interpreter. For example, you can specify a literal dollar sign, brace, or bracket by quoting it with a backslash. As a rule, however, if you
find yourself using lots of backslashes, there is probably a simpler way to achieve the effect you are striving for. In particular, the list
command described on page 65 will do quoting for you automatically. InExample 1-8 backslash is used to get a literal$:
Example 1-8 Quoting special characters with backslash
set dollar \$foo
=> $foo
set x $dollar
=> $foo
Only a single round of interpretation is done.
The second set command in the example illustrates an important property of Tcl. The value ofdollar does not affect the substitution performed
in the assignment to x. In other words, the Tcl parser does not care about the value of a variable when it does the substitution. In the example,
the value of x and dollar is the string $foo. In general, you do not have to worry about the value of variables until you useeval, which is
described in Chapter 10.
You can also use backslash sequences to specify characters with their Unicode, hexadecimal, or octal value:
set escape \u001b
set escape \0x1b
set escape \033
The value of variable escape is the ASCII ESC character, which has character code27. Table 1-1 on page 20 summarizes backslash
substitutions.
A common use of backslashes is to continue long commands on multiple lines. This is necessary because a newline terminates a command.
The backslash in the next example is required; otherwise the expr command gets terminated by the newline after the plus sign.
Example 1-9 Continuing long lines with backslashes
set totalLength [expr [string length $one] + \
[string length $two]]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
There are two fine points to escaping newlines. First, if you are grouping an argument as described in the next section, then you do not need
to escape newlines; the newlines are automatically part of the group and do not terminate the command. Second, a backslash as the last
character in a line is converted into a space, and all the white space at the beginning of the next line is replaced by this substitution. In other
words, the backslash-newline sequence also consumes all the leading white space on the next line.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Grouping with Braces and Double Quotes
Double quotes and curly braces are used to group words together into one argument. The difference between double quotes and curly braces
is that quotes allow substitutions to occur in the group, while curly braces prevent substitutions. This rule applies to command, variable, and
backslash substitutions.
Example 1-10 Grouping with double quotes vs. braces
set s Hello
=> Hello
puts stdout "The length of $s is [string length $s]."
=> The length of Hello is 5.
puts stdout {The length of $s is [string length $s].}
=> The length of $s is [string length $s].
In the second command of Example 1-10, the Tcl interpreter does variable and command substitution on the second argument toputs. In the
third command, substitutions are prevented, so the string is printed as is.
In practice, grouping with curly braces is used when substitutions on the argument must be delayed until a later time (or never done at all).
Examples include loops, conditional statements, and procedure declarations. Double quotes are useful in simple cases like the puts
command previously shown.
Another common use of quotes is with the format command. This is similar to the C printf function. The first argument to format is a format
specifier that often includes special characters like newlines, tabs, and spaces. The easiest way to specify these characters is with backslash
sequences (e.g., \n for newline and \t for tab). The backslashes must be substituted before theformat command is called, so you need to use
quotes to group the format specifier.
puts [format "Item: %s\t%5.3f" $name $value]
Here format is used to align a name and a value with a tab. The%s and %5.3f indicate how the remaining arguments to format are to be
formatted. Note that the trailing \n usually found in a C printf call is not needed because puts provides one for us. For more information about
the format command, see page 56.
Square Brackets Do Not Group
The square bracket syntax used for command substitution does not provide grouping. Instead, a nested command is considered part of the
current group. In the command below, the double quotes group the last argument, and the nested command is just part of that group.
puts stdout "The length of $s is [string length $s]."
If an argument is made up of only a nested command, you do not need to group it with double-quotes because the Tcl parser treats the whole
nested command as part of the group.
puts stdout [string length $s]
The following is a redundant use of double quotes:
puts stdout "[expr $x + $y]"
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Grouping before Substitution
The Tcl parser makes a single pass through a command as it makes grouping decisions and performs string substitutions. Grouping
decisions are made before substitutions are performed, which is an important property of Tcl. This means that the values being substituted do
not affect grouping because the grouping decisions have already been made.
The following example demonstrates how nested command substitution affects grouping. A nested command is treated as an unbroken
sequence of characters, regardless of its internal structure. It is included with the surrounding group of characters when collecting arguments
for the main command.
Example 1-11 Embedded command and variable substitution
set x 7; set y 9
puts stdout $x+$y=[expr $x + $y]
=> 7+9=16
In Example 1-11, the second argument to puts is:
$x+$y=[expr $x + $y]
The white space inside the nested command is ignored for the purposes of grouping the argument. By the time Tcl encounters the left
bracket, it has already done some variable substitutions to obtain:
7+9=
When the left bracket is encountered, the interpreter calls itself recursively to evaluate the nested command. Again, the $x and $y are
substituted before calling expr. Finally, the result ofexpr is substituted for everything from the left bracket to the right bracket. Theputs
command gets the following as its second argument:
7+9=16
Grouping before substitution.
The point of this example is that the grouping decision about puts's second argument is made before the command substitution is done. Even
if the result of the nested command contained spaces or other special characters, they would be ignored for the purposes of grouping the
arguments to the outer command. Grouping and variable substitution interact the same as grouping and command substitution. Spaces or
special characters in variable values do not affect grouping decisions because these decisions are made before the variable values are
substituted.
If you want the output to look nicer in the example, with spaces around the + and =, then you must use double quotes to explicitly group the
argument to puts:
puts stdout "$x + $y = [expr $x + $y]"
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The double quotes are used for grouping in this case to allow the variable and command substitution on the argument to puts.
Grouping Math Expressions with Braces
It turns out that expr does its own substitutions inside curly braces. This is explained in more detail on page 15. This means you can write
commands like the one below and the substitutions on the variables in the expression still occur:
puts stdout "$x + $y = [expr {$x + $y}]"
More Substitution Examples
If you have several substitutions with no white space between them, you can avoid grouping with quotes. The following command sets concat
to the value of variables a, b, and c all concatenated together:
set concat $a$b$c
Again, if you want to add spaces, you'll need to use quotes:
set concat "$a $b $c"
In general, you can place a bracketed command or variable reference anywhere. The following computes a command name:
[findCommand $x] arg arg
When you use Tk, you often use widget names as command names:
$text insert end "Hello, World!"
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Procedures
Tcl uses the proc command to define procedures. Once defined, a Tcl procedure is used just like any of the other built-in Tcl commands. The
basic syntax to define a procedure is:
proc name arglist body
The first argument is the name of the procedure being defined. The second argument is a list of parameters to the procedure. The third
argument is a command body that is one or more Tcl commands.
The procedure name is case sensitive, and in fact it can contain any characters. Procedure names and variable names do not conflict with
each other. As a convention, this book begins procedure names with uppercase letters and it begins variable names with lowercase letters.
Good programming style is important as your Tcl scripts get larger. Tcl coding style is discussed in Chapter 12.
Example 1-12 Defining a procedure
proc Diag {a b} {
set c [expr {sqrt($a * $a + $b * $b)}]
return $c
}
puts "The diagonal of a 3, 4 right triangle is [Diag 3 4]"
=> The diagonal of a 3, 4 right triangle is 5.0
The Diag procedure defined in the example computes the length of the diagonal side of a right triangle given the lengths of the other two
sides. The sqrt function is one of many math functions supported by theexpr command. The variable c is local to the procedure; it is defined
only during execution of Diag. Variable scope is discussed further inChapter 7. It is not really necessary to use the variablec in this example.
The procedure can also be written as:
proc Diag {a b} {
return [expr {sqrt($a * $a + $b * $b)}]
}
The return command is used to return the result of the procedure. Thereturn command is optional in this example because the Tcl interpreter
returns the value of the last command in the body as the value of the procedure. So, the procedure could be reduced to:
proc Diag {a b} {
expr {sqrt($a * $a + $b * $b)}
}
Note the stylized use of curly braces in the example. The curly brace at the end of the first line starts the third argument to proc, which is the
command body. In this case, the Tcl interpreter sees the opening left brace, causing it to ignore newline characters and scan the text until a
matching right brace is found. Double quotes have the same property. They group characters, including newlines, until another double quote
is found. The result of the grouping is that the third argument to proc is a sequence of commands. When they are evaluated later, the
embedded newlines will terminate each command.
The other crucial effect of the curly braces around the procedure body is to delay any substitutions in the body until the time the procedure is
called. For example, the variables a, b, and c are not defined until the procedure is called, so we do not want to do variable substitution at the
time Diag is defined.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The proc command supports additional features such as having variable numbers of arguments and default values for arguments. These are
described in detail in Chapter 7.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
A Factorial Example
To reinforce what we have learned so far, below is a longer example that uses a while loop to compute the factorial function:
Example 1-13 A while loop to compute factorial
proc Factorial {x} {
set i 1; set product 1
while {$i <= $x} {
set product [expr {$product * $i}]
incr i
}
return $product
}
Factorial 10
=> 3628800
The semicolon is used on the first line to remind you that it is a command terminator just like the newline character. The while loop is used to
multiply all the numbers from one up to the value of x. The first argument to while is a boolean expression, and its second argument is a
command body to execute. The while command and other control structures are described inChapter 6.
The same math expression evaluator used by the expr command is used by while to evaluate the boolean expression. There is no need to
explicitly use the expr command in the first argument to while, even if you have a much more complex expression.
The loop body and the procedure body are grouped with curly braces in the same way. The opening curly brace must be on the same line as
proc and while. If you like to put opening curly braces on the line after awhile or if statement, you must escape the newline with a backslash:
while {$i < $x} \
{
set product ...
}
Always group expressions and command bodies with curly braces.
Curly braces around the boolean expression are crucial because they delay variable substitution until the while command implementation
tests the expression. The following example is an infinite loop:
set i 1; while $i<=10 {incr i}
[*]
The loop will run indefinitely. The reason is that the Tcl interpreter will substitute for$i before while is called, so while gets a constant
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
expression 1<=10 that will always be true. You can avoid these kinds of errors by adopting a consistent coding style that groups expressions
with curly braces:
[*]
Ironically, Tcl 8.0 introduced a byte-code compiler, and the first releases of Tcl 8.0 had a bug in the compiler that
caused this loop to terminate! This bug is fixed in the 8.0.5 patch release.
set i 1; while {$i<=10} {incr i}
The incr command is used to increment the value of the loop variablei. This is a handy command that saves us from the longer command:
set i [expr {$i + 1}]
The incr command can take an additional argument, a positive or negative integer by which to change the value of the variable. Using this
form, it is possible to eliminate the loop variable i and just modify the parameterx. The loop body can be written like this:
while {$x > 1} {
set product [expr {$product * $x}]
incr x -1
}
Example 1-14 shows factorial again, this time using a recursive definition. A recursive function is one that calls itself to complete its work.
Each recursive call decrements x by one, and whenx is one, then the recursion stops.
Example 1-14 A recursive definition of factorial
proc Factorial {x} {
if {$x <= 1} {
return 1
} else {
return [expr {$x * [Factorial [expr {$x - 1}]]}]
}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
More about Variables
The set command will return the value of a variable if it is only passed a single argument. It treats that argument as a variable name and
returns the current value of the variable. The dollar-sign syntax used to get the value of a variable is really just an easy way to use the set
command. Example 1-15 shows a trick you can play by putting the name of one variable into another variable:
Example 1-15 Using set to return a variable value
set var {the value of var}
=> the value of var
set name var
=> var
set name
=> var
set $name
=> the value of var
This is a somewhat tricky example. In the last command, $name gets substituted with var. Then, the set command returns the value of var,
which is the value of var. Nested set commands provide another way to achieve a level of indirection. The lastset command above can be
written as follows:
set [set name]
=> the value of var
Using a variable to store the name of another variable may seem overly complex. However, there are some times when it is very useful.
There is even a special command, upvar, that makes this sort of trick easier. Theupvar command is described in detail in Chapter 7.
Funny Variable Names
The Tcl interpreter makes some assumptions about variable names that make it easy to embed variable references into other strings. By
default, it assumes that variable names contain only letters, digits, and the underscore. The construct $foo.o represents a concatenation of
the value of foo and the literal ".o".
If the variable reference is not delimited by punctuation or white space, then you can use curly braces to explicitly delimit the variable name
(e.g., ${x}). You can also use this to reference variables with funny characters in their name, although you probably do not want variables
named like that. If you find yourself using funny variable names, or computing the names of variables, then you may want to use the upvar
command.
Example 1-16 Embedded variable references
set foo filename
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set object $foo.o
=> filename.o
set a AAA
set b abc${a}def
=> abcAAAdef
set .o yuk!
set x ${.o}y
=> yuk!y
The unset Command
You can delete a variable with theunset command:
unset ?-nocomplain? ?--? varName varName2 ...
Any number of variable names can be passed to the unset command. However, unset will raise an error if a variable is not already defined,
unless the -nocomplain is given. Use -- to unset a variable named -nocomplain.
Using info to Find Out about Variables
The existence of a variable can be tested with the info exists command. For example, becauseincr requires that a variable exist, you might
have to test for the existence of the variable first.
Example 1-17 Using info to determine if a variable exists
if {![info exists foobar]} {
set foobar 0
} else {
incr foobar
}
Example 7-6 on page 92 implements a version of incr which handles this case.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
More about Math Expressions
This section describes a few fine points about math in Tcl scripts. In Tcl 7.6 and earlier versions math is not that efficient because of
conversions between strings and numbers. The expr command must convert its arguments from strings to numbers. It then does all its
computations with double precision floating point values. The result is formatted into a string that has, by default, 12 significant digits. This
number can be changed by setting the tcl_precision variable to the number of significant digits desired. Seventeen digits of precision are
enough to ensure that no information is lost when converting back and forth between a string and an IEEE double precision number:
Example 1-18 Controlling precision with tcl_precision
expr 1 / 3
=> 0
expr 1 / 3.0
=> 0.333333333333
set tcl_precision 17
=> 17
expr 1 / 3.0
# The trailing 1 is the IEEE rounding digit
=> 0.33333333333333331
In Tcl 8.0 and later versions, the overhead of conversions is eliminated in most cases by the built-in compiler. Even so, Tcl was not designed
to support math-intensive applications. You may want to implement math-intensive code in a compiled language and register the function as a
Tcl command as described in Chapter 47.
There is support for string comparisons by expr, so you can test string values inif statements. You must use quotes so thatexpr knows to do
string comparisons:
if {$answer == "yes"} { ... }
However, the string compare and string equal commands described in Chapter 4 are more reliable because expr may do conversions on
strings that look like numbers. The issues with string operations and expr are discussed on page 52. Tcl 8.4 introducedeq and ne expr
operators to allow strict string based comparison.
Expressions can include variable and command substitutions and still be grouped with curly braces. This is because an argument to expr is
subject to two rounds of substitution: one by the Tcl interpreter, and a second by expr itself. Ordinarily this is not a problem because math
values do not contain the characters that are special to the Tcl interpreter. The second round of substitutions is needed to support commands
like while and if that use the expression evaluator internally.
Grouping expressions can make them run more efficiently.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
You should always group expressions in curly braces and let expr do command and variable substitutions. Otherwise, your values may suffer
extra conversions from numbers to strings and back to numbers. Not only is this process slow, but the conversions can lose precision in
certain circumstances. For example, suppose x is computed from a math function:
set x [expr {sqrt(2.0)}]
At this point the value ofx is a double-precision floating point value, just as you would expect. If you do this:
set two [expr $x * $x]
then you may or may not get 2.0 as the result! This is because Tcl will substitute $x and expr will concatenate all its arguments into one string,
and then parse the expression again. In contrast, if you do this:
set two [expr {$x * $x}]
then expr will do the substitutions, and it will be careful to preserve the floating point value ofx. The expression will be more accurate and run
more efficiently because no string conversions will be done. The story behind Tcl values is described in more detail in Chapter 47 on C
programming and Tcl.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Comments
Tcl uses the pound character, #, for comments. Unlike in many other languages, the# must occur at the beginning of a command. A# that
occurs elsewhere is not treated specially. An easy trick to append a comment to the end of a command is to precede the # with a semicolon
to terminate the previous command:
# Here are some parameters
set rate 7.0 ;# The interest rate
set months 60 ;# The loan term
One subtle effect to watch for is that a backslash effectively continues a comment line onto the next line of the script. In addition, a semicolon
inside a comment is not significant. Only a newline terminates comments:
# Here is the start of a Tcl comment \
and some more of it; still in the comment
The behavior of a backslash in comments is pretty obscure, but it can be exploited as shown inExample 2-3 on page 27.
A surprising property of Tcl comments is that curly braces inside comments are still counted for the purposes of finding matching brackets.
The motivation for this odd feature was to keep the original Tcl parser simpler. However, it means that the following will not work as expected
to comment out an alternate version of an if expression:
# if {boolean expression1} {
if {boolean expression2} {
some commands
}
The previous sequence results in an extra left curly brace, and probably a complaint about a missing close brace at the end of your script! A
technique I use to comment out large chunks of code is to put the code inside an if block that will never execute:
if {0} {
unused code here
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Substitution and Grouping Summary
The following rules summarize the fundamental mechanisms of grouping and substitution that are performed by the Tcl interpreter before it
invokes a command:
Command arguments are separated by white space, unless arguments are grouped with curly braces or double quotes as
described below.
Grouping with curly braces, { }, prevents substitutions. Braces nest. The interpreter includes all characters between the matching
left and right brace in the group, including newlines, semicolons, and nested braces. The enclosing (i.e., outermost) braces are not
included in the group's value.
Grouping with double quotes, " ", allows substitutions. The interpreter groups everything until another double quote is found,
including newlines and semicolons. The enclosing quotes are not included in the group of characters. A double-quote character
can be included in the group by quoting it with a backslash, (e.g., \").
Grouping decisions are made before substitutions are performed, which means that the values of variables or command results do
not affect grouping.
A dollar sign, $, causes variable substitution. Variable names can be any length, and case is significant. If variable references are
embedded into other strings, or if they include characters other than letters, digits, and the underscore, they can be distinguished
with the ${varname} syntax.
Square brackets, [ ], cause command substitution. Everything between the brackets is treated as a command, and everything
including the brackets is replaced with the result of the command. Nesting is allowed.
The backslash character, \, is used to quote special characters. You can think of this as another form of substitution in which the
backslash and the next character or group of characters are replaced with a new character.
Substitutions can occur anywhere unless prevented by curly brace grouping. Part of a group can be a constant string, and other
parts of it can be the result of substitutions. Even the command name can be affected by substitutions.
A single round of substitutions is performed before command invocation. The result of a substitution is not interpreted a second
time. This rule is important if you have a variable value or a command result that contains special characters such as spaces,
dollar signs, square brackets, or braces. Because only a single round of substitution is done, you do not have to worry about
special characters in values causing extra substitutions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Fine Points
A common error is to forget a space between arguments when grouping with braces or quotes. This is because white space is
used as the separator, while the braces or quotes only provide grouping. If you forget the space, you will get syntax errors about
unexpected characters after the closing brace or quote. The following is an error because of the missing space between } and {:
if {$x > 1}{puts "x = $x"}
A double quote is only used for grouping when it comes after white space. This means you can include a double quote in the
middle of a group without quoting it with a backslash. This requires that curly braces or white space delimit the group. I do not
recommend using this obscure feature, but this is what it looks like:
set silly a"b
When double quotes are used for grouping, the special effect of curly braces is turned off. Substitutions occur everywhere inside a
group formed with double quotes. In the next command, the variables are still substituted:
set x xvalue
set y "foo {$x} bar"
=> foo {xvalue} bar
When double quotes are used for grouping and a nested command is encountered, the nested command can use double quotes
for grouping, too.
puts "results [format "%f %f" $x $y]"
Spaces are not required around the square brackets used for command substitution. For the purposes of grouping, the interpreter
considers everything between the square brackets as part of the current group. The following sets x to the concatenation of two
command results because there is no space between ] and [.
set x [cmd1][cmd2]
Newlines and semicolons are ignored when grouping with braces or double quotes. They get included in the group of characters
just like all the others. The following sets x to a string that contains newlines:
set x "This is line one.
This is line two.
This is line three."
During command substitution, newlines and semicolons are significant as command terminators. If you have a long command that
is nested in square brackets, put a backslash before the newline if you want to continue the command on another line. This was
illustrated in Example 1-9 on page 8.
A dollar sign followed by something other than a letter, digit, underscore, or left parenthesis is treated as a literal dollar sign. The
following sets x to the single character $.
set x $
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Reference
Backslash Sequences
Table 1-1. Backslash sequences
\a
Bell. (0x7)
\b
Backspace. (0x8)
\f
Form feed. (0xc)
\n
Newline. (0xa)
\r
Carriage return. (0xd)
\t
Tab. (0x9)
\v
Vertical tab. (0xb)
\<newline>
Replace the newline and the leading white space on the next line with a space.
\\
Backslash. ('\')
\ooo
Octal specification of character code. 1, 2, or 3 octal digits (0-7).
\xhh
Hexadecimal specification of character code. 1 or 2 hex digits. Be careful when using this in a string of characters, because
all hexadecimal characters following the \x will be consumed, but only the last 2 will specify the value.
\uhhhh
Hexadecimal specification of a 16-bit Unicode character value. 4 hex digits.
\c
Replaced with literal c if c is not one of the cases listed above. In particular, \$, \", \{, \}, \], and \[ are used to obtain these
characters.
Arithmetic Operators
Table 1-2. Arithmetic operators from highest to lowest precedence
-~!
Unary minus, bitwise NOT, logical NOT.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
*/%
Multiply, divide, remainder.
+-
Add, subtract.
<< >>
Left shift, right shift.
< > <= >=
Comparison: less, greater, less or equal, greater or equal.
== != eq ne
Equal, not equal, string equal (Tcl 8.4), string not equal (Tcl 8.4).
&
Bitwise AND.
^
Bitwise XOR.
|
Bitwise OR.
&&
Logical AND.
||
Logical OR.
x?y:z
If x then y else z.
Built-in Math Functions
Table 1-3. Built-in math functions
acos(x)
Arccosine of x.
asin(x)
Arcsine of x.
atan(x)
Arctangent of x.
atan2(y,x)
Rectangular (x,y) to polar (r,th). atan2 gives th.
ceil(x)
Least integral value greater than or equal tox.
cos(x)
Cosine of x.
cosh(x)
Hyperbolic cosine of x.
exp(x)
x
Exponential, e .
floor(x)
Greatest integral value less than or equal tox.
fmod(x,y)
Floating point remainder of x/y.
hypot(x,y)
Returns sqrt(x*x + y*y). r part of polar coordinates.
log(x)
Natural log of x.
log10(x)
Log base 10 of x.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
pow(x,y)
x to the y power, xy.
sin(x)
Sine of x.
sinh(x)
Hyperbolic sine of x.
sqrt(x)
Square root of x.
tan(x)
Tangent of x.
tanh(x)
Hyperbolic tangent of x.
abs(x)
Absolute value of x.
double(x)
Promote x to floating point.
int(x)
Truncate x to an integer.
round(x)
Round x to an integer.
rand()
Return a random floating point value between 0.0 and 1.0.
srand(x)
Set the seed for the random number generator to the integerx.
wide(x)
Promote x to a wide (64-bit) integer. (Tcl 8.4)
Core Tcl Commands
The pages listed in Table 1-4 give the primary references for the command.
Table 1-4. Built-in Tcl commands
Command
after
Pg.
228
Description
Schedule a Tcl command for later execution.
append
56
Append arguments to a variable's value. No spaces added.
array
97
Query array state and search through elements.
binary
59
Convert between strings and binary data.
break
83
Exit loop prematurely.
catch
83
Trap errors.
cd
122
Change working directory.
clock
183
Get the time and format date strings.
close
121
Close an open I/O stream.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Command
Pg.
Description
concat
65
Concatenate arguments with spaces between. Splices lists.
console
29
Control the console used to enter commands interactively.
continue
83
Continue with next loop iteration.
error
85
Raise an error.
eof
116
Check for end of file.
eval
130
Concatenate arguments and evaluate them as a command.
exec
105
Fork and execute a UNIX program.
exit
124
Terminate the process.
expr
6
Evaluate a math expression.
fblocked
233
Poll an I/O channel to see if data is ready.
fconfigure
231
Set and query I/O channel properties.
fcopy
250
Copy from one I/O channel to another.
file
108
Query the file system.
fileevent
229
Register callback for event-driven I/O.
flush
116
Flush output from an I/O stream's internal buffers.
for
82
Loop construct similar to C for statement.
foreach
79
Loop construct over a list, or lists, of values.
format
56
Format a string similar to Csprintf.
gets
119
Read a line of input from an I/O stream.
glob
122
Expand a pattern to matching file names.
global
90
history
196
Declare global variables.
Use command-line history.
if
76
Test a condition. Allowselse and elseif clauses.
incr
12
Increment a variable by an integer amount.
info
186
Query the state of the Tcl interpreter.
interp
292
Create additional Tcl interpreters.
join
72
Concatenate list elements with a given separator string.
lappend
66
Add elements to the end of a list.
lindex
68
Fetch an element of a list.
linsert
68
Insert elements into a list.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Command
Pg.
Description
list
65
Create a list out of the arguments.
llength
68
Return the number of elements in a list.
load
697
Load shared libraries that define Tcl commands.
lrange
68
Return a range of list elements.
lreplace
68
Replace elements of a list.
lsearch
69
Search for an element of a list that matches a pattern.
lset
62
Set an element in a list. (Tcl 8.4)
lsort
70
Sort a list.
namespace
213
Create and manipulate namespaces.
open
116
Open a file or process pipeline for I/O.
package
175
Provide or require code packages.
pid
124
Return the process ID.
proc
87
Define a Tcl procedure.
puts
119
Output a string to an I/O stream.
pwd
122
Return the current working directory.
read
120
Read blocks of characters from an I/O stream.
regexp
158
Match regular expressions.
regsub
162
Substitute based on regular expressions.
rename
88
Change the name of a Tcl command.
return
86
Return a value from a procedure.
scan
58
Parse a string according to a format specification.
seek
121
set
5
Set the seek offset of an I/O stream.
Assign a value to a variable.
socket
239
Open a TCP/IP network connection.
source
26
Evaluate the Tcl commands in a file.
split
71
Chop a string up into list elements.
string
49
Operate on strings.
subst
140
switch
77
tell
121
Substitute embedded commands and variable references.
Test several conditions.
Return the current seek offset of an I/O stream.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Command
Pg.
Description
time
202
Measure the execution time of a command.
trace
193
Monitor variable assignments.
unknown
178
Handle unknown commands.
unset
uplevel
upvar
13
Delete variables.
138
Execute a command in a different scope.
91
Reference a variable in a different scope.
variable
207
Declare namespace variables.
vwait
230
Wait for a variable to be modified.
while
79
[ Team LiB ]
Loop until a boolean expression is false.
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 2. Getting Started
This chapter explains how to run Tcl and Tk on different operating system platforms: UNIX, Windows, and Macintosh. Tcl commands
discussed are: source, console and info.
This chapter explains how to run Tcl scripts on different computer systems. While you can write Tcl scripts that are portable among UNIX,
Windows, and Macintosh, the details about getting started are different for each system. If you are looking for a current version of Tcl/Tk, use
the CD-ROM or check the URLs listed in the Preface on page liv.
The main Tcl/Tk program is wish. Wish stands for windowing shell, and with it you can create graphical applications that run on all these
platforms. The name of the program is a little different on each of the UNIX, Windows, and Macintosh systems. On UNIX it is just wish. On
Windows you will find wish.exe, and on the Macintosh the application name isWish. A version number may also be part of the name, such as
wish8.0, wish83.exe, or Wish 8.4. The differences among versions are introduced on pagelii, and described in more detail inPart VII of the
book. This book will use wish to refer to all of these possibilities.
Tk adds Tcl commands that are used to create graphical user interfaces, and Tk is described in Part III. You can run Tcl without Tk if you do
not need a graphical interface, such as with the CGI script discussed in Chapter 3. In this case the program is tclsh, tclsh.exe or Tclsh.
When you run wish, it displays an empty window and prompts for a Tcl command with a % prompt. You can enter Tcl commands interactively
and experiment with the examples in this book. On Windows and Macintosh, a console window is used to prompt for Tcl commands. On
UNIX, your terminal window is used. As described later, you can also set up standalone Tcl/Tk scripts that are self-contained applications.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The source Command
It is a good idea to try out the examples in this book as you read along. The highlighted examples from the book are on the CD-ROM in the
exsource folder. You can edit these scripts in your favorite editor. Save your examples to a file and then execute them with the Tcl
source
command:
source filename
The source command reads Tcl commands from a file and evaluates them just as if you had typed them interactively.
Chapter 3 develops a sample application. To get started, just open an editor on a file namedcgi1.tcl. Each time you update this file you can
save it, reload it into Tcl with the source command, and test it again. Development goes quickly because you do not wait for things to compile!
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
UNIX Tcl Scripts
On UNIX you can create a standalone Tcl or Tcl/Tk script much like an sh or csh script. The trick is in the first line of the file that contains your
script. If the first line of a file begins with #!pathname, then UNIX uses pathname as the interpreter for the rest of the script. The "Hello, World!"
program from Chapter 1 is repeated in Example 2-1 with the special starting line:
Example 2-1 A standalone Tcl script on UNIX
#!/usr/local/bin/tclsh
puts stdout {Hello, World!}
The Tk hello world program from Chapter 23 is shown in Example 2-2:
Example 2-2 A standalone Tk script on UNIX
#!/usr/local/bin/wish
button .hello -text Hello -command {puts "Hello, World!"}
pack .hello -padx 10 -pady 10
The actual pathnames for tclsh and wish may be different on your system. If you type the pathname for the interpreter wrong, you receive a
confusing "command not found" error. You can find out the complete pathname of the Tcl interpreter with the info nameofexecutable
command. This is what appears on my system:
info nameofexecutable
=> /home/welch/install/linux-ix86/bin/tclsh8.4
Watch out for long pathnames.
On most UNIX systems, this special first line is limited to 32 characters, including the #!. If the pathname is too long, you may end up with
/bin/sh trying to interpret your script, giving you syntax errors. You might try using a symbolic link from a short name to the true, long name of
the interpreter. However, watch out for systems like older versions of Solaris in which the script interpreter cannot be a symbolic link.
Fortunately, Solaris doesn't impose a 32-character limit on the pathname, so you can just use a long pathname.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The next example shows a trick that works around the pathname length limitation in all cases. The trick comes from a posting to comp.lang.tcl
by Kevin Kenny. It takes advantage of a difference between comments in Tcl and the Bourne shell. Tcl comments are described on page 16.
In the example, the exec Bourne shell command that runs the Tcl interpreter is hidden in a comment as far as Tcl is concerned, but it is visible
to /bin/sh. The exec command (in /bin/sh) replaces the current program, so that is all that the Bourne shell processes; Tcl interprets the rest of
the script.
Example 2-3 Using /bin/sh to run a Tcl script
#!/bin/sh
# The backslash makes the next line a comment in Tcl \
exec /some/very/long/path/to/wish "$0" ${1+"$@"}
# ... Tcl script goes here ...
You do not even have to know the complete pathname oftclsh or wish to use this trick. You can just do the following:
#!/bin/sh
# Run wish from the users PATH \
exec wish -f "$0" ${1+"$@"}
The drawback of an incomplete pathname is that many sites have different versions of wish and tclsh that correspond to different versions of
Tcl and Tk. In addition, some users may not have these programs in their PATH.
You can hide more than one Bourne shell command in a script with this trick. For example, you might need to set environment variables:
#!/bin/sh
#\
export LD_LIBRARY_PATH=/usr/local/lib
#\
exec /usr/local/bin/tclsh "$0" ${1+"$@"}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Windows Start Menu
You can add your Tcl/Tk programs to the Windows start menu. The command is the complete name of the wish.exe program and the name
of the script. The trick is that the name of wish.exe has a space in it in the default configuration, so you must use quotes. Your start command
will look something like this:
"c:\Program Files\Tcl84\wish84.exe" "c:\My Files\script.tcl"
This starts c:\My Files\script.tcl as a standalone Tcl/Tk program.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
Macintosh OS 8/9 and ResEdit
If you want to create a self-contained Tcl/Tk application on Macintosh OS 8 or 9, you must copy the Wish program and add a Macintosh
resource named tclshrc that has the start-up Tcl code. The Tcl code can be a singlesource command that reads your script file. Here are
step-by-step instructions to create the resource using ResEdit:
First, make a copy of Wish and open the copy in ResEdit.
Pull down the Resource menu and select Create New Resource operation to make a newTEXT resource.
ResEdit opens a window and you can type in text. Type in asource command that names your script:
source "Hard Disk:Tcl/Tk 8.3:Applications:MyScript.tcl"
Set the name of the resource to be tclshrc. You do this through theGet Resource Info dialog under the Resources menu in ResEdit.
This sequence of commands is captured in an application called Drag n Drop Tclets, which comes with the Macintosh Tcl distribution. If you
drag a Tcl script onto this icon, it will create a copy of Wish and create the tclshrc text resource that has asource command that will load that
script.
If you have a Macintosh development environment, you can build a version of Wish that has additional resources built right in. You add the
resources to the applicationInit.r file. If a resource contains Tcl code, you use it like this:
source -rcrc resource
If you don't want to edit resources, you can just use theWish Source menu to select a script to run.
Macintosh OS X
Mac OS X can run the same Tcl/Tk as Macintosh system 8 or 9. However, the preferred version for Mac OS X is Tcl/Tk Aqua, which uses the
native windowing system known as Aqua. There are some differences in the application structure due to the new application framework used
when building this variant. Wish checks the Resources/Scripts directory in its application bundle for a file calledAppMain.tcl, if found it is used
as the startup script and the Scripts folder is added to the auto_path. This is similar in spirit to the tclshrc resource described above. Daniel
Steffen deserves a great deal of credit for the Tcl/Tk Aqua port and his continued support of the Macintosh platform. He has put together a
great distribution that includes many popular extensions, which you can find on the CD-ROM. You can find out more about Tcl/Tk on
Macintosh through these URLs:
http://wiki.tcl.tk/macos/
http://www.maths.mq.edu.au/~steffen/tcltk/
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The console Command
The Windows and Macintosh platforms have a built-in console that is used to enter Tcl commands interactively. You can control this console
with the console command. The console is visible by default. Hide the console like this:
console hide
Display the console like this:
console show
The console is implemented by a second Tcl interpreter. You can evaluate Tcl commands in that interpreter with:
console eval command
There is an alternate version of this console called TkCon. It is included on the CD-ROM, and you can find current versions on the Internet.
TkCon was created by Jeff Hobbs and has lots of nice features. You can useTkCon on Unix systems, too. Some of its features were added to
console in 8.4.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Command-Line Arguments
If you run a script from the command line, for example from a UNIX shell, you can pass the script command-line arguments. You can also
specify these arguments in the shortcut command in Windows. For example, under UNIX you can type this at a shell:
% myscript.tcl arg1 arg2 arg3
In Windows, you can have a shortcut that runs wish on your script and also passes additional arguments:
"c:\Program Files\Tcl84\wish.exe" c:\your\script.tcl arg1
The Tcl shells pass the command-line arguments to the script as the value of the argv variable. The number of command-line arguments is
given by the argc variable. The name of the program, or script, is not part ofargv nor is it counted byargc. Instead, it is put into theargv0
variable. Table 2-2 lists all the predefined variables in the Tcl shells. argv is a list, so you can use thelindex command, which is described on
page 63, to extract items from it:
set arg1 [lindex $argv 0]
The following script prints its arguments (foreach is described on page 79):
Example 2-4 The EchoArgs script
# Tcl script to echo command line arguments
puts "Program: $argv0"
puts "Number of arguments: $argc"
set i 0
foreach arg $argv {
puts "Arg $i: $arg"
incr i
}
Command-Line Options to Wish
Some command-line options are interpreted by wish, and they do not appear in theargv variable. The general form of the wish command line
is:
wish ?options? ?script? ?arg1 arg2?
If no script is specified, then wish just enters an interactive command loop.Table 2-1 lists the options that wish supports:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 2-1. Wish command line options
-colormap new
Use a new private colormap. See page 624.
-display display
Use the specified Xdisplay. UNIX only.
-geometry geometry
The size and position of the window. See page 658.
-name name
Specify the Tk application name. See page 648.
-sync
Run X synchronously. UNIX only.
-use id
Use the window specified byid for the main window. See page 667.
-visual visual
Specify the visual for the main window. See page 624.
--
Terminate options to wish.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
[ Team LiB ]
Predefined Variables
Table 2-2. Variables defined by tclsh and wish
argc
The number of command-line arguments.
argv
A list of the command-line arguments.
argv0
The name of the script being executed. If being used interactively,argv0 is the name of the shell program.
embed_args
The list of arguments in the <EMBED> tag. Tcl applets only. See page 314.
env
An array of the environment variables. See page 124.
tcl_interactive
True (one) if the tclsh is prompting for commands.
tcl_library
The script library directory.
tcl_patchLevel
Modified version number, e.g., 8.0b1.
tcl_platform
Array containing operating system information. See page 192.
tcl_prompt1
If defined, this is a command that outputs the prompt.
tcl_prompt2
If defined, this is a command that outputs the prompt if the current command is not yet complete.
tcl_version
Version number.
auto_path
The search path for script library directories. See page 172.
auto_index
A map from command name to a Tcl command that defines it.
auto_noload
If set, the library facility is disabled.
auto_noexec
If set, the auto execute facility is disabled.
geometry
(wish only). The value of the -geometry argument.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 3. The Guestbook CGI Application
This chapter presents a simple Tcl program that computes a Web page. The chapter provides a brief background to HTML and the CGI
interface to Web servers. The chapter uses the ncgi package from the standard Tcl library.
This chapter presents a complete, but simple, guestbook program that computes an HTML document, or Web page, based on the contents of
a simple database. The basic idea is that a user with a Web browser visits a page that is computed by the program. The details of how the
page gets from your program to the user with the Web browser vary from system to system. The Tcl Web Server described in Chapter 18
comes with this guestbook example already set up. You can also use these scripts on your own Web server, but you will need help from your
Webmaster to set things up.
The chapter provides a very brief introduction to HTML and CGI programming. HTML is a way to specify text formatting, including hypertext
links to other pages on the World Wide Web. CGI is a standard for communication between a Web server that delivers documents and a
program that computes documents for the server. There are many books on these subjects alone.
A guestbook is a place for visitors to sign their name and perhaps provide other information. We will build a guestbook that takes advantage
of the World Wide Web. Our guests can leave their address as a Universal Resource Location (URL). The guestbook will be presented as a
page that has hypertext links to all these URLs so that other guests can visit them. The program works by keeping a simple database of the
guests, and it generates the guestbook page from the database.
The Tcl scripts described in this chapter use commands and techniques that are described in more detail in later chapters. The goal of the
examples is to demonstrate the power of Tcl without explaining every detail. If the examples in this chapter raise questions, you can follow
the references to examples in other chapters that do go into more depth.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
A Quick Introduction to HTML
Web pages are written in a text markup language called HTML (HyperText Markup Language). The idea of HTML is that you annotate, or
mark up, regular text with special tags that indicate structure and formatting. For example, the title of a Web page is defined like this:
<TITLE>My Home Page</TITLE>
The tags provide general formatting guidelines, but the browsers that display HTML pages have freedom in how they display things. This
keeps the markup simple. The general syntax for HTML tags is:
<tag parameters>normal text</tag>
As shown here, the tags usually come in pairs. The open tag may have some parameters, and the close tag name begins with a slash. The
case of a tag is not considered, so <title>, <Title>, and <TITLE> are all valid and mean the same thing. The corresponding close tag could be
</title>, </Title>, </TITLE>, or even </TiTlE>.
The <A> tag defines hypertext links that reference other pages on the Web. The hypertext links connect pages into a Web so that you can
move from page to page to page and find related information. It is the flexibility of the links that makes the Web so interesting. The <A> tag
takes an HREF parameter that defines the destination of the link. If you wanted to link to my home page, you would put this in your page:
<A HREF="http://www.beedub.com/">Brent Welch</A>
When this construct appears in a Web page, your browser typically displays "Brent Welch" in blue underlined text. When you click on that text,
your browser switches to the page at the address "http://www.beedub.com/". There is a lot more to HTML, of course, but this should give you
a basic idea of what is going on in the examples. Table 3-1 summarizes the HTML tags that will be used in the examples:
Table 3-1. HTML tags used in the examples
HTML
Main tag that surrounds the whole document.
HEAD
Delimits head section of the HTML document.
TITLE
Defines the title of the page.
BODY
Delimits the body section. Lets you specify page colors.
H1 - H6
HTML defines 6 heading levels: H1, H2, H3, H4, H5, H6.
P
Start a new paragraph.
BR
One blank line.
B
Bold text.
I
Italic text.
A
Used for hypertext links.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
IMG
Specify an image.
DL
Definition list.
DT
Term clause in a definition list.
DD
Definition clause in a definition list.
UL
An unordered list.
LI
A bulleted item within a list.
TABLE
Create a table.
TR
A table row.
TD
A cell within a table row.
FORM
Defines a data entry form.
INPUT
A one-line entry field, checkbox, radio button, or submit button.
TEXTAREA
A multiline text field.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
CGI for Dynamic Pages
There are two classes of pages on the Web: static and dynamic. A static page is written and stored on a Web server, and the same thing is
returned each time a user views the page. This is the easy way to think about Web pages. You have some information to share, so you
compose a page and tinker with the HTML tags to get the information to look good. If you have a home page, it is probably in this class.
In contrast, a dynamic page is computed each time it is viewed. This is how pages that give up-to-the-minute stock prices work, for example.
A dynamic page does not mean it includes animations; it just means that a program computes the page contents when a user visits the page.
The advantage of this approach is that a user might see something different each time he or she visits the page. As we shall see, it is also
easier to maintain information in a database of some sort and generate the HTML formatting for the data with a program.
A CGI (Common Gateway Interface) program is used to compute Web pages. The CGI standard defines how inputs are passed to the
program as well as a way to identify different types of results, such as images, plain text, or HTML markup. A CGI program simply writes the
contents of the document to its standard output, and the Web server takes care of delivering the document to the user's Web browser.
Example 3-1 is a very simple CGI script:
Example 3-1 A simple CGI script
puts "Content-Type: text/html"
puts ""
puts "<TITLE>The Current Time</TITLE>"
puts "The time is <B>[clock format [clock seconds]]</B>"
The program computes a simple HTML page that has the current time. Each time a user visits the page, she will see the current time on the
server. The server that has the CGI program and the user viewing the page might be on different sides of the planet. The output of the
program is divided into two sections: the protocol header and the page contents. In this simple example, the protocol header just has a
Content-Type line that tells your Web browser what kind of data comes next. A blank line separates the protocol header from the page, which
starts with a <TITLE> tag, in this case.
The clock command is used twice: once to get the current time in seconds, and a second time to format the time into a nice-looking string.
The
clock command is described in detail on page 183. Fortunately, there is no conflict between the markup syntax used by HTML and the Tcl
syntax for embedded commands, so we can mix the two in the argument to the puts command. Double quotes are used to group the
argument to puts so that the clock command will be executed. Example 3-2 shows what the output of the program will look like:
Example 3-2 Output of Example 3-1
Content-Type: text/html
<TITLE>The Current Time</TITLE>
The time is <B>Wed Jul 10 14:29:36 2002</B>
This example is a bit sloppy in its use of HTML, but it should display properly in most Web browsers. Example 3-3 includes all the required
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
tags for a proper HTML document.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The guestbook.cgi Script
The guestbook.cgi script computes a page that lists all the registered guests.Example 3-3 is shown first, and then each part of it is discussed
in more detail later. One thing to note right away is that the HTML tags are generated by procedures that hide the details of the HTML syntax.
The first lines of the script use the UNIX trick to have tclsh interpret the script. This is described on page 26:
Example 3-3 The guestbook.cgi script, version 1
#!/bin/sh
# guestbook.cgi
# Implement a simple guestbook page.
# The set of visitors is kept in a simple database.
# The newguest.cgi script will update the database.
#\
exec tclsh "$0" ${1+"$@"}
# The guestbook.data file has the database
# The datafile is in the same directory as the script
set dir [file dirname [info script]]
set datafile [file join $dir guestbook.data]
puts "text/html"
puts ""
set title "Brent's Guestbook"
puts "<HTML><HEAD><TITLE>$title</TITLE></HEAD>"
puts "<BODY BGCOLOR=white TEXT=black>"
puts "<H1>$title</H1>"
if {![file exists $datafile]} {
puts "No registered guests, yet.
<P>
Be the first
<A href='newguest.html'>registered guest!</A>"
} else {
puts "The following folks have registered in my GuestBook.
<P>
<A href='newguest.html'>Register</A>
<H2>Guests</H2>"
catch {source $datafile}
foreach name [lsort [array names Guestbook]] {
set item $Guestbook($name)
set homepage [lindex $item 0]
set markup [lindex $item 1]
puts "<H3><A href=$homepage>$name</A></H3>"
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
puts $markup
}
}
puts "</BODY></HTML>"
Using a Script Library File
If you write one CGI script, you are likely to write several. You could start making copies and modifying your first script, but that quickly
becomes hard to maintain. If you learn something new after writing your third script, will you remember to update the first two scripts you
wrote? Probably not. The best way to approach this problem is to create a collection of Tcl procedures in a file that you share among all your
CGI scripts.
The Standard Tcl Library, tcllib, provides several packages of procedures that you can use. Later in this chapter, we will look at thencgi
package that helps handle form data. Before we do that, let's start a simple collection of our own procedures and learn how to share them
among several different CGI scripts. Suppose you have a file cgihacks.tcl that contains your Tcl procedures. The source command loads that
file into your script. The naive approach shown here probably won't work:
source cgihacks.tcl
Loading a file from the same directory as your script
The problem is that the current directory of the CGI process may not be the same as the directory that contains the CGI script or the
cgihacks.tcl file. You can use the info script command to find out where the CGI script is, and from that load the supporting file. Thefile dirname
and file join commands manipulate file names in a platform-independent way. They are described on page 108. I use the following trick to
avoid putting absolute file names into my scripts, which would have to be changed if the program moves later:
set dir [file dirname [info script]]
source [file join $dir cgihacks.tcl]
You can also create script libraries as described in Chapter 12. That chapter describes tools to create an index of procedures so an
application can quickly load the procedures it needs, and how to create packages of procedures so you can keep your code organized.
However you set them up, it is always a good idea to have a library of procedures you share with other applications.
Beginning the HTML Page
The way you start your HTML page is a great candidate for capturing in a Tcl procedure. For example, I like to have the page title appear in
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
the TITLE tag in the head, and repeated in an H1 tag at the beginning of the body. You may also have a favorite set of colors or fonts that you
want to specify in the BODY tag. By putting all this into a Tcl procedure, you can make it easy to share this among all your scripts. If your
tastes change tomorrow, then you can change the Tcl procedure in one spot and affect all CGI scripts that share the procedure. Example 3-4
shows Cgi_Header that generates a simple standard page header:
Example 3-4 The Cgi_Header procedure
proc Cgi_Header {title {body {bgcolor=white text=black}}} {
puts stdout "Content-Type: text/html
<HTML>
<HEAD>
<TITLE>$title</TITLE>
</HEAD>
<BODY $body>
<H1>$title</H1>"
}
The Cgi_Header procedure takes as arguments the title for the page and some optional parameters for the HTML
BODY tag. The procedure
definition uses the syntax for an optional parameter, so you do not have to pass bodyparams to Cgi_Header. The default specifies black text
on a white background to avoid the standard gray background of most browsers. Default values for procedure parameters are described on
page 87.
Example 3-5 The guestbook.cgi script, version 2
#!/bin/sh
# guestbook.cgi
# Implement a simple guestbook page.
# The set of visitors is kept in a simple database.
# The newguest.cgi script will update the database.
#\
exec tclsh "$0" ${1+"$@"}
# The guestbook.data file has the database
# The datafile is in the same directory as the script
set dir [file dirname [info script]]
set datafile [file join $dir guestbook.data]
# Load our supporting Tcl procedures to define Cgi_Header
source [file join $dir cgihacks.tcl]
Cgi_Header "Brent's Guestbook"
if {![file exists $datafile]} {
puts "No registered guests, yet.
<P>
Be the first
<A href='newguest.html'>registered guest!</A>"
} else {
puts "The following folks have registered in my GuestBook.
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
<P>
<A href='newguest.html'>Register</A>
<h2>Guests</h2>"
catch {source $datafile}
foreach name [lsort [array names Guestbook]] {
set item $Guestbook($name)
set homepage [lindex $item 0]
set markup [lindex $item 1]
puts "<H3><A href=$homepage>$name</A></H3>"
puts $markup
}
}
puts "</BODY></HTML>"
Example 3-5 is a new version of the original CGI script that loads thecgihacks.tcl file and uses Cgi_Header. The Cgi_Header procedure just
contains a single puts command that generates the standard boilerplate that appears at the beginning of the output. Note that several lines
are grouped together with double quotes. Double quotes are used so that the variable references mixed into the HTML are substituted
properly. The output of the Cgi_Header procedure matches what we wrote by hand inExample 3-3.
Sample Output of the CGI Script
The program tests to see whether there are any registered guests or not. The file command, which is described in detail on page 108, is used
to see whether there is any data. The exclamation point means "not" in a boolean expression:
if {![file exists $datafile]} {
If the database file does not exist, a different page is displayed to encourage a registration. The page includes a hypertext link to a registration
page, newguest.html, which is described on page 43. The output of the program would be as below inExample 3-6 if there were no data file:
Example 3-6 Initial output of guestbook.cgi with no data
Content-Type: text/html
<HTML>
<HEAD>
<TITLE>Brent's Guestbook</TITLE>
</HEAD>
<BODY BGCOLOR=white TEXT=black>
<H1>Brent's Guestbook</H1>
<P>
No registered guests.
<P>
Be the first
<A HREF="newguest.html">registered guest!</A>
</BODY></HTML>
Note the inconsistent indentation of the HTML that comes from the indentation in the puts command used for that part of the page. The
browser doesn't care about white space in the HTML. You have a choice between lining up the Tcl commands in your CGI script, or lining up
the HTML output. Here we have two different examples. The Cgi_Header procedure produces output that is lined up, but the procedure
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
definition looks a bit odd. The main script, in contrast, keeps its Tcl commands neatly indented, but that shows up in the output. If you
generate most of your HTML from code, you may choose to keep your code tidy.
Example 3-7 shows the output of the guestbook.cgi script when there is some data in the data file:
Example 3-7 Output of guestbook.cgi with guestbook data
Content-Type: text/html
<HTML>
<HEAD>
<TITLE>Brent's Guestbook</TITLE>
</HEAD>
<BODY BGCOLOR=white TEXT=black>
<H1>Brent's Guestbook</H1>
<P>
The following folks have registered in my guestbook.
<P>
<A HREF='newguest.html'>Register</A>
<H2>Guests</H2>
<H3><A HREF="http://www.beedub.com/">Brent Welch</A></H3>
<IMG SRC="http://www.beedub.com/welch.gif">
</BODY></HTML>
Using a Tcl Array for the Database
The data file contains Tcl commands that define an array that holds the guestbook data. If this file is kept in the same directory as the
guestbook.cgi script, then you can compute its name:
set dir [file dirname [info script]]
set datafile [file join $dir guestbook.data]
By using Tcl commands to represent the data, we can load the data with the source command. The catch command is used to protect the
script from a bad data file, which will show up as an error from the source command. Catching errors is described in detail on page 85:
catch {source $datafile}
The Guestbook variable is the array defined inguestbook.data. Array variables are the topic ofChapter 8. Each element of the array is defined
with a Tcl command that looks like this:
set Guestbook(key) {url markup}
The person's name is the array index, or key. The value of the array element is a Tcl list with two elements: their URL and some additional
HTML markup that they can include in the guestbook. Tcl lists are the topic of Chapter 5. The following example shows what the command
looks like with real data:
set {Guestbook(Brent Welch)} {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
http://www.beedub.com/
{<img src=http://www.beedub.com/welch.gif>}
}
The spaces in the name result in additional braces to group the whole variable name and each list element. This syntax is explained on page
96. Do not worry about it now. We will see on page 46 that all the braces in the previous statement are generated automatically. The main
point is that the person's name is the key, and the value is a list with two elements.
The array names command returns all the indices, or keys, in the array, and thelsort command sorts these alphabetically. The foreach
command loops over the sorted list, setting the loop variable x to each key in turn:
foreach name [lsort [array names Guestbook]] {
The lsort command will sort the names based on the person's first name. You can havelsort sort things in a variety of ways. One trick we can
use here is to have lsort treat each key as a list and sort on the last item in the list (i.e., the last name):
foreach name [lsort -index end [array names Guestbook]] {
The lsort command is described in more detail on page 70. Theforeach command assigns name to each key of the Guestbook array. We get
the value like this:
set item $Guestbook($name)
The two list elements are extracted withlindex, which is described on page 68.
set homepage [lindex $item 0]
set markup [lindex $item 1]
We generate the HTML for the guestbook entry as a level-three header that contains a hypertext link to the guest's home page. We follow the
link with any HTML markup text that the guest has supplied to embellish his or her entry:
puts "<H3><a href=$homepage>$name</a></H3>"
puts $markup
The homepage and markup variables are not strictly necessary, and the code could be written more compactly without them. However, the
variables make the code more understandable. Here is what it looks like without the temporary variables:
puts "<H3><a href=[lindex $item 0]>$name</a></H3>"
puts [lindex $item 1]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Defining Forms and Processing Form Data
The guestbook.cgi script only generates output. The other half of CGI deals with input from the user. Input is more complex for two reasons.
First, we have to define another HTML page that has a form for the user to fill out. Second, the data from the form is organized and encoded
in a standard form that must be decoded by the script. Example 3-8 on page 43 defines a very simple form, and the procedure that decodes
the form data is shown in Example 11-6 on page 165.
The guestbook page contains a link to newguest.html. This page contains a form that lets a user register his or her name, home page URL,
and some additional HTML markup. The form has a submit button. When a user clicks that button in her browser, the information from the
form is passed to the newguest.cgi script. This script updates the database and computes another page for the user that acknowledges the
user's contribution.
The newguest.html Form
An HTML form contains tags that define data entry fields, buttons, checkboxes, and other elements that let the user specify values. For
example, a one-line entry field that is used to enter the home page URL is defined like this:
<INPUT TYPE=text NAME=url>
The INPUT tag is used to define several kinds of input elements, and itstype parameter indicates what kind. In this case,TYPE=text creates a
one-line text entry field. The submit button is defined with an INPUT tag that has TYPE=submit, and the VALUE parameter becomes the text
that appears on the submit button:
<INPUT TYPE=submit NAME=submit VALUE=Register>
A general type-in window is defined with the TEXTAREA tag. This creates a multiline, scrolling text field that is useful for specifying lots of
information, such as a free-form comment. In our case, we will let guests type in HTML that will appear with their guestbook entry. The text
between the open and close TEXT-AREA tags is inserted into the type-in window when the page is first displayed.
<TEXTAREA NAME=markup ROWS=10 COLS=50>Hello.</TEXTAREA>
A common parameter to the form tags is NAME=something. This name identifies the data that will come back from the form. The tags also
have parameters that affect their display, such as the label on the submit button and the size of the text area. Those details are not important
for our example. The complete form is shown in Example 3-8:
Example 3-8 The newguest.html form
<HTML>
<HEAD>
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
<TITLE>Register in my Guestbook</TITLE>
</HEAD>
<BODY BGCOLOR=white TEXT=black>
<FORM ACTION="newguest.cgi" METHOD="POST">
<H1>Register in my Guestbook</H1>
<UL>
<LI>Name <INPUT TYPE="text" NAME="name" SIZE="40">
<LI>URL <INPUT TYPE="text" NAME="url" SIZE="40">
<P>
If you don't have a home page, you can use an email URL like "mailto:welch@acm.org"
<LI>Additional HTML to include after your link:
<BR>
<TEXTAREA NAME="html" COLS="60" ROWS="15">
</TEXTAREA>
<LI><INPUT TYPE="submit" NAME="new" VALUE="Add me to your guestbook">
<LI><INPUT TYPE="submit" NAME="update" VALUE="Update my guestbook entry">
</UL>
</FORM>
</BODY>
</HTML>
The ncgi and cgi.tcl Packages
The newguest.cgi script uses the ncgi package to process form data. This is one of many packages available in the Standard Tcl Library,
commonly known as "tcllib". If you don't have tcllib installed, you can find it on the CD-ROM, on SourceForge at www.sf.net/projects/tcllib, or
via the main www.tcl.tk Web site. If your Tcl installation includes tcllib, then you use thepackage command to load the package.
package require ncgi
The procedures in the ncgi package are in thencgi namespace. Tcl namespaces are described in detail inChapter 14. Procedures in a
namespace are qualified with the name of the namespace and :: syntax. For example, the standard setup procedure for a CGI script is
ncgi::parse.
The "n" in ncgi is for "new". Don Libes wrote the original package for CGI scripts known as cgi.tcl. There is also the cgilib.tcl package that
contains Cgi_Header and some other procedures described in earlier editions of this book. The ncgi and html packages of tcllib provide most
of the features in both cgi.tcl and cgilib.tcl, but follow the standard namespace conventions use by the packages in tcllib. You can still find
cgi.tcl
on the Web at
http://expect.nist.gov/cgi.tcl/
The newguest.cgi Script
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
When the user clicks the Submit button in her browser, the data from the form is passed to the program identified by the ACTION parameter
of the form tag. That program takes the data, does something useful with it, and then returns a new page for the browser to display. In our
case, the FORM tag names newguest.cgi as the program to handle the data:
<FORM ACTION=newguest.cgi METHOD=POST>
The CGI specification defines how the data from the form is passed to the program. The data is encoded and organized so that the program
can figure out the values the user specified for each form element. The encoding is handled rather nicely with some regular expression tricks
that are done in ncgi::parse. ncgi::parse saves the form data, and ncgi::value gets a form value in the script. These procedures are described
in Example 11-6 on page 165. Example 3-9 starts out by calling ncgi::parse:
Example 3-9 The newguest.cgi script
#!/bin/sh
#\
exec tclsh "$0" ${1+"$@"}
# Use the ncgi package from tcllib to process form data
package require ncgi
ncgi::parse
# Load our data file and supporting procedures
set dir [file dirname [info script]]
set datafile [file join $dir guestbook.data]
source [file join $dir cgihacks.tcl]
# Open the datafile in append mode
if {[catch {open $datafile a} out]} {
Cgi_Header "Guestbook Registration Error" \
{BGCOLOR=black TEXT=red}
puts "<P>Cannot open the data file<P>"
puts $out;# the error message
exit 0
}
# Append a Tcl set command that defines the guest's entry
puts $out ""
puts $out [list set Guestbook([ncgi::value name]) \
[list [ncgi::value url] [ncgi::value html]]]
close $out
# Return a page to the browser
Cgi_Header "Guestbook Registration Confirmed" \
{BGCOLOR=white TEXT=black}
puts "
<TABLE BORDER=1>
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
<TR><TD>Name</TD>
<TD>[ncgi::value name]</TD></TR>
<TR><TD>URL</TD>
<TD><A HREF='[ncgi::value url]'>[ncgi::value url]</A></TD></TR>
<TR><TD>Extra HTML</TD>
<TD>[ncgi::value html]</TD></TR>
</TABLE>
"
puts </BODY></HTML>
Using Tcl Scripts to Store Data
The main idea of the newguest.cgi script is that it saves the data to a file as a Tcl command that defines an element of the
Guestbook array.
This lets the guestbook.cgi script simply load the data by using the Tclsource command. This trick of storing data as a Tcl script saves us from
the chore of defining a new file format and writing code to parse it. Instead, we can rely on the well-tuned Tcl implementation to do the hard
work for us efficiently.
The script opens the datafile in append mode so that it can add a new record to the end. Opening files is described in detail on page 116.
The script uses a catch command to guard against errors. If an error occurs, a page explaining the error is returned to the user. Working with
files is one of the most common sources of errors (permission denied, disk full, file-not-found, and so on), so I always open the file inside a
catch statement:
if {[catch {open $datafile a} out]} {
# an error occurred
} else {
# open was ok
}
In this command, the variable out gets the result of the open command, which is either a file descriptor or an error message. This style of using
catch is described in detail in Example 6-14 on page 83.
Use list to generate Tcl commands.
The script writes the data as a Tcl set command. The list command is used to format the data properly:
puts $out [list set Guestbook([ncgi::value name]) \
[list [ncgi::value url] [ncgi::value html]]]
There are two lists. First, the url and html values are formatted into one list. This list will be the value of the array element. Then the whole Tcl
command is formed as a list. In simplified form, the command is generated from this:
list set variable value
Using the list command ensures that the result will always be a valid Tcl command that sets the variable to the given value. This is a very
important technique. If you want to generate Tcl commands, the best way to do it is to generate lists using list manipulation commands. The
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
list command is described in more detail on page 65.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Handling Errors in CGI Scripts
One of the more frustrating aspects of CGI programming is that errors in your script result in blank browser pages, and it may be difficult or
impossible to find any trace of the error message. The other main problem is that your Web server may not be configured properly to find your
CGI script. I use two simple tricks to track down the source of these errors. The first trick simply verifies that my script has run at all by
creating an empty file somewhere on the Web server. On a UNIX system, you can put this line at the beginning of your script:
close [open /tmp/my_cgi_script_ran w]
When you aim the browser at your CGI script, it should at least create the file. If not, then the Web server cannot find your script, or it cannot
find the Tclsh required by your script. Double-check your setup and the #! line in your script. On Windows, your best bet may be to use the
TclHttpd Web server, which has a built-in ability to run Tcl CGI scripts. TclHttpd has other even cooler ways to generate pages, too.
If your script suddenly stops working after you've modified it, then you have introduced a programming bug. I generally put all of the script into
a catch statement and print out any errors that occur. That way the errors will be displayed by the browser instead of filed into the void by your
Web server. Example 3-10 shows the newguest.cgi script rewritten so the catch statement surrounds all the statements. At the end, the value
of the errorInfo variable is printed out if an error has occurred:
Example 3-10 The newguest.cgi script with error handling
#!/bin/sh
#\
exec tclsh "$0" ${1+"$@"}
# Trap all errors
if {[catch {
# Use the ncgi package from tcllib to process form data
package require ncgi
ncgi::parse
# Load our data file and supporting procedures
set dir [file dirname [info script]]
set datafile [file join $dir guestbook.data]
source [file join $dir cgihacks.tcl]
# Open the datafile in append mode
set out [open $datafile a]
# Append a Tcl set command that defines the guest's entry
puts $out ""
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
puts $out [list set Guestbook([ncgi::value name]) \
[list [ncgi::value url] [ncgi::value html]]]
close $out
# Return a page to the browser
Cgi_Header "Guestbook Registration Confirmed" \
{BGCOLOR=white TEXT=black}
puts "
<TABLE BORDER=1>
<TR><TD>Name</TD>
<TD>[ncgi::value name]</TD></TR>
<TR><TD>URL</TD>
<TD><A HREF='[ncgi::value url]'>[ncgi::value url]</A></TD></TR>
<TR><TD>Extra HTML</TD>
<TD>[ncgi::value html]</TD></TR>
</TABLE>
</BODY></HTML>
"
# End of main script
} err]} {
# Error occurred - display in the Web page
puts "Content-Type: text/plain"
puts ""
puts "CGI error occurred in [info script]"
puts $errorInfo
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Next Steps
There are a number of details that can be added to this example. Users may want to update their entry, for example. They could do that now,
but they would have to retype everything. They might also like a chance to check the results of their registration and make changes before
committing them. This requires another page that displays their guest entry as it would appear on a page, and also has the fields that let them
update the data.
The details of how a CGI script is hooked up with a Web server vary from server to server. You should ask your local Webmaster for help if
you want to try this out on your local Web site. The Tcl Web Server comes with this guestbook example already set up, plus it has a number
of other very interesting ways to generate pages. My own taste in Web page generation has shifted from CGI to a template-based approach
supported by the Tcl Web Server. This is the topic of Chapter 18.
The next few chapters describe basic Tcl commands and data structures. We return to the CGI example inChapter 11 on regular expressions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 4. String Processing in Tcl
This chapter describes string manipulation and simple pattern matching. Tcl commands described are: string, append, format, scan, and
binary. The string command is a collection of several useful string manipulation operations.
Strings are the basic data item in Tcl, so it should not be surprising that there are a large number of commands to manipulate strings. A
closely related topic is pattern matching, in which string comparisons are made more powerful by matching a string against a pattern. This
chapter describes a simple pattern matching mechanism that is similar to that used in many other shell languages. Chapter 11 describes a
more complex and powerful regular expression pattern matching mechanism.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
[ Team LiB ]
The string Command
The string command is really a collection of operations you can perform on strings. The following example calculates the length of the value of
a variable.
set name "Brent Welch"
string length $name
=> 11
The first argument to string determines the operation. You can askstring for valid operations by giving it a bad one:
[View full width]
string junk
=> bad option "junk": should be bytelength, compare, equal, first, index, is, last, length
, map, match, range, repeat, replace, tolower, totitle, toupper, trim, trimleft, trimright,
wordend, or wordstart
This trick of feeding a Tcl command bad arguments to find out its usage is common across many commands. Table 4-1 summarizes the string
command.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 4-1. The string command
string bytelength str
Returns the number of bytes used to store a string, which may be different from the character length returned
by string length because of UTF-8 encoding. See page 220 of Chapter 15 about Unicode and UTF-8.
string compare ?-nocase?
Compares strings lexicographically. Use -nocase for case insensitive comparison. Use-length to limit the
?-length len? str1 str2
comparison to the first len characters. Returns 0 if equal, -1 ifstr1 sorts before str2, else 1.
string equal ?-nocase?
Compares strings and returns 1 if they are the same. Use-nocase for case insensitive comparison.
str1 str2
string first subString string
Returns the index in string of the first occurrence of subString, or -1 if string is not found. startIndex may be
?startIndex?
specified to start in the middle of string.
string index string index
Returns the character at the specifiedindex. An index counts from zero. Useend for the last character.
string is class ?-strict?
?-failindex varname?
Returns 1 if string belongs to class. If -strict, then empty strings never match, otherwise they always match. If
string
a member of class. See Table 4-3 on page 54 for character class names.
string last subString string
Returns the index in string of the last occurrence of subString, or -1 if subString is not found. startIndex may be
?startIndex?
specified to start in the middle of string.
string length string
Returns the number of characters in string.
string map ?-nocase?
Returns a new string created by mapping characters in string according to the input, output list incharMap.
charMap string
See page 55.
string match ?-nocase?
Returns 1 if str matches the pattern, else 0. Glob-style matching is used. See page 53.
-failindex is specified, then varname is assigned the index of the character instring that prevented it from being
pattern str
string range str i j
Returns the range of characters instr from i to j.
string repeat str count
Returns str repeated count times.
string replace str first last
Returns a new string created by replacing charactersfirst through last with newstr, or nothing.
?newstr?
string tolower string ?first?
Returns string in lower case. first and last determine the range of string on which to operate.
?last?
string totitle string ?first?
Capitalizes string by replacing its first character with the Unicode title case, or upper case, and the rest with
?last?
lower case. first and last determine the range of string on which to operate.
string toupper string ?first?
Returns string in upper case. first and last determine the range of string on which to operate.
?last?
string trim string ?chars?
Trims the characters in chars from both ends of string. chars defaults to whitespace.
string trimleft string
Trims the characters in chars from the beginning of string. chars defaults to whitespace.
?chars?
string trimright string
Trims the characters in chars from the end of string. chars defaults to whitespace.
?chars?
string wordend str ix
Returns the index in str of the character after the word containing the character at indexix.
string wordstart str ix
Returns the index in str of the first character in the word containing the character at indexix.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
These are the string operations I use most:
The equal operation, which is shown inExample 4-2 on page 53.
String match. This pattern matching operation is described on page 53.
The tolower, totitle, and toupper operations convert case.
The trim, trimright, and trimleft operations are handy for cleaning up strings.
These new operations were added in Tcl 8.1 (actually, they first appeared in the 8.1.1 patch release):
The equal operation, which is simpler than usingstring compare.
The is operation that test for kinds of strings. String classes are listed inTable 4-3 on page 54.
The map operation that translates characters (e.g., like the Unixtr command.)
The repeat and replace operations.
The totitle operation, which is handy for capitalizing words.
String Indices
Several of the string operations involve string indices that are positions within a string. Tcl counts characters in strings starting with zero. The
special index end is used to specify the last character in a string:
string range abcd 2 end
=> cd
Tcl 8.1 added syntax for specifying an index relative to the end. Specify end-N to get the Nth character before the end. For example, the
following command returns a new string that drops the first and last characters from the original:
string range $string 1 end-1
There are several operations that pick apart strings: first, last, wordstart, wordend, index, and range. If you find yourself using combinations of
these operations to pick apart data, it may be faster if you can do it with the regular expression pattern matcher described in Chapter 11.
Strings and Expressions
Strings can be compared with expr, if, and while using the comparison operators eq, ne, ==, !=, < and >. However, there are a number of subtle
issues that can cause problems. First, you must quote the string value so that the expression parser can identify it as a string type. Then, you
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
must group the expression with curly braces to prevent the double quotes from being stripped off by the main interpreter:
if {$x == "foo"} command
expr is only reliable for string comparison when usingeq or ne.
Despite the quotes, the expression operators that work on numbers and strings first convert try converting items to numbers if possible, and
then converts them back if it detects a case of string comparison. The conversion back is always done as a decimal number. This can lead to
unexpected conversions between strings that look like hexadecimal or octal numbers. The following boolean expression is true!
if {"0xa" == "10"} { puts stdout ack! }
=> ack!
A safe way to compare strings is to use the string compare and string equal operations. The eq and ne expr operators were introduced in 8.4
to allow more compact strict string comparison. These operations also work faster because the unnecessary conversions are eliminated. Like
the C library strcmp function, string compare returns 0 if the strings are equal, minus 1 if the first string is lexicographically less than the
second, or 1 if the first string is greater than the second:
Example 4-1 Comparing strings with string compare
if {[string compare $s1 $s2] == 0} {
# strings are equal
}
The string equal command added in Tcl 8.1 makes this simpler:
Example 4-2 Comparing strings with string equal
if {[string equal $s1 $s2]} {
# strings are equal
}
The eq operator added in Tcl 8.4 is semantically equal, but more compact. It also avoids any internal format conversions. There is also ane
operator to efficiently test for inequality.
Example 4-3 Comparing strings with eq
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
if {$s1 eq $s2} {
# strings are equal
}
String Matching
The string match command implements glob-style pattern matching that is modeled after the file name pattern matching done by various UNIX
shells. The heritage of the word "glob" is rooted in UNIX, and Tcl preserves this historical oddity in the glob command that does pattern
matching on file names. The glob command is described on page 122. Table 4-2 shows the three constructs used instring match patterns:
Table 4-2. Matching characters used with string match
*
Match any number of any characters.
?
Match exactly one character.
[chars]
Match any character in chars.
Any other characters in a pattern are taken as literals that must match the input exactly. The following example matches all strings that begin
with a:
string match a* alpha
=> 1
To match all two-letter strings:
string match ?? XY
=> 1
To match all strings that begin with either a or b:
string match {[ab]*} cello
=> 0
Be careful! Square brackets are also special to the Tcl interpreter, so you will need to wrap the pattern up in curly braces to prevent it from
being interpreted as a nested command. Another approach is to put the pattern into a variable:
set pat {[ab]*x}
string match $pat box
=> 1
You can specify a range of characters with the syntax [x-y]. For example, [a-z] represents the set of all lower-case letters, and [0-9] represents
all the digits. You can include more than one range in a set. Any letter, digit, or the underscore is matched with:
string match {[a-zA-Z0-9_]} $char
The set matches only a single character. To match more complicated patterns, like one or more characters from a set, then you need to use
regular expression matching, which is described on page 158.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
If you need to include a literal *, ?, or bracket in your pattern, preface it with a backslash:
string match {*\?} what?
=> 1
In this case the pattern is quoted with curly braces because the Tcl interpreter is also doing backslash substitutions. Without the braces, you
would have to use two backslashes. They are replaced with a single backslash by Tcl before string match is called.
string match *\\? what?
Character Classes
The string is command tests a string to see whether it belongs to a particularclass. This is useful for input validation. For example, to make
sure something is a number, you do:
if {![string is integer -strict $input]} {
error "Invalid input. Please enter a number."
}
Classes are defined in terms of the Unicode character set, which means they are more general than specifying character sets with ranges
over the ASCII encoding. For example, alpha includes many characters outside the range of[A-Za-z] because of different characters in other
alphabets. The classes are listed in Table 4-3.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 4-3. Character class names
alnum
Any alphabet or digit character.
alpha
Any alphabet character.
ascii
Any character with a 7-bit character code (i.e., less than 128.)
boolean
A valid Tcl boolean value, such as0, 1, true, false (in any case).
control
Character code less than 32, and not NULL.
digit
Any digit character.
double
A valid floating point number.
false
A valid Tcl boolean false value, such as0 or false (in any case).
graph
Any printing characters, not including space characters.
integer
A valid integer.
lower
A string in all lower case.
print
A synonym for alnum.
punct
Any punctuation character.
space
Space, tab, newline, carriage return, vertical tab, backspace.
true
A valid Tcl boolean true value, such as1 or true (in any case).
upper
A string all in upper case.
wordchar
Alphabet, digit, and the underscore.
xdigit
Valid hexadecimal digits.
Mapping Strings
The string map command translates a string based on a character map. The map is in the form of a input, output list. Wherever a string
contains an input sequence, that is replaced with the corresponding output. For example:
string map {f p d l} food
=> pool
The inputs and outputs can be more than one character and they do not have to be the same length:
string map {f p d ll oo u} food
=> pull
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
Example 4-4 is more practical. It uses string map to replace fancy quotes and hyphens produced by Microsoft Word into ASCII equivalents. It
uses the open, read, and close file operations that are described inChapter 9, and the fconfigure command described on page 234 to ensure
that the file format is UNIX friendly.
Example 4-4 Mapping Microsoft World special characters to ASCII
proc Dos2Unix {filename} {
set input [open $filename]
set output [open $filename.new]
fconfigure $output -translation lf
puts $output [string map {
\223 "
\224 "
\222 '
\226 } [read $input]]
close $input
close $output
}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The append Command
The append command takes a variable name as its first argument and concatenates its remaining arguments onto the current value of the
named variable. The variable is created if it does not already exist:
set foo z
append foo a b c
set foo
=> zabc
The append command is efficient with large strings.
The append command provides an efficient way to add items to the end of a string. It modifies a variable directly, so it can exploit the memory
allocation scheme used internally by Tcl. Using the append command like this:
append x " some new stuff"
is always faster than this:
set x "$x some new stuff"
The lappend command described on page 65 has similar performance benefits when working with Tcl lists.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The format Command
The format command is similar to the C printf function. It formats a string according to a format specification:
format spec value1 value2 ...
The spec argument includes literals and keywords. The literals are placed in the result as is, while each keyword indicates how to format the
corresponding argument. The keywords are introduced with a percent sign, %, followed by zero or more modifiers, and terminate with a
conversion specifier. The most general keyword specification for each argument contains up to six parts:
position specifier
flags
field width
precision
word length
conversion character
Example keywords include %f for floating point, %d for integer, and %s for string format. Use%% to obtain a single percent character. The
following examples use double quotes around the format specification. This is because often the format contains white space, so grouping is
required, as well as backslash substitutions like \t or \n, and the quotes allow substitution of these special characters.Table 4-4 lists the
conversion characters:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 4-4. Format conversions
d
Signed integer.
u
Unsigned integer.
i
Signed integer. The argument may be in hex (0x) or octal (0) format.
o
Unsigned octal.
x or X
Unsigned hexadecimal. 'x' gives lowercase results.
c
Map from an integer to the ASCII character it represents.
s
A string.
f
Floating point number in the formata.b.
e or E
Floating point number in scientific notation, a.bE+-c.
g or G
Floating point number in either%f or %e format, whichever is shorter.
A position specifier is i$, which means take the value from argumenti as opposed to the normally corresponding argument. The position
counts from 1. If a position is specified for one format keyword, the position must be used for all of them. If you group the format specification
with double quotes, you need to quote the $ with a backslash:
set lang 2
format "%${lang}\$s" one un uno
=> un
The position specifier is useful for picking a string from a set, such as this simple language-specific example. The message catalog facility
described in Chapter 15 is a much more sophisticated way to solve this problem. The position is also useful if the same value is repeated in
the formatted string.
The flags in a format are used to specify padding and justification. In the following examples, the # causes a leading 0x to be printed in the
hexadecimal value. The zero in 08 causes the field to be padded with zeros. Table 4-5 summarizes the format flag characters.
format "%#x" 20
=> 0x14
format "%#08x" 10
=> 0x0000000a
After the flags you can specify a minimum field width value. The value is padded to this width with spaces, or with zeros if the 0 flag is used:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 4-5. Format flags
-
Left justify the field.
+
Always include a sign, either + or -.
space
Precede a number with a space, unless the number has a leading sign. Useful for packing numbers close together.
0
Pad with zeros.
#
Leading 0 for octal. Leading 0x for hex. Always include a decimal point in floating point. Do not remove trailing zeros (%g).
format "%-20s %3d" Label 2
=> Label
2
You can compute a field width and pass it to format as one of the arguments by using* as the field width specifier. In this case the next
argument is used as the field width instead of the value, and the argument after that is the value that gets formatted.
set maxl 8
format "%-*s = %s" $maxl Key Value
=> Key = Value
The precision comes next, and it is specified with a period and a number. For %f and %e it indicates how many digits come after the decimal
point. For %g it indicates the total number of significant digits used. For%d and %x it indicates how many digits will be printed, padding with
zeros if necessary.
format "%6.2f %6.2d" 1 1
=> 1.00 01
The storage length part comes last but it only became useful in Tcl 8.4 where wide integer support was added. Otherwise Tcl maintains all
floating point values in double-precision, and all integers as long words. Wide integers are a minimum of 64-bits wide. By adding the l (long)
word length specifier, we can see the difference between regular and wide integers.
format %u -1
=> 4294967295
format %lu -1
=> 18446744073709551615
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The scan Command
The scan command parses a string according to a format specification and assigns values to variables. It returns the number of successful
conversions it made, unless no capture variables are given, in which case it returns the scan matches in a list. The general form of the
command is:
scan string format ?var? ?var? ?var? ...
The format for scan is nearly the same as in theformat command. The %c scan format converts one character to its decimal value.
The scan format includes a set notation. Use square brackets to delimit a set of characters. The set matches one or more characters that are
copied into the variable. A dash is used to specify a range. The following scans a field of all lowercase letters.
scan abcABC {%[a-z]} result
=> 1
set result
=> abc
If the first character in the set is a right square bracket, then it is considered part of the set. If the first character in the set is ^, then characters
not in the set match. Again, put a right square bracket immediately after the^ to include it in the set. Nothing special is required to include a left
square bracket in the set. As in the previous example, you will want to protect the format with braces, or use backslashes, because square
brackets are special to the Tcl parser.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
[ Team LiB ]
The binary Command
Tcl 8.0 added support for binary strings. Previous versions of Tcl used null-terminated strings internally, which foils the manipulation of some
types of data. Tcl now uses counted strings, so it can tolerate a null byte in a string value without truncating it.
This section describes the binary command that provides conversions between strings and packed binary data representations. Thebinary
format command takes values and packs them according to a template. For example, this can be used to format a floating point vector in
memory suitable for passing to Fortran. The resulting binary value is returned:
binary format template value ?value ...?
The binary scan command extracts values from a binary string according to a similar template. For example, this is useful for extracting data
stored in binary data file. It assigns values to a set of Tcl variables:
binary scan value template variable ?variable ...?
Format Templates
The format template consists of type keys and counts. The count is interpreted differently depending on the type. For types like integer (i) and
double (d), the count is a repetition count (e.g.,i3 means three integers). For strings, the count is a length (e.g.,a3 means a three-character
string). If no count is specified, it defaults to 1. If count is *, then binary scan uses all the remaining bytes in the value.
Several type keys can be specified in a template. Each key-count combination moves an imaginary cursor through the binary data. There are
special type keys to move the cursor. The x key generates null bytes inbinary format, and it skips over bytes inbinary scan. The @ key uses its
count as an absolute byte offset to which to set the cursor. As a special case,@* skips to the end of the data. The X key backs up count bytes.
The types are summarized in Table 4-6. In the table, count is the optional count following the type letter.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
Table 4-6. Binary conversion types
a
A character string of length count. Padded with nulls inbinary format.
A
A character string of length count. Padded with spaces inbinary format. Trailing nulls and blanks are discarded inbinary scan.
b
A binary string of length count. Low-to-high order.
B
A binary string of length count. High-to-low order.
h
A hexadecimal string of length count. Low-to-high order.
H
A hexadecimal string of length count. High-to-low order. (More commonly used thanh.)
c
An 8-bit character code. Thecount is for repetition.
s
A 16-bit integer in little-endian byte order. Thecount is for repetition.
S
A 16-bit integer in big-endian byte order. Thecount is for repetition.
i
A 32-bit integer in little-endian byte order. Thecount is for repetition.
I
A 32-bit integer in big-endian byte order. Thecount is for repetition.
f
Single-precision floating point value in native format.Thecount is for repetition.
d
Double-precision floating point value in native format. Thecount is for repetition.
w
A 64-bit integer in little-endian byte order. Thecount is for repetition. (Tcl 8.4)
W
A 64-bit integer in big-endian byte order. Thecount is for repetition. (Tcl 8.4)
x
Pack count null bytes with binary format.
Skip count bytes with binary scan.
X
Backup count bytes.
@
Skip to absolute position specified bycount. If count is *, skip to the end.
Numeric types have a particular byte order that determines how their value is laid out in memory. The type keys are lowercase for little-endian
byte order (e.g., Intel) and uppercase for big-endian byte order (e.g., SPARC and Motorola). Different integer sizes are 16-bit (s or S), 32-bit (i
or I), and, with Tcl 8.4 or greater, 64-bit (w or W). Note that the official byte order for data transmitted over a network is big-endian. Floating
point values are always machine-specific, so it only makes sense to format and scan these values on the same machine.
There are three string types: character (a or A), binary (b or B), and hexadecimal (h or H). With these types the count is the length of the string.
The a type pads its value to the specified length with null bytes inbinary format and the A type pads its value with spaces. If the value is too
long, it is truncated. In binary scan, the A type strips trailing blanks and nulls.
A binary string consists of zeros and ones. The b type specifies bits from low-to-high order, and theB type specifies bits from high-to-low order.
A hexadecimal string specifies 4 bits (i.e., nybbles) with each character. The h type specifies nybbles from low-to-high order, and theH type
specifies nybbles from high-to-low order. The B and H formats match the way you normally write out numbers.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Examples
When you experiment with binary format and binary scan, remember that Tcl treats things as strings by default. A "6", for example, is the
character 6 with character code 54 or 0x36. The c type returns these character codes:
set input 6
binary scan $input "c" 6val
set 6val
=> 54
You can scan several character codes at a time:
binary scan abc "c3" list
=> 1
set list
=> 97 98 99
The previous example uses a single type key, so binary scan sets one corresponding Tcl variable. If you want each character code in a
separate variable, use separate type keys:
binary scan abc "ccc" x y z
=> 3
set z
=> 99
Use the H format to get hexadecimal values:
binary scan 6 "H2" 6val
set 6val
=> 36
Use the a and A formats to extract fixed width fields. Here the * count is used to get all the rest of the string. Note thatA trims trailing spaces:
binary scan "hello world " a3x2A* first second
puts "\"$first\" \"$second\""
=> "hel" " world"
Use the @ key to seek to a particular offset in a value. The following command gets the second double-precision number from a vector.
Assume the vector is read from a binary data file:
binary scan $vector "@8d" double
With binary format, the a and A types create fixed width fields. A pads its field with spaces, if necessary. The value is truncated if the string is
too long:
binary format "A9A3" hello world
=> hello wor
An array of floating point values can be created with this command:
binary format "f*" 1.2 3.45 7.43 -45.67 1.03e4
Remember that floating point values are always in native format, so you have to read them on the same type of machine that they were
created. With integer data you specify either big-endian or little-endian formats. The tcl_platform variable described on page 193 can tell you
the byte order of the current platform.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
Binary Data and File I/O
When working with binary data in files, you need to turn off the newline translations and character set encoding that Tcl performs
automatically. These are described in more detail on pages 120 and 219. For example, if you are generating binary data, the following
command puts your standard output in binary mode:
fconfigure stdout -translation binary -encoding binary
puts [binary format "B8" 11001010]
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Related Chapters
To learn more about manipulating data in Tcl, read about lists inChapter 5 and arrays in Chapter 8.
For more about pattern matching, read about regular expressions in Chapter 11.
For more about file I/O, see Chapter 9.
For information on Unicode and other Internationalization issues, seeChapter 15.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 5. Tcl Lists
This chapter describes Tcl lists. Tcl commands described are: list, lindex, llength, lrange, lappend, linsert, lreplace, lsearch, lset, lsort, concat,
join, and split.
Lists in Tcl have the same structure as Tcl commands. All the rules you learned about grouping arguments in Chapter 1 apply to creating valid
Tcl lists. However, when you work with Tcl lists, it is best to think of lists in terms of operations instead of syntax. Tcl commands provide
operations to put values into a list, get elements from lists, count the elements of lists, replace elements of lists, and so on. It is a good habit
to use commands like list and lappend to construct lists, instead of creating them by hand. Lists are used with commands such asforeach that
take lists as arguments. In addition, lists are important when you are building up a command to be evaluated later. Delayed command
evaluation with eval is described in Chapter 10, and similar issues with Tk callback commands are described inChapter 30.
However, Tcl lists are not often the right way to build complicated data structures in scripts. You may find Tcl arrays more useful, and they are
the topic of Chapter 8. List operations are also not right for handling unstructured data such as user input. Use regular expressions instead,
which are described in Chapter 11.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Tcl Lists
A Tcl list is a sequence of values. When you write out a list, it has the same syntax as a Tcl command. A list has its elements separated by
white space. Braces or quotes can be used to group words with white space into a single list element. Because of the relationship between
lists and commands, the list-related commands described in this chapter are used often when constructing Tcl commands.
Since Tcl 8.0, lists are really 1-dimensional object arrays.
Early versions of Tcl represented all values as strings. Lists were just strings with special syntax to group their elements. The string
representation was parsed on each list access, so you could have performance problems with large lists. The performance of lists was
improved by the Tcl compiler added in Tcl 8.0. The Tcl runtime now stores lists using an C array of pointers to each element. (The Tcl_Obj
type is described on page 694.) Tcl can access any element in the list with the same cost. Appending new elements to a list is made efficient
by over allocating the array so there is room to grow. The internal format also records the number of list elements, so getting the length of a
list is cheap. However, you can still get into performance trouble if you use a big Tcl list like a string, e.g., for output. Tcl will convert the list
into a string representation if you print it to a file, or manipulate it with string commands. Table 5-1 describes Tcl commands for lists.
Table 5-1. List-related commands
list arg1 arg2 ...
Creates a list out of all its arguments.
lindex list ?i ...?
Returns the ith element from list. Specifying multiple index elements allows you to descend into nested lists easily.
llength list
Returns the number of elements in list.
lrange list i j
Returns the ith through jth elements from list.
lappend listVar arg ...
Appends elements to the value of listVar.
linsert list index arg
Inserts elements into list before the element at position index. Returns a new list.
arg ...
lreplace list i j arg arg
...
Replaces elements i through j of list with the args. Returns a new list.
lsearch ?options? list
Returns the index of the element in list that matches the value according to the options. Glob matching is the
value
default. Returns -1 if not found.
lset listVar ?i ...?
newValue
Set the ith element in variable listVar to newValue. (Tcl 8.4)
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
lsort ?switches? list
Sorts elements of the list according to the switches: -ascii, -dictionary, -integer, -real, -increasing, -decreasing,
-index ix, -unique, -command command. Returns a new list.
concat list list ...
Joins multiple lists together into one list.
join list joinString
Merges the elements of a list together by separating them withjoinString.
split string splitChars
Splits a string up into list elements, using the characters insplitChars as boundaries between list elements.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Constructing Lists
Constructing a list can be tricky if you try to write the proper list syntax by hand. The manual approach works for simple cases. In more
complex cases, however, you should use Tcl commands that build lists. Using list commands eliminates the struggle to get the grouping and
quoting right, and the list is maintained in an efficient internal format. If you create lists by hand with quoting, there is additional overhead to
parse the string representation the first time you use the list.
The list command
The list command constructs a list out of its arguments so that there is one list element for each argument. The simple beauty of
list is that any
special characters in the list elements do not matter. Spaces inside an element do not cause it to become more than one list element. The list
command is efficient, too. It doesn't matter if list is making a list of three single-character values, or three 10 kilobyte values. The cost to make
that three element list is the same in either case. The most compelling uses of list involve making lists out of variables that could have
arbitrary values, as shown in Example 5-1.
Example 5-1 Constructing a list with the list command
set x {1 2}
=> 1 2
set y \$foo
=> $foo
set l1 [list $x "a b" $y]
=> {1 2} {a b} {$foo}
set l2 [list $l1 $x]
=> {{1 2} {a b} {$foo}}} {1 2}
The list command does automatic quoting.
The first list, l1, has three elements. The values of the elements do not affect the list structure. The second list,l2, has two elements, the value
of l1 and the value of x. Internally Tcl shares values instead of making copies, so constructing lists out of other values is quite efficient.
When you first experiment with Tcl lists, the treatment of curly braces can be confusing. In the assignment to x, for example, the curly braces
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
disappear. However, they seem to come back again when $x is put into a bigger list. Also, the double quotes arounda b get changed into
curly braces. What's going on? There are three steps in the process. In the first step, the Tcl parser groups arguments to the list command. In
the grouping process, the braces and quotes are syntax that define groups. These syntax characters get stripped off. The braces and quotes
are not part of the values being grouped. In the second step, the list command creates an internal list structure. This is an array of references
to each value. In the third step the value is printed out. This step requires conversion of the list into a string representation. The string
representation of the list uses curly braces to group values back into list elements.
The lappend Command
The lappend command is used to append elements to the end of a list. The first argument tolappend is the name of a Tcl variable, and the rest
of the arguments are added to the variable's value as new list elements. Like list, lappend operates efficiently on the internal representation of
the list value. It is always more efficient to use lappend than to try and append elements by hand.
Example 5-2 Using lappend to add elements to a list
lappend new 1 2
=> 1 2
lappend new 3 "4 5"
=> 1 2 3 {4 5}
set new
=> 1 2 3 {4 5}
The lappend command is unique among the list-related commands because its first argument is the name of a list-valued variable, while all
the other commands take list values as arguments. You can call lappend with the name of an undefined variable and the variable will be
created.
The lset Command
The lset command was introduced in Tcl 8.4 to make it easier, and more efficient, to set one element of a list or nested list. Like
lappend, the
first argument to lset is the name of a list variable. The last argument is the value to set. The middle arguments, if any, specify which element
to set. If no index is specified, the whole variable is set to the new value. If the index is a single integer, or end-integer, then that element of
the list is set. If you have a nested list, then you can specify several indices, and each one navigates into the nested list structure. This is
illustrated in Example 5-3. If you specify several indices they can be separate arguments, or grouped into a list. Range checking in
lset is strict
and an error will be thrown for indices given outside of the list or sublist range. The new value of the list in the variable is returned, although
you rarely need this because lset modifies the list variable directly.
Example 5-3 Using lset to set an element of a list
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
lset new "a b c"
=> a b c
lset new 1 "d e"
=> a {d e} c
lset new 1 0 "g h"
=> a {{g h} e} c
The concat Command
The concat command is useful for splicing lists together. It works by concatenating its arguments, separating them with spaces. This joins
multiple lists into one list where the top-level list elements in each input list become top-level list elements in the resulting list:
Example 5-4 Using concat to splice lists together
set x {4 5 6}
set y {2 3}
set z 1
concat $z $y $x
=> 1 2 3 4 5 6
Double quotes behave much like the concat command. In simple cases, double quotes behave exactly likeconcat. However, the concat
command trims extra white space from the end of its arguments before joining them together with a single separating space character.
Example 5-5 compares the use of list, concat, and double quotes:
Example 5-5 Double quotes compared to the concat and list commands
set x {1 2}
=> 1 2
set y "$x 3"
=> 1 2 3
set y [concat $x 3]
=> 1 2 3
set s { 2 }
=> 2
set y "1 $s 3"
=> 1 2 3
set y [concat 1 $s 3]
=> 1 2 3
set z [list $x $s 3]
=> {1 2} { 2 } 3
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The distinction between list and concat becomes important when Tcl commands are built dynamically. The basic rule is thatlist and lappend
preserve list structure, while concat (or double quotes) eliminates one level of list structure. The distinction can be subtle because there are
examples where list and concat return the same results. Unfortunately, this can lead to data-dependent bugs. Throughout the examples of this
book, you will see the list command used to safely construct lists. This issue is discussed more inChapter 10.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Getting List Elements:
,
, and
llength lindex
lrange
The llength command returns the number of elements in a list.
llength {a b {c d} "e f g" h}
=> 5
llength {}
=> 0
The lindex command returns a particular element of a list. It takes an index; list indices count from zero.
set x {1 2 3}
lindex $x 1
=> 2
You can use the keyword end to specify the last element of a list, or the syntaxend-N to count back from the end of the list. The following
commands are equivalent ways to get the element just before the last element in a list.
lindex $list [expr {[llength $list] - 2}]
lindex $list end-1
The lrange command returns a range of list elements. It takes a list and two indices as arguments. Again,end or end-N can be used as an
index:
lrange {1 2 3 {4 5}} 2 end
=> 3 {4 5}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Modifying Lists: linsert and
lreplace
The linsert command inserts elements into a list value at a specified index. If the index is zero or less, then the elements are added to the
front. If the index is equal to or greater than the length of the list, then the elements are appended to the end. Otherwise, the elements are
inserted before the element that is currently at the specified index. The following command adds to the front of a list:
linsert {1 2} 0 new stuff
=> new stuff 1 2
lreplace replaces a range of list elements with new elements. If you don't specify any new elements, you effectively delete elements from a
list.
Note: linsert and lreplace do not modify an existing list like thelappend and lset commands. Instead, they return a new list value. In theExample
5-6, the lreplace command does not change the value ofx:
Example 5-6 Modifying lists with lreplace
set x [list a {b c} e d]
=> a {b c} e d
lreplace $x 1 2 B C
=> a B C d
lreplace $x 0 0
=> {b c} e d
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Searching Lists: lsearch
lsearch returns the index of a value in the list, or -1 if it is not present.lsearch supports pattern matching in its search. Simple pattern matching
is the default, and this can be disabled with the -exact option. The glob pattern matching lsearch uses is described in more detail on page 53.
The -regexp option lets you specify the list value with a regular expression. Regular expressions are described inChapter 11.
In the following example, the glob pattern l* matches the value list, and lsearch returns the index of that element in the input list:
lsearch {here is a list} l*
=> 3
Example 5-7 shows ldelete as a combination of lreplace and lsearch:
Example 5-7 Deleting a list element by value
proc ldelete { list value } {
set ix [lsearch -exact $list $value]
if {$ix >= 0} {
return [lreplace $list $ix $ix]
} else {
return $list
}
}
Tcl 8.4 added several features to lsearch, including typed searching, optimized searches for sorted lists, and the ability to find all matching
elements of a list. The lsearch typed searches use the internal object representation for efficiency and speed. For example, if you have a list
of numbers, the -integer option tells lsearch to leave the values in their native integer format. Otherwise it would convert them to strings as it
did the search. If your list has been sorted, the -sorted option tells lsearch to perform an efficient binary search.Sorting lists is described on
page 70.
The -inline option returns the list value instead of the index. This is most useful when you are matching a pattern, and it works well with the
-all
option that returns all matching indices, or values:
set foo {the quick brown fox jumped over a lazy dog}
lsearch -inline -all $foo *o*
=> brown fox over dog
The lsearch options are described in Table 5-2:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 5-2. Options to the lsearch command
-all
Search for all items that match and return a list of matching indices.
-ascii
The list elements are to be compared as ascii strings. Only meaningful when used with-exact or -sorted.
-decreasing
Assume list elements are in decreasing order. Only meaningful when used with-sorted.
-dictionary
The list elements are to be compared using dictionary-style comparison. Only meaningful when used with
-exact or -sorted.
-exact
Do exact string matching. Mutually exclusive with-glob and -regexp.
-glob
Do glob-style pattern matching (default). Mutually exclusive with-exact and -regexp.
-increasing
Assume list elements are in increasing order. Only meaning when used with sorted.
-
-inline
Return the actual matching element(s) instead of the index to the element. An empty string is returned if no elements match.
-integer
The list elements are to be compared as integers. Only meaning when used with-exact or -sorted.
-not
Negate the sense of the match.
-real
Examine all elements as real (floating-point) values. Only meaning when used with-exact or -sorted.
-regexp
Do regular expression pattern matching. Mutually exclusive with -exact and -glob. Regular expressions are described in
Chapter 11.
-sorted
Specifies that the list is presorted, so Tcl can do a faster binary search to find the pattern.
-start ix
Specify the start index in the list to begin searching.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Sorting Lists: lsort
You can sort a list in a variety of ways with lsort. The list is not sorted in place. Instead, a new list value is returned. The basic types of sorts
are specified with the -ascii, -dictionary, -integer, or -real options. The -increasing or -decreasing option indicate the sorting order. The default
option set is -ascii -increasing. An ASCII sort uses character codes, and a dictionary sort folds together case and treats digits like numbers.
For example:
lsort -ascii {a Z n2 n100}
=> Z a n100 n2
lsort -dictionary {a Z n2 n100}
=> a n2 n100 Z
You can provide your own sorting function for special-purpose sorting. For example, suppose you have a list of names, where each element
is itself a list containing the person's first name, middle name (if any), and last name. The default sorts by everyone's first name. If you want to
sort by their last name, you need to supply a sorting command.
Example 5-8 Sorting a list using a comparison function
proc NameCompare {a b} {
set alast [lindex $a end]
set blast [lindex $b end]
set res [string compare $alast $blast]
if {$res != 0} {
return $res
} else {
return [string compare $a $b]
}
}
set list {{Brent B. Welch} {John Ousterhout} {Miles Davis}}
=> {Brent B. Welch} {John Ousterhout} {Miles Davis}
lsort -command NameCompare $list
=> {Miles Davis} {John Ousterhout} {Brent B. Welch}
The NameCompare procedure extracts the last element from each of its arguments and compares those. If they are equal, then it just
compares the whole of each argument.
Tcl 8.0 added a -index option to lsort that can be used to sort lists on an index. Instead of usingNameCompare, you could do this:
lsort -index end $list
Tcl 8.3 added a -unique option that removes duplicates during sort:
lsort -unique {a b a z c b}
=> a b c z
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
The split Command
The split command takes a string and turns it into a list by breaking it at specified characters and ensuring that the result has the proper list
syntax. The split command provides a robust way to turn input lines into proper Tcl lists:
set line {welch:*:28405:100:Brent Welch:/usr/welch:/bin/csh}
split $line :
=> welch * 28405 100 {Brent Welch} /usr/welch /bin/csh
lindex [split $line :] 4
=> Brent Welch
Do not use list operations on arbitrary data.
Even if your data has space-separated words, you should be careful when using list operators on arbitrary input data. Otherwise, stray double
quotes or curly braces in the input can result in invalid list structure and errors in your script. Your code will work with simple test cases, but
when invalid list syntax appears in the input, your script will raise an error. The next example shows what happens when input is not a valid
list. The syntax error, an unmatched quote, occurs in the middle of the list. However, you cannot access any of the list because the lindex
command tries to convert the value to a list before returning any part of it.
Example 5-9 Use split to turn input data into Tcl lists
set line {this is "not a tcl list}
lindex $line 1
=> unmatched open quote in list
lindex [split $line] 2
=> "not
The default separator character for split is white space, which contains spaces, tabs, and newlines. If there are multiple separator characters
in a row, these result in empty list elements; the separators are not collapsed. The following command splits on commas, periods, spaces, and
tabs. The backslash–space sequence is used to include a space in the set of characters. You could also group the argument to split with
double quotes:
set line "\tHello, world."
split $line \ ,.\t
=> {} Hello {} world {}
A trick that splits each character into a list element is to specify an empty string as the split character. This lets you get at individual characters
with list operations:
split abc {}
=> a b c
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
However, if you write scripts that process data one character at a time, they may run slowly. Read Chapter 11 about regular expressions for
hints on really efficient string processing and using regexp for a multi-character split routine.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
[ Team LiB ]
The join Command
The join command is the inverse of split. It takes a list value and reformats it with specified characters separating the list elements. In doing
so, it removes any curly braces from the string representation of the list that are used to group the top-level elements. For example:
join {1 {2 3} {4 5 6}} :
=> 1:2 3:4 5 6
If the treatment of braces is puzzling, remember that the first value is parsed into a list. The braces around element values disappear in the
process. Example 5-10 shows a way to implement join in a Tcl procedure, which may help to understand the process:
Example 5-10 Implementing join in Tcl
proc join {list sep} {
set s {} ;# s is the current separator
set result {}
foreach x $list {
append result $s $x
set s $sep
}
return $result
}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Related Chapters
Arrays are the other main data structure in Tcl. They are described inChapter 8.
List operations are used when generating Tcl code dynamically. Chapter 10 describes these techniques when using theeval
command.
The foreach command loops over the values in a list. It is described on page 79 inChapter 6.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 6. Control Structure Commands
This chapter describes the Tcl commands that implement control structures: if, switch, foreach, while, for, break, continue, catch, error, and
return.
Control structure in Tcl is achieved with commands, just like everything else. There are looping commands: while, foreach, and for. There are
conditional commands: if and switch. There is an error handling command: catch. Finally, there are some commands to fine-tune control
structures: break, continue, return, and error.
A control structure command often has a command body that is executed later, either conditionally or in a loop. In this case, it is important to
group the command body with curly braces to avoid substitutions at the time the control structure command is invoked. Group with braces,
and let the control structure command trigger evaluation at the proper time. A control structure command returns the value of the last
command it chose to execute.
Another pleasant property of curly braces is that they group things together while including newlines. The examples use braces in a way that
is both readable and convenient for extending the control structure commands across multiple lines.
Commands like if, for, and while involve boolean expressions. They use the expr command internally, so there is no need for you to invokeexpr
explicitly to evaluate their boolean test expressions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
If Then Else
The if command is the basic conditional command. If an expression is true, then execute one command body; otherwise, execute another
command body. The second command body (the else clause) is optional. The syntax of the command is:
if expression ?then? body1 ?else? ?body2?
The then and else keywords are optional. In practice, I omitthen but use else as illustrated in the next example. I always use braces around
the command bodies, even in the simplest cases:
Example 6-1 A conditional if then else command
if {$x == 0} {
puts stderr "Divide by zero!"
} else {
set slope [expr $y/$x]
}
Curly brace positioning is important.
The style of this example takes advantage of the way the Tcl interpreter parses commands. Recall that newlines are command terminators,
except when the interpreter is in the middle of a group defined by braces or double quotes. The stylized placement of the opening curly brace
at the end of the first and third lines exploits this property to extend the if command over multiple lines.
The first argument to if is a boolean expression. As a matter of style this expression is grouped with curly braces. The expression evaluator
performs variable and command substitution on the expression. Using curly braces ensures that these substitutions are performed at the
proper time. It is possible to be lax in this regard, with constructs such as:
if $x break continue
This is a sloppy, albeit legitimate, if command that will either break out of a loop or continue with the next iteration depending on the value of
variable x. This style is fragile and error prone. Instead, always use braces around the command bodies to avoid trouble later when you
modify the command. The following is much better (use then if it suits your taste):
if {$x} {
break
} else {
continue
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
When you are testing the result of a command, you can get away without using curly braces around the command, like this:
if [command] body1
However, it turns out that you can execute theif statement more efficiently if you always group the expression with braces, like this:
if {[command]} body1
You can create chained conditionals by using the elseif keyword. Again, note the careful placement of curly braces that create a singleif
command:
Example 6-2 Chained conditional with elseif
if {$key < 0} {
incr range 1
} elseif {$key == 0} {
return $range
} else {
incr range -1
}
Any number of conditionals can be chained in this manner. However, the switch command provides a more powerful way to test multiple
conditions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Switch
The switch command is used to branch to one of many command bodies depending on the value of an expression. The choice can be made
on the basis of pattern matching as well as simple comparisons. Pattern matching is discussed in more detail in Chapter 4 and Chapter 11.
The general form of the command is:
switch flags value pat1 body1 pat2 body2 ...
Any number of pattern-body pairs can be specified. If multiple patterns match, only the body of the first matching pattern is evaluated. You
can also group all the pattern-body pairs into one argument:
switch flags value { pat1 body1 pat2 body2 ... }
The first form allows substitutions on the patterns but will require backslashes to continue the command onto multiple lines. This is shown in
Example 6-4 on page 78. The second form groups all the patterns and bodies into one argument. This makes it easy to group the whole
command without worrying about newlines, but it suppresses any substitutions on the patterns. This is shown in Example 6-3. In either case,
you should always group the command bodies with curly braces so that substitution occurs only on the body with the pattern that matches the
value.
There are four possible flags that determine how value is matched.
-exact
Matches the value exactly to one of the patterns. This is the default.
-glob
Uses glob-style pattern matching. See page 53.
-regexp
Uses regular expression pattern matching. See page 144.
--
No flag (or end of flags). Necessary when value can begin with -.
The switch command raises an error if any other flag is specified or if thevalue begins with -. In practice I always use the-- flag before value so
that I don't have to worry about that problem.
If the pattern associated with the last body is default, then this command body is executed if no other patterns match. Thedefault keyword
works only on the last pattern-body pair. If you use the default pattern on an earlier body, it will be treated as a pattern to match the literal
string default:
Example 6-3 Using switch for an exact match
switch -exact -- $value {
foo { doFoo; incr count(foo) }
bar { doBar; return $count(foo)}
default { incr count(other) }
}
If you have variable references or backslash sequences in the patterns, then you cannot use braces around all the pattern-body pairs. You
must use backslashes to escape the newlines in the command:
Example 6-4 Using switch with substitutions in the patterns
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
switch -regexp -- $value \
^$key { body1 }\
\t### { body2 }\
{[0-9]*} { body3 }
In this example, the first and second patterns have substitutions performed to replace $key with its value and \t with a tab character. The third
pattern is quoted with curly braces to prevent command substitution; square brackets are part of the regular expression syntax, too. (See
page Chapter 11.)
If the body associated with a pattern is just a dash, -, then the switch command "falls through" to the body associated with the next pattern.
You can tie together any number of patterns in this manner.
Example 6-5 A switch with "fall through" cases
switch -glob -- $value {
X* Y* { takeXorYaction $value }
}
Comments in switch Commands
A comment can occur only where the Tcl parser expects a command to begin. This restricts the location of
comments in a switch command. You must put them inside the command body associated with a pattern, as
shown in Example 6-6. If you put a comment at the same level as the patterns, theswitch command will try to
interpret the comment as one or more pattern-body pairs.
Example 6-6 Comments in switch commands
switch -- $value {
# this comment confuses switch
pattern { # this comment is ok }
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
While
The while command takes two arguments, a test and a command body:
while booleanExpr body
The while command repeatedly tests the boolean expression and then executes the body if the expression is true (nonzero). Because the test
expression is evaluated again before each iteration of the loop, it is crucial to protect the expression from any substitutions before the while
command is invoked. The following is an infinite loop (see also Example 1-13 on page 12):
set i 0 ; while $i<10 {incr i}
The following behaves as expected:
set i 0 ; while {$i<10} {incr i}
It is also possible to put nested commands in the boolean expression. The following example uses gets to read standard input. The gets
command returns the number of characters read, returning -1 upon end of file. Each time through the loop, the variable line contains the next
line in the file:
Example 6-7 A while loop to read standard input
set numLines 0 ; set numChars 0
while {[gets stdin line] >= 0} {
incr numLines
incr numChars [string length $line]
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Foreach
The foreach command loops over a command body assigning one or more loop variables to each of the values in one or more lists. Multiple
loop variables, which were introduced in Tcl 7.5, are a very useful feature. The syntax for the simple case of a single variable and a single list
is:
foreach loopVar valueList commandBody
The first argument is the name of a variable, and the command body is executed once for each element in the list with the loop variable taking
on successive values in the list. The list can be entered explicitly, as in the next example:
Example 6-8 Looping with foreach
set i 1
foreach value {1 3 5 7 11 13 17 19 23} {
set i [expr $i*$value]
}
set i
=> 111546435
It is also common to use a list-valued variable or command result instead of a static list value. The next example loops through command-line
arguments. The variable argv is set by the Tcl interpreter to be a list of the command-line arguments given when the interpreter was started:
Example 6-9 Parsing command-line arguments
# argv is set by the Tcl shells
# possible flags are:
# -max integer
# -force
# -verbose
set state flag
set force 0
set verbose 0
set max 10
foreach arg $argv {
switch -- $state {
flag {
switch -glob -- $arg {
-f* {set force 1}
-v* {set verbose 1}
-max {set state max}
default {error "unknown flag $arg"}
}
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
max {
set max $arg
set state flag
}
}
}
The loop uses the state variable to keep track of what is expected next, which in this example is either a flag or the integer value for
-max. The
-- flag to switch is required in this example because theswitch command complains about a bad flag if the pattern begins with a- character. The
-glob option lets the user abbreviate the-force and -verbose options.
If the list of values is to contain variable values or command results, then thelist command should be used to
form the list. Avoid double quotes because if any values or command results contain spaces or braces, the
list structure will be reparsed, which can lead to errors or unexpected results.
Example 6-10 Using list with foreach
foreach x [list $a $b [foo]] {
puts stdout "x = $x"
}
The loop variable x will take on the value of a, the value of b, and the result of thefoo command, regardless of any special characters or
whitespace in those values.
Multiple Loop Variables
You can have more than one loop variable with foreach. Suppose you have two loop variablesx and y. In the first iteration of the loop,x gets
the first value from the value list and y gets the second value. In the second iteration, x gets the third value andy gets the fourth value. This
continues until there are no more values. If there are not enough values to assign to all the loop variables, the extra variables get the empty
string as their value.
Example 6-11 Multiple loop variables with foreach
foreach {key value} {orange 55 blue 72 red 24 green} {
puts "$key: $value"
}
orange: 55
blue: 72
red: 24
green:
If you have a command that returns a short list of values, then you can abuse the foreach command to assign the results of the commands to
several variables all at once. For example, suppose the command MinMax returns two values as a list: the minimum and maximum values.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Here is one way to get the values:
set result [MinMax $list]
set min [lindex $result 0]
set max [lindex $result 1]
The foreach command lets us do this much more compactly:
foreach {min max} [MinMax $list] {break}
The break in the body of the foreach loop guards against the case where the command returns more values than we expected. This trick is
encapsulated into the lassign procedure in Example 10-4 on page 139.
Multiple Value Lists
The foreach command has the ability to loop over multiple value lists in parallel. In this case, each value list can also have one or more
variables. The foreach command keeps iterating until all values are used from all value lists. If a value list runs out of values before the last
iteration of the loop, its corresponding loop variables just get the empty string for their value.
Example 6-12 Multiple value lists with foreach
foreach {k1 k2} {orange blue red green black} value {55 72 24} {
puts "$k1 $k2: $value"
}
orange blue: 55
red green: 72
black : 24
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
For
The for command is similar to the C for statement. It takes four arguments:
for initial test final body
The first argument is a command to initialize the loop. The second argument is a boolean expression that determines whether the loop body
will execute. The third argument is a command to execute after the loop body:
Example 6-13 A for loop
for {set i 0} {$i < 10} {incr i 3} {
lappend aList $i
}
set aList
=> 0 3 6 9
You could use for to iterate over a list, but you should really useforeach instead. Code like the following is slow and cluttered:
for {set i 0} {$i < [llength $list]} {incr i} {
set value [lindex $list $i]
}
This is the same as:
foreach value $list {
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Break
and
Continue
You can control loop execution with the break and continue commands. The break command causes immediate exit from a loop, while the
continue command causes the loop to continue with the next iteration. There is nogoto command in Tcl.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Catch
Until now we have ignored the possibility of errors. In practice, however, a command will raise an error if it is called with the wrong number of
[*]
arguments, or if it detects some error condition particular to its implementation. An uncaught error aborts execution of a script. The catch
command is used to trap such errors. It takes two arguments:
[*]
More precisely, the Tcl script unwinds and the currentTcl_Eval procedure in the C runtime library returns
TCL_ERROR. There are three cases. In interactive use, the Tcl shell prints the error message. In Tk, errors that arise
during event handling trigger a call to bgerror, a Tcl procedure you can implement in your application. In your own C
code, you should check the result of Tcl_Eval and take appropriate action in the case of an error.
catch command ?resultVar?
The first argument to catch is a command body. The second argument is the name of a variable that will contain the result of the command, or
an error message if the command raises an error. catch returns zero if there was no error caught, or a nonzero error code if it did catch an
error.
You should use curly braces to group the command instead of double quotes because catch invokes the full Tcl interpreter on the command.
If double quotes are used, an extra round of substitutions occurs before catch is even called. The simplest use of catch looks like the
following:
catch { command }
A more careful catch phrase saves the result and prints an error message:
Example 6-14 A standard catch phrase
if {[catch { command arg1 arg2 ... } result]} {
puts stderr $result
} else {
# command was ok, result contains the return value
}
A more general catch phrase is shown in the next example. Multiple commands are grouped into a command body. The
errorInfo variable is set
by the Tcl interpreter after an error to reflect the stack trace from the point of the error:
Example 6-15 A longer catch phrase
if {[catch {
command1
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
command2
command3
} result]} {
global errorInfo
puts stderr $result
puts stderr "*** Tcl TRACE ***"
puts stderr $errorInfo
} else {
# command body ok, result of last command is in result
}
These examples have not grouped the call to catch with curly braces. This is acceptable because catch always returns an integer, so theif
command will parse correctly. However, if we had used while instead of if, then curly braces would be necessary to ensure that thecatch
phrase was evaluated repeatedly.
Catching More Than Errors
The catch command catches more than just errors. If the command body containsreturn, break, or continue commands, these terminate the
command body and are reflected by catch as nonzero return codes. You need to be aware of this if you try to isolate troublesome code with a
catch phrase. An innocent looking return command will cause the catch to signal an apparent error. The next example usesswitch to find out
exactly what catch returns. Nonerror cases are passed up to the surrounding code by invokingreturn, break, or continue:
Example 6-16 There are several possible return values from catch
switch [catch {
command1
command2
...
} result] {
0{
# Normal completion }
1{
# Error case }
2 { return $result ;# return from procedure}
3 { break
;# break out of the loop}
4 { continue
;# continue loop}
default {
# User-defined error codes }
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
Error
The error command raises an error condition that terminates a script unless it is trapped with thecatch command. The command takes up to
three arguments:
error message ?info? ?code?
The message becomes the error message stored in the result variable of thecatch command.
If the info argument is provided, then the Tcl interpreter uses this to initialize theerrorInfo global variable. That variable is used to collect a
stack trace from the point of the error. If the info argument is not provided, then the error command itself is used to initialize theerrorInfo trace.
Example 6-17 Raising an error
proc foo {} {
error bogus
}
foo
=> bogus
set errorInfo
=> bogus
while executing
"error bogus"
(procedure "foo" line 2)
invoked from within
"foo"
In the previous example, the error command itself appears in the trace. One common use of theinfo argument is to preserve the errorInfo that
is available after a catch. In the next example, the information from the original error is preserved:
Example 6-18 Preserving errorInfo when calling error
if {[catch {foo} result]} {
global errorInfo
set savedInfo $errorInfo
# Attempt to handle the error here, but cannot...
error $result $savedInfo
}
The code argument specifies a concise, machine-readable description of the error. It is stored into the globalerrorCode variable. It defaults to
NONE. Many of the file system commands return anerrorCode that has three elements: POSIX, the error name (e.g., ENOENT), and the
associated error message:
POSIX ENOENT {No such file or directory}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
In addition, your application can define error codes of its own. Catch phrases can examine the code in the global errorCode variable and
decide how to respond to the error.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Return
The return command is used to return from a procedure. It is needed if return is to occur before the end of the procedure body, or if a
constant value needs to be returned. As a matter of style, I also use return at the end of a procedure, even though a procedure returns the
value of the last command executed in the body.
Exceptional return conditions can be specified with some optional arguments toreturn. The complete syntax is:
return ?-code c? ?-errorinfo i? ?-errorcode ec? string
The -code option value is one of ok, error, return, break, continue, or an integer. ok is the default if -code is not specified.
The -code error option makes return behave much like theerror command. The -errorcode option sets the global errorCode variable, and the
-errorinfo option initializes the errorInfo global variable. When you use return -code error, there is no error command in the stack trace.
Compare Example 6-17 with Example 6-19:
Example 6-19 Raising an error with return
proc bar {} {
return -code error bogus
}
catch {bar} result
=> 1
set result
=> bogus
set errorInfo
=> bogus
while executing
"bar"
The return, break, and continue code options take effect in the caller of the procedure doing the exceptional return. If
-code return is specified,
then the calling procedure returns. If -code break is specified, then the calling procedure breaks out of a loop, and if-code continue is
specified, then the calling procedure continues to the next iteration of the loop. These -code options to return enable the construction of new
control structures entirely in Tcl. The following example implements the break command with a Tcl procedure:
proc break {} {
return -code break
}
You can return integer-valued codes of your own with return -code, and trap them with catch in order to create your own control structures.
There are also a number of exception packages available on the net that provide Java-like try-catch-except structures for Tcl, although the
Tcl exception mechanism strikes a nice balance between simplicity and power.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 7. Procedures and Scope
Procedures encapsulate a set of commands, and they introduce a local scope for variables. Commands described are:proc, global, and upvar.
Procedures parameterize a commonly used sequence of commands. In addition, each procedure has a new local scope for variables. The
scope of a variable is the range of commands over which it is defined. Originally, Tcl had one global scope for shared variables, local scopes
within procedures, and one global scope for procedures. Tcl 8.0 added namespaces that provide new scopes for procedures and global
variables. For simple applications you can ignore namespaces and just use the global scope. Namespaces are described in Chapter 14.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
The proc Command
A Tcl procedure is defined with the proc command. It takes three arguments:
proc name params body
The first argument is the procedure name, which is added to the set of commands understood by the Tcl interpreter. The name is case
sensitive and can contain any characters. Procedure names do not conflict with variable names. The second argument is a list of parameter
names. The last argument is the body of the procedure.
Once defined, a Tcl procedure is used just like any other Tcl command. When it is called, each argument is assigned to the corresponding
parameter and the body is evaluated. The result of the procedure is the result returned by the last command in the body. The return command
can be used to return a specific value.
Procedures can have default parameters so that the caller can leave out some of the command arguments. A default parameter is specified
with its name and default value, as shown in the next example:
Example 7-1 Default parameter values
proc P2 {a {b 7} {c -2} } {
expr $a / $b + $c
}
P2 6 3
=> 0
Here the procedure P2 can be called with one, two, or three arguments. If it is called with only one argument, then the parameters
b and c take
on the values specified in the proc command. If two arguments are provided, then onlyc gets the default value, and the arguments are
assigned to a and b. At least one argument and no more than three arguments can be passed toP2.
A procedure can take a variable number of arguments by specifying the args keyword as the last parameter. When the procedure is called,
the args parameter is a list that contains all the remaining values:
Example 7-2 Variable number of arguments
proc ArgTest {a {b foo} args} {
foreach param {a b args} {
puts stdout "\t$param = [set $param]"
}
}
set x one
set y {two things}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set z \[special\$
ArgTest $x
=> a = one
b = foo
args =
ArgTest $y $z
=> a = two things
b = [special$
args =
ArgTest $x $y $z
=> a = one
b = two things
args = {[special$}
ArgTest $z $y $z $x
=> a = [special$
b = two things
args = {[special$} one
The effect of the list structure in args is illustrated by the treatment of variable z in Example 7-2. The value of z has special characters in it.
When $z is passed as the value of parameterb, its value comes through to the procedure unchanged. When$z is part of the optional
parameters, quoting is automatically added to create a valid Tcl list as the value of args. Example 10-3 on page 136 illustrates a technique
that uses eval to undo the effect of the added list structure.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Changing Command Names with rename
The rename command changes the name of a command. There are two main uses forrename. The first is to augment an existing procedure.
Before you redefine it with proc, rename the existing command:
rename foo foo.orig
From within the new implementation of foo you can invoke the original command asfoo.orig. Existing users of foo will transparently use the
new version.
The other thing you can do with rename is completely remove a command by renaming it to the empty string. For example, you might not
want users to execute UNIX programs, so you could disableexec with the following command:
rename exec {}
Command renaming and deletion can be traced with thetrace command described in Chapter 13.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Scope
By default there is a single, global scope for procedure names. This means that you can use a procedure anywhere in your script. Variables
defined outside any procedure are global variables. However, as described below, global variables are not automatically visible inside
procedures. There is a different namespace for variables and procedures, so you could have a procedure and a global variable with the same
name without conflict. You can use the namespace facility described in Chapter 7 to manage procedures and global variables.
Each procedure has a local scope for variables. That is, variables introduced in the procedure live only for the duration of the procedure call.
After the procedure returns, those variables are undefined. Variables defined outside the procedure are not visible to a procedure unless the
upvar or global scope commands are used. You can also use qualified names to name variables in a namespace scope. The
global and upvar
commands are described later in this chapter. Qualified names are described on page 208. If the same variable name exists in an outer
scope, it is unaffected by the use of that variable name inside a procedure.
In Example 7-3, the variable a in the global scope is different from the parametera to P1. Similarly, the global variableb is different from the
variable b inside P1:
Example 7-3 Variable scope and Tcl procedures
set a 5
set b -8
proc P1 {a} {
set b 42
if {$a < 0} {
return $b
} else {
return $a
}
}
P1 $b
=> 42
P1 [expr {$a*2}]
=> 10
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The global Command
Global scope is the toplevel scope. This scope is outside of any procedure. Variables defined at the global scope must be made accessible to
the commands inside a procedure by using the global command. The syntax for global is:
global varName1 varName2 ...
The global command goes inside a procedure.
The global command adds a global variable to the current scope. A common mistake is to have a single
global command and expect that to
apply to all procedures. However, a global command in the global scope has no effect. Instead, you must put aglobal command in all
procedures that access the global variable. The variable can be undefined at the time the global command is used. When the variable is
defined, it becomes visible in the global scope.
Example 7-4 shows a random number generator. Before we look at the example, let me point out that the best way to get random numbers in
Tcl is to use the rand() math function:
expr rand()
=> .137287362934
The point of the example is to show a state variable, the seed, that has to persist between calls to random, so it is kept in a global variable.
The choice of randomSeed as the name of the global variable associates it with the random number generator. It is important to pick names
of global variables carefully to avoid conflict with other parts of your program. For comparison, Example 14-1 on page 206 uses namespaces
to hide the state variable:
[*]
Example 7-4 A random number generator.
proc RandomInit { seed } {
global randomSeed
set randomSeed $seed
}
proc Random {} {
global randomSeed
set randomSeed [expr ($randomSeed*9301 + 49297) % 233280]
return [expr $randomSeed/double(233280)]
}
proc RandomRange { range } {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
expr int([Random]*$range)
}
RandomInit [pid]
=> 5049
Random
=> 0.517686899863
Random
=> 0.217176783265
RandomRange 100
=> 17
[*]
Adapted from Exploring Expect by Don Libes, O'Reilly & Associates, Inc., 1995, and fromNumerical Recipes in C by
Press et al., Cambridge University Press, 1988.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Call by Name Using
upvar
Use the upvar command when you need to pass the name of a variable, as opposed to its value, into a procedure. Theupvar command
associates a local variable with a variable in a scope up the Tcl call stack. The syntax of the upvar command is:
upvar ?level? varName localvar
The level argument is optional, and it defaults to 1, which means one level up the Tcl call stack. You can specify some other number of
frames to go up, or you can specify an absolute frame number with a #number syntax. Level #0 is the global scope, so theglobal foo
command is equivalent to:
upvar #0 foo foo
The variable in the uplevel stack frame can be either a scalar variable, an array element, or an array name. In the first two cases, the local
variable is treated like a scalar variable. In the case of an array name, then the local variable is treated like an array. The use of upvar and
arrays is discussed further in Chapter 8 on page 99. The following procedure uses upvar to print the value of a variable given its name.
Example 7-5 Print variable by name
proc PrintByName { varName } {
upvar 1 $varName var
puts stdout "$varName = $var"
}
You can use upvar to fix the incr command. One drawback of the built-inincr is that it raises an error if the variable does not exist. We can
define a new version of incr that initializes the variable if it does not already exist:
Example 7-6 Improved incr procedure
proc incr { varName {amount 1}} {
upvar 1 $varName var
if {[info exists var]} {
set var [expr $var + $amount]
} else {
set var $amount
}
return $var
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Variable Aliases with upvar
The upvar command is useful in any situation where you have the name of a variable stored in another variable. InExample 7-2 on page 88,
the loop variable param holds the names of other variables. Their value is obtained with this construct:
puts stdout "\t$param = [set $param]"
Another way to do this is to use upvar. It eliminates the need to use awkward constructs like[set $param]. If the variable is in the same scope,
use zero as the scope number with upvar. The following is equivalent:
upvar 0 $param x
puts stdout "\t$param = $x"
Associating State with Data
Suppose you have a program that maintains state about a set of objects like files, URLs, or people. You can use the name of these objects as
the name of a variable that keeps state about the object. The upvar command makes this more convenient:
upvar #0 $name state
Using the name directly like this is somewhat risky. If there were an object named x, then this trick might conflict with an unrelated variable
named x elsewhere in your program. You can modify the name to make this trick more robust:
upvar #0 state$name state
Your code can pass name around as a handle on an object, then useupvar to get access to the data associated with the object. Your code is
just written to use the state variable, which is an alias to the state variable for the current object. This technique is illustrated inExample 17-7
on page 245.
Namespaces and upvar
You can use upvar to create aliases for namespace variables, too. Namespaces are described inChapter 14. For example, as an alternative
to reserving all global variables beginning with state, you can use a namespace to hide these variables:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
upvar #0 state::$name state
Now state is an alias to the namespace variable. Thisupvar trick works from inside any namespace.
Commands That Take Variable Names
Several Tcl commands involve variable names. For example, the Tk widgets can be associated with a global Tcl variable. The vwait and
tkwait commands also take variable names as arguments.
Upvar aliases do not work with Tk widget text variables.
The aliases created with upvar do not work with these commands, nor do they work if you usetrace, which is described on page 193. Instead,
you must use the actual name of the global variable. To continue the above example where state is an alias, you cannot:
vwait state(foo)
button .b -textvariable state(foo)
Instead, you must
vwait state$name\(foo)
button .b -textvariable state$name\(foo)
The backslash turns off the array reference so Tcl does not try to access name as an array. You do not need to worry about special
characters in $name, except parentheses. Once the name has been passed into the Tk widget it will be used directly as a variable name. Text
variables for labels are explained on page 490, and text variables for entry widgets are illustrated in Example 34-1 on page 508.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 8. Tcl Arrays
This chapter describes Tcl arrays, which provide a flexible mechanism to build many other data structures in Tcl. Tcl command described is:
array.
An array is a Tcl variable with a string-valued index. You can think of the index as a key, and the array as a collection of related data items
identified by different keys. The index, or key, can be any string value. Internally, an array is implemented with a hash table, so the cost of
accessing each array element is about the same. Before Tcl 8.0, arrays had a performance advantage over lists that took time to access
proportional to the size of the list.
The flexibility of arrays makes them an important tool for the Tcl programmer. A common use of arrays is to manage a collection of variables,
much as you use a C struct or Pascal record. This chapter shows how to create several simple data structures using Tcl arrays.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Array Syntax
The index of an array is delimited by parentheses. The index can have any string value, and it can be the result of variable or command
substitution. Array elements are defined with set:
set arr(index) value
The value of an array element is obtained with $ substitution:
set foo $arr(index)
Example 8-1 uses the loop variable value $i as an array index. It setsarr(x) to the product of 1 * 2 * ... * x:
Example 8-1 Using arrays
set arr(0) 1
for {set i 1} {$i <= 10} {incr i} {
set arr($i) [expr {$i * $arr([expr {$i-1}])}]
}
Complex Indices
An array index can be any string, like orange, 5, 3.1415, or foo,bar. The examples in this chapter, and in this book, often use indices that are
pretty complex strings to create flexible data structures. As a rule of thumb, you can use any string for an index, but avoid using a string that
contains spaces.
Parentheses are not a grouping mechanism.
The main Tcl parser does not know about array syntax. All the rules about grouping and substitution described in Chapter 1 are still the same
in spite of the array syntax described here. Parentheses do not group like curly braces or quotes, which is why a space causes problems. If
you have complex indices, use a comma to separate different parts of the index. If you use a space in an index instead, then you have a
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
quoting problem. The space in the index needs to be quoted with a backslash, or the whole variable reference needs to be grouped:
set {arr(I'm asking for trouble)} {I told you so.}
set arr(I'm\ asking\ for\ trouble) {I told you so.}
If the array index is stored in a variable, then there is no problem with spaces in the variable's value. The following works well:
set index {I'm asking for trouble}
set arr($index) {I told you so.}
Array Variables
You can use an array element as you would a simple variable. For example, you can test for its existence with info exists, increment its value
with incr, and append elements to it withlappend:
if {[info exists stats($event)]} {incr stats($event)}
You can delete an entire array, or just a single array element with unset. Using unset on an array is a convenient way to clear out a big data
structure.
It is an error to use a variable as both an array and a normal variable. The following is an error:
set arr(0) 1
set arr 3
=> can't set "arr": variable is array
The name of the array can be the result of a substitution. This is a tricky situation, as shown in Example 8-2:
Example 8-2 Referencing an array indirectly
set name TheArray
=> TheArray
set ${name}(xyz) {some value}
=> some value
set x $TheArray(xyz)
=> some value
set x ${name}(xyz)
=> TheArray(xyz)
set x [set ${name}(xyz)]
=> some value
A better way to deal with this situation is to use the upvar command, which is introduced on page 91. The previous example is much cleaner
when upvar is used:
Example 8-3 Referencing an array indirectly using upvar
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set name TheArray
=> TheArray
upvar 0 $name a
set a(xyz) {some value}
=> some value
set x $TheArray(xyz)
=> some value
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The array Command
The array command returns information about array variables. Thearray names command returns the index names that are defined in the
array. If the array variable is not defined, then array names just returns an empty list. It allows easy iteration through an array with aforeach
loop:
foreach index [array names arr pattern] {
# use arr($index)
}
The order of the names returned by array names is arbitrary. It is essentially determined by the hash table implementation of the array. You
can limit what names are returned by specifying a pattern that matches indices. The pattern is the kind supported by thestring match
command, which is described on page 53.
It is also possible to iterate through the elements of an array one at a time using the search-related commands listed in Table 8-1. The
ordering is also random, and I find the foreach over the results ofarray names much more convenient. If your array has an extremely large
number of elements, or if you need to manage an iteration over a long period of time, then the array search operations might be more
appropriate. Frankly, I never use them. Table 8-1 summarizes the array command:
Table 8-1. The array command
array exists arr
Returns 1 if arr is an array variable.
array get arr ?pattern?
Returns a list that alternates between an index and the corresponding array value. pattern selects matching
indices. If not specified, all indices and values are returned.
array names arr ?mode?
Returns the list of all indices defined for arr, or those that matchpattern. mode specifies the pattern type and
?pattern?
may be -exact, -glob (default) or -regexp.
array set arr list
Initializes the array arr from list, which has the same form as the list returned by
array get.
array size arr
Returns the number of indices defined for arr.
array unset arr ?pattern?
Unset elements in arr matching the specified glob-stylepattern. If not specified, unsetarr. (Tcl 8.3)
array startsearch arr
Returns a search token for a search througharr.
array nextelement arr id
Returns the value of the next element in arr in the search identified by the tokenid. Returns an empty string if
no more elements remain in the search.
array anymore arr id
Returns 1 if more elements remain in the search.
array donesearch arr id
Ends the search identified by id.
array statistics arr
Returns statistics about the array hash table. (Tcl 8.4)
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Converting Between Arrays and Lists
The array get and array set operations are used to convert between an array and a list. The list returned byarray get has an even number of
elements. The first element is an index, and the next is the corresponding array value. The list elements continue to alternate between index
and value. The list argument to array set must have the same structure.
array set fruit {
best kiwi
worst peach
ok banana
}
array get fruit
=> ok banana best kiwi worst peach
Another way to loop through the contents of an array is to usearray get and the two-variable form of the foreach command.
foreach {key value} [array get fruit] {
# key is ok, best, or worst
# value is some fruit
}
Passing Arrays by Name
The upvar command works on arrays. You can pass an array name to a procedure and use theupvar command to get an indirect reference to
the array variable in the caller's scope. This is illustrated in Example 8-4, which inverts an array. As witharray names, you can specify a
pattern to array get to limit what part of the array is returned. This example usesupvar because the array names are passed into the
ArrayInvert procedure. The inverse array does not need to exist before you callArrayInvert.
Example 8-4 ArrayInvert inverts an array
proc ArrayInvert {arrName inverseName {pattern *}} {
upvar $arrName array $inverseName inverse
foreach {index value} [array get array $pattern] {
set inverse($value) $index
}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Building Data Structures with Arrays
This section describes several data structures you can build with Tcl arrays. These examples are presented as procedures that implement
access functions to the data structure. Wrapping up your data structures in procedures is good practice. It shields the user of your data
structure from the details of its implementation.
Use arrays to collect related variables.
A good use for arrays is to collect together a set of related variables for a module, much as one would use a record in other languages. By
collecting these together in an array that has the same name as the module, name conflicts between different modules are avoided. Also, in
each of the module's procedures, a single global statement will suffice to make all the state variables visible. You can also useupvar to
manage a collection of arrays, as shown in Example 8-9 on page 101.
Simple Records
Suppose we have a database of information about people. The following examples show three different ways to store the employee name, ID,
manager, and phone number. Each example implements Emp_AddRecord that stores the values, and one example accessor function that
returns information about the employee (e.g., Emp_Manager.) By using simple procedures to return fields of the record, the implementation is
hidden so that you can change it more easily. Example 8-5 uses on array for each field. The name of the person is the index into each array:
Example 8-5 Using arrays for records, version 1
proc Emp_AddRecord {id name manager phone} {
global employeeID employeeManager \
employeePhone employeeName
set employeeID($name) $id
set employeeManager($name) $manager
set employeePhone($name) $phone
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set employeeName($id) $name
}
proc Emp_Manager {name} {
global employeeManager
return $employeeManager($name)
}
The employeeName array provides a secondary key. It maps from the employee ID to the name so that the other information can be obtained
if you have an ID instead of a name. Example 8-6 implements the same little database using a single array with more complex indices:
Example 8-6 Using arrays for records, version 2
proc Emp_AddRecord {id name manager phone} {
global employee
set employee(id,$name) $id
set employee(manager,$name) $manager
set employee(phone,$name) $phone
set employee(name,$id) $name
}
proc Emp_Manager {name} {
global employee
return $employee(manager,$name)
}
Example 8-7 shows the last approach. Each array element is a list of fields, and the accessor functions hide the
lindex command used to pick
out the right field. Here the cross referencing by ID is implement differently. If we can assume that names and IDs are distinct, we can keep
the cross reference in the same array:
Example 8-7 Using arrays for records, version 3
proc Emp_AddRecord {id name manager phone} {
global employee
set employee($name) [list $name $id $manager $phone]
set employee($id) $name
}
proc Emp_Manager {name} {
global employee
return [lindex $employee($name) 2]
}
The difference between these three approaches is partly a matter of taste. Using a single array can be more convenient because there are
fewer variables to manage. Using the lists for the fields is probably the most space efficient because there are fewer elements in the array,
but maintaining the lindex offsets is tedious. In any case, you should hide the implementation in a small set of procedures.
A Stack
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A stack can be implemented with either a list or an array. If you use a list, then the push and pop operations have a runtime cost that is
proportional to the size of the stack. If the stack has a few elements this is fine. If there are a lot of items in a stack, you may wish to use
arrays instead.
Example 8-8 Using a list to implement a stack
proc Push { stack value } {
upvar $stack list
lappend list $value
}
proc Pop { stack } {
upvar $stack list
set value [lindex $list end]
set list [lrange $list 0 [expr [llength $list]-2]]
return $value
}
In these examples, the name of the stack is a parameter, and upvar is used to convert that into the data used for the stack. The variable is a
list in Example 8-8 and an array in Example 8-9. The user of the stack module does not have to know.
The array implementation of a stack uses one array element to record the number of items in the stack. The other elements of the array have
the stack values. The Push and Pop procedures both guard against a nonexistent array with theinfo exists command. When the first
assignment to S(top) is done by Push, the array variable is created in the caller's scope. The example uses array indices in two ways. Thetop
index records the depth of the stack. The other indices are numbers, so the construct $S($S(top)) is used to reference the top of the stack.
Example 8-9 Using an array to implement a stack
proc Push { stack value } {
upvar $stack S
if {![info exists S(top)]} {
set S(top) 0
}
set S($S(top)) $value
incr S(top)
}
proc Pop { stack } {
upvar $stack S
if {![info exists S(top)]} {
return {}
}
if {$S(top) == 0} {
return {}
} else {
incr S(top) -1
set x $S($S(top))
unset S($S(top))
return $x
}
}
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A List of Arrays
Suppose you have many arrays, each of which stores some data, and you want to maintain an overall ordering among the data sets. One
approach is to keep a Tcl list with the name of each array in order. Example 8-10 defines RecordInsert to add an array to the list, and an
iterator function, RecordIterate, that applies a script to each array in order. The iterator usesupvar to make data an alias for the current array.
The script is executed with eval, which is described in detail inChapter 10. The Tcl commands in script can reference the arrays with the name
data:
Example 8-10 A list of arrays
proc RecordAppend {listName arrayName} {
upvar $listName list
lappend list $arrayName
}
proc RecordIterate {listName script} {
upvar $listName list
foreach arrayName $list {
upvar #0 $arrayName data
eval $script
}
}
Another way to implement this list-of-records structure is to keep references to the arrays that come before and after each record. Example
8-11 shows the insert function and the iterator function when using this approach. Once again,upvar is used to set up data as an alias for the
current array in the iterator. In this case, the loop is terminated by testing for the existence of the next array. It is perfectly all right to make an
alias with upvar to a nonexistent variable. It is also all right to change the target of theupvar alias. One detail that is missing from the example
is the initialization of the very first record so that its next element is the empty string:
Example 8-11 A list of arrays
proc RecordInsert {recName afterThis} {
upvar $recName record $afterThis after
set record(next) $after(next)
set after(next) $recName
}
proc RecordIterate {firstRecord body} {
upvar #0 $firstRecord data
while {[info exists data]} {
eval $body
upvar #0 $data(next) data
}
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A Simple In-Memory Database
Suppose you have to manage a lot of records, each of which contain a large chunk of data and one or more key values you use to look up
those values. The procedure to add a record is called like this:
Db_Insert keylist datablob
The datablob might be a name, value list suitable for passing toarray set, or simply a large chunk of text or binary data. One implementation of
Db_Insert might just be:
foreach key $keylist {
lappend Db($key) $datablob
}
The problem with this approach is that it duplicates the data chunks under each key. A better approach is to use two arrays. One stores all
the data chunks under a simple ID that is generated automatically. The other array stores the association between the keys and the data
chunks. Example 8-12, which uses the namespace syntax described inChapter 14, illustrates this approach. The example also shows how
you can easily dump data structures by writing array set commands to a file, and then load them later with asource command:
Example 8-12 A simple in-memory database
namespace eval db {
variable data
;# Array of data blobs
variable uid 0
;# Index into data
variable index
;# Cross references into data
}
proc db::insert {keylist datablob} {
variable data
variable uid
variable index
set data([incr uid]) $datablob
foreach key $keylist {
lappend index($key) $uid
}
}
proc db::get {key} {
variable data
variable index
set result {}
if {![info exist index($key)]} {
return {}
}
foreach uid $index($key) {
lappend result $data($uid)
}
return $result
}
proc db::save {filename} {
variable uid
set out [open $filename w]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
puts $out [list namespace eval db \
[list variable uid $uid]]
puts $out [list array set db::data [array get db::data]]
puts $out [list array set db::index [array get db::index]]
close $out
}
proc db::load {filename} {
source $filename
}
Alternatives to Using Arrays
While Tcl arrays are flexible and general purpose, they are not always the best solution to your data structure problems. If you find yourself
building elaborate data structures, you should consider implementing a C library to encapsulate the data structure and expose it to the
scripting level with Tcl commands. For example, Chapter 47 implements a blob data structure in C. You can also use the SWIG code
generator can quickly generate a Tcl command interface for a C API. Find out about SWIG at http://www.swig.org.
The Metakit embedded database provides an efficient, easy, scriptable database for Tcl. It is more powerful than the simple "flat file"
databases implemented in this Chapter, but it is not a full SQL database. It is part of Tclkit, or you can use it with the mk4tcl extension. Tclkit
and Metakit are described in Chapter 22.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 9. Working with Files and Programs
This chapter describes how to run programs, examine the file system, and access environment variables through the env array. Tcl
commands described are: exec, file, open, close, read, write, puts, gets, flush, seek, tell, glob, pwd, cd, exit, pid, and registry.
This chapter describes how to run programs and access the file system from Tcl. These commands were designed for UNIX. In Tcl 7.5 they
were implemented in the Tcl ports to Windows and Macintosh. There are facilities for naming files and manipulating file names in a
platform-independent way, so you can write scripts that are portable across systems. These capabilities enable your Tcl script to be a
general-purpose glue that assembles other programs into a tool that is customized for your needs. Tcl 8.4 added support for 64-bit file
systems, where available.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Running Programs with exec
[*]
The exec command runs programs from your Tcl script. For example:
[*]
Unlike other UNIX shellexec commands, the Tcl exec does not replace the current process with the new one.
Instead, the Tcl library forks first and executes the program as a child process.
set d [exec date]
The standard output of the program is returned as the value of the exec command. However, if the program writes to its standard error
channel or exits with a nonzero status code, then exec raises an error. If you do not care about the exit status, or you use a program that
insists on writing to standard error, then you can use catch to mask the errors:
catch {exec program arg arg} result
The exec command supports a full set of I/O redirection and pipeline syntax. Each process normally has three I/O channels associated with it:
standard input, standard output, and standard error. With I/O redirection, you can divert these I/O channels to files or to I/O channels you
have opened with the Tcl open command. A pipeline is a chain of processes that have the standard output of one command hooked up to the
standard input of the next command in the pipeline. Any number of programs can be linked together into a pipeline.
Example 9-1 Using exec on a process pipeline
set n [exec sort < /etc/passwd | uniq | wc -l 2> /dev/null]
Example 9-1 uses exec to run three programs in a pipeline. The first program issort, which takes its input from the file/etc/passwd. The output
of sort is piped into uniq, which suppresses duplicate lines. The output of uniq is piped into wc, which counts the lines. The error output of the
command is diverted to the null device to suppress any error messages. Table 9-1 provides a summary of the syntax understood by theexec
command.
Table 9-1. Summary of the exec syntax for I/O redirection
-keepnewline
(First argument.) Do not discard trailing newline from the result.
|
Pipes standard output from one process into another.
|&
Pipes both standard output and standard error output.
< fileName
Takes input from the named file.
<@ fileId
Takes input from the I/O channel identified byfileId.
<< value
Takes input from the given value.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
> fileName
Overwrites fileName with standard output.
2> fileName
Overwrites fileName with standard error output.
>& fileName
Overwrites fileName with both standard error and standard out.
>> fileName
Appends standard output to the named file.
2>> fileName
Appends standard error to the named file.
>>& fileName
Appends both standard error and standard output to the named file.
>@ fileId
Directs standard output to the I/O channel identified byfileId.
2>@ fileId
Directs standard error to the I/O channel identified by fileId.
>&@ fileId
Directs both standard error and standard output to the I/O channel.
&
As the last argument, indicates pipeline should run in background.
A trailing & causes the program to run in the background. In this case, the process identifier is returned by theexec command. Otherwise, the
exec command blocks during execution of the program, and the standard output of the program is the return value ofexec. The trailing newline
in the output is trimmed off, unless you specify -keepnewline as the first argument to exec.
If you look closely at the I/O redirection syntax, you'll see that it is built up from a few basic building blocks. The basic idea is that | stands for
pipeline, > for output, and< for input. The standard error is joined to the standard output by&. Standard error is diverted separately by using2>.
You can use your own I/O channels by using @.
The auto_noexec Variable
The Tcl shell programs are set up during interactive use to attempt to execute unknown Tcl commands as programs. For example, you can
get a directory listing by typing:
ls
instead of:
exec ls
This is handy if you are using the Tcl interpreter as a general shell. It can also cause unexpected behavior when you are just playing around.
To turn this off, define the auto_noexec variable:
set auto_noexec anything
Limitations of exec on Windows
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Windows 3.1 has an unfortunate combination of special cases that stem from console-mode programs, 16-bit programs, and 32-bit programs.
In addition, pipes are really just simulated by writing output from one process to a temporary file and then having the next process read from
that file. If exec or a process pipeline fails, it is because of a fundamental limitation of Windows. The good news is that Windows 98 and
Windows NT cleaned up most of the problems with exec. Windows NT, Window 2000, and Windows XP are pretty robust.
Tcl 8.0p2 was the last release to officially support Windows 3.1. That release includes Tcl1680.dll, which is necessary to work with the win32s
subsystem. If you copy that file into the same directory as the other Tcl DLLs, you may be able to use some later releases of Tcl on Windows
3.1. However, Tcl 8.3 completely removed support for win32s while adding support for Windows XP-64.
AppleScript
on Macintosh
The exec command is not provided on the Macintosh. Tcl ships with an AppleScript extension that lets you control other Macintosh
applications. You can find documentation in the AppleScript.html that goes with the distribution. You must usepackage require to load the
AppleScript command:
package require Tclapplescript
AppleScript junk
=> bad option "junk": must be compile, decompile, delete, execute, info, load, run, or store.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The file Command
The file command provides several ways to check the status of files in the file system. For example, you can find out if a file exists, what type
of file it is, and other file attributes. There are facilities for manipulating files in a platform-independent manner. Table 9-2 provides a summary
of the various forms of the file command. They are described in more detail later. Note that several operations have been added since the
introduction of the file command; the table indicates the version of Tcl in which they were added.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 9-2. The file command options
file atime name ?time?
Returns access time as a decimal string. Iftime is specified, the access time of the file is set.
file attributes name ?option?
?value? ...
Queries or sets file attributes. (Tcl 8.0)
file channels ?pattern?
Returns the open channels in this interpreter, optionally filtered by the glob-stylepattern. (Tcl 8.3)
file copy ?-force? source
Copies file source to file destination. The source and destination can be directories. (Tcl 7.6)
destination
file delete ?-force? name
Deletes the named file. (Tcl 7.6)
file dirname name
Returns parent directory of file name.
file executable name
Returns 1 if name has execute permission, else 0.
file exists name
Returns 1 if name exists, else 0.
file extension name
Returns the part of name from the last dot (i.e.,.) to the end. The dot is included in the return value.
file isdirectory name
Returns 1 if name is a directory, else 0.
file isfile name
Returns 1 if name is not a directory, symbolic link, or device, else 0.
file join path path...
Joins pathname components into a new pathname. (Tcl 7.5)
file link ?-type? name ?target?
Returns the link pointed to by name, or creates a link totarget if it is specified. type can be -hard or
-symbolic. (Tcl 8.4)
file lstat name var
Places attributes of the link name into var.
file mkdir name
Creates directory name. (Tcl 7.6)
file mtime name ?time?
Returns modify time of name as a decimal string. Iftime is specified, the modify time of the file is set.
file nativename name
Returns the platform-native version of name. (Tk 8.0).
file normalize name
Returns a unique, absolute, path forname while eliminating extra /, /., and /.. components. (Tcl 8.4)
file owned name
Returns 1 if current user owns the filename, else 0.
file pathtype name
relative, absolute, or volumerelative. (Tcl 7.5)
file readable name
Returns 1 if name has read permission, else 0.
file readlink name
Returns the contents of the symbolic link name.
file rename ?-force? old new
Changes the name of old to new. (Tcl 7.6)
file rootname name
Returns all but the extension of name (i.e., up to but not including the last. in name).
file separator ?name?
Returns the default file separator character on this file system, or the separator character for name if it is
specified. (Tcl 8.4)
file size name
Returns the number of bytes in name.
file split name
Splits name into its pathname components. (Tcl 7.5)
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
file stat name var
Places attributes of name into array var. The elements defined forvar are listed in Table 9-3.
file system name
Returns a tuple of the filesystem for name (e.g. native or vfs) and the platform-specific type forname (e.g
NTFS or FAT32). (Tcl 8.4)
file tail name
Returns the last pathname component ofname.
file type name
Returns type identifier, which is one of: file, directory, characterSpecial, blockSpecial, fifo, link, or socket.
file volumes name
Returns the available file volumes on this computer. On Unix, this always returns /. On Windows, this
would be a list like {a:/ c:/}. (Tcl 8.3)
file writable name
[ Team LiB ]
Returns 1 if name has write permission, else 0.
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Cross-Platform File Naming
Files are named differently on UNIX, Windows, and Macintosh. UNIX separates file name components with a forward slash (/), Macintosh
separates components with a colon (:), and Windows separates components with a backslash (\). In addition, the way that absolute and
relative names are distinguished is different. For example, these are absolute pathnames for the Tcl script library (i.e., $tcl_library) on
Macintosh, Windows, and UNIX, respectively:
Disk:System Folder:Extensions:Tool Command Language:tcl7.6
c:\Program Files\Tcl\lib\Tcl7.6
/usr/local/tcl/lib/tcl7.6
The good news is that Tcl provides operations that let you deal with file pathnames in a platform-independent manner. The file operations
described in this chapter allow either native format or the UNIX naming convention. The backslash used in Windows pathnames is especially
awkward because the backslash is special to Tcl. Happily, you can use forward slashes instead:
c:/Program Files/Tcl/lib/Tcl7.6
There are some ambiguous cases that can be specified only with native pathnames. On my Macintosh, Tcl and Tk are installed in a directory
that has a slash in it. You can name it only with the native Macintosh name:
Disk:Applications:Tcl/Tk 4.2
Another construct to watch out for is a leading // in a file name. This is the Windows syntax for network names that reference files on other
computers. You can avoid accidentally constructing a network name by using the file join command described next. Of course, you can use
network names to access remote files.
If you must communicate with external programs, you may need to construct a file name in the native syntax for the current platform. You can
construct these names with file join described later. You can also convert a UNIX-like name to a native name withfile nativename.
Several of the file operations operate on pathnames as opposed to returning information about the file itself. You can use thedirname,
extension, join, normalize, pathtype, rootname, split, and tail operations on any string; there is no requirement that the pathnames refer to an
existing file.
Building up Pathnames: file join
You can get into trouble if you try to construct file names by simply joining components with a slash. If part of the name is in native format,
joining things with slashes will result in incorrect pathnames on Macintosh and Windows. The same problem arises when you accept user
input. The user is likely to provide file names in native format. For example, this construct will not create a valid pathname on the Macintosh
because $tcl_library is in native format:
set file $tcl_library/init.tcl
Use file join to construct file names.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The platform-independent way to construct file names is with file join. The following command returns the name of theinit.tcl file in native
format:
set file [file join $tcl_library init.tcl]
The file join operation can join any number of pathname components. In addition, it has the feature that an
absolute pathname overrides any previous components. For example (on UNIX), /b/c is an absolute
pathname, so it overrides any paths that come before it in the arguments to file join:
file join a b/c d
=> a/b/c/d
file join a /b/c d
=> /b/c/d
On Macintosh, a relative pathname starts with a colon, and an absolute pathname does not. To specify an absolute path, you put a trailing
colon on the first component so that it is interpreted as a volume specifier. These relative components are joined into a relative pathname:
file join a :b:c d
=> :a:b:c:d
In the next case, b:c is an absolute pathname withb: as the volume specifier. The absolute name overrides the previous relative name:
file join a b:c d
=> b:c:d
The file join operation converts UNIX-style pathnames to native format. For example, on Macintosh you get this:
file join /usr/local/lib
=> usr:local:lib
Chopping Pathnames: split, dirname, tail
The file split command divides a pathname into components. It is the inverse offile join. The split operation detects automatically if the input is
in native or UNIX format. The results of file split may contain some syntax to help resolve ambiguous cases when the results are passed back
to file join. For example, on Macintosh a UNIX-style pathname is split on slash separators. The Macintosh syntax for a volume specifierDisk:)
(
is returned on the leading component:
file split "/Disk/System Folder/Extensions"
=> Disk: {System Folder} Extensions
A common reason to split up pathnames is to divide a pathname into the directory part and the file part. This task is handled directly by the
dirname and tail operations. The dirname operation returns the parent directory of a pathname, whiletail returns the trailing component of the
pathname:
file dirname /a/b/c
=> /a/b
file tail /a/b/c
=> c
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
For a pathname with a single component, the dirname option returns ".", on UNIX and Windows, or ":" on Macintosh. This is the name of the
current directory.
The extension and root options are also complementary. The extension option returns everything from the last period in the name to the end
(i.e., the file suffix including the period.) The root option returns everything up to, but not including, the last period in the pathname:
file root /a/b.c
=> /a/b
file extension /a/b.c
=> .c
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Manipulating Files and Directories
Tcl 7.6 added file operations to copy files, delete files, rename files, and create directories. In earlier versions it was necessary to exec other
programs to do these things, except on Macintosh, where cp, rm, mv, mkdir, and rmdir were built in. These commands are no longer supported
on the Macintosh. Your scripts should use the file command operations described below to manipulate files in a platform-independent way.
File name patterns are not directly supported by the file operations. Instead, you can use theglob command described on page 122 to get a
list of file names that match a pattern.
Copying Files
The file copy operation copies files and directories. The following example copiesfile1 to file2. If file2 already exists, the operation raises an
error unless the -force option is specified:
file copy ?-force? file1 file2
Several files can be copied into a destination directory. The names of the source files are preserved. The -force option indicates that files
under directory can be replaced:
file copy ?-force? file1 file2 ... directory
Directories can be recursively copied. The -force option indicates that files underdir2 can be replaced:
file copy ?-force? dir1 dir2
Creating Directories
The file mkdir operation creates one or more directories:
file mkdir dir dir ...
It is not an error if the directory already exists. Furthermore, intermediate directories are created if needed. This means that you can always
make sure a directory exists with a single mkdir operation. Suppose /tmp has no subdirectories at all. The following command creates
/tmp/sub1 and /tmp/sub1/sub2:
file mkdir /tmp/sub1/sub2
The -force option is not understood by file mkdir, so the following command accidentally creates a folder named-force, as well as one named
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
oops.
file mkdir -force oops
Symbolic and Hard Links
The file link operation allows the user to manipulate links. Hard links are directory entries that directly reference an existing file or directory.
Symbolic (i.e., soft) links are files that contain the name of another file or directory. Generally, opening a link opens the file referenced by the
link. Operating system support for links varies. Unix supports both types of links. Classic Macintosh only supports symbolic links (i.e., aliases).
Windows 95/98/ME do not support links at all, while Windows NT/2000/XP support symbolic links to directories and hard links to files.
With only a single argument, file link returns the value of a symbolic link, or raises an error if the file is not a symbolic link. With two pathname
arguments, the first is the name of the link, and the second is the name of the file referenced by the link. If you leave out the -hard or
-symbolic, the appropriate link type is created for the current platform:
file link the_link the_existing_file
Deleting Files
The file delete operation deletes files and directories. It isnot an error if the files do not exist. A non-empty directory is not deleted unless the
-force option is specified, in which case it is recursively deleted:
file delete ?-force? name name ...
To delete a file or directory named -force, you must specify a nonexistent file before the-force to prevent it from being interpreted as a flag
(-force -force won't work):
file delete xyzzy -force
Renaming Files and Directories
The file rename operation changes a file's name fromold to new. The -force option causes new to be replaced if it already exists.
file rename ?-force? old new
Using file rename is the best way to update an existing file. First, generate the new version of the file in a temporary file. Then, use
file rename
to replace the old version with the new version. This ensures that any other programs that access the file will not see the new version until it is
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
complete.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
File Attributes
There are several file operations that return specific file attributes: atime, executable, exists, isdirectory, isfile, mtime, owned, readable,
readlink, size and type. Refer to Table 9-2 on page 108 for their function. The following command usesfile mtime to compare the modify times
of two files. If you have ever resorted to piping the results of ls -l into awk in order to derive this information in other shell scripts, you will
appreciate this example:
Example 9-2 Comparing file modify times
proc newer { file1 file2 } {
if {![file exists $file2]} {
return 1
} else {
# Assume file1 exists
expr {[file mtime $file1] > [file mtime $file2]}
}
}
You can use the optional time argument to mtime and atime to set the file's time attributes, like the Unixtouch command. The stat and lstat
operations return a collection of file attributes. They take a third argument that is the name of an array variable, and they initialize that array
with elements that contain the file attributes. If the file is a symbolic link, then the lstat operation returns information about the link itself and
the stat operation returns information about the target of the link.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 9-3. Array elements defined by file stat
atime
The last access time, in seconds.
ctime
The last change time (not the create time), in seconds.
dev
The device identifier, an integer.
gid
The group owner, an integer.
ino
The file number (i.e., inode number), an integer.
mode
The permission bits.
mtime
The last modify time, in seconds.
nlink
The number of links, or directory references, to the file.
size
The number of bytes in the file.
type
file, directory, characterSpecial, blockSpecial, fifo, link, or socket.
uid
The owner's user ID, an integer.
The array elements are listed in Table 9-3. All the element values are decimal strings, except fortype, which can have the values returned by
the type option. The element names are based on the UNIXstat system call. Use thefile attributes command described later to get other
platform-specific attributes.
Example 9-3 uses the device (dev) and inode (ino) attributes of a file to determine whether two pathnames reference the same file. These
attributes are UNIX specific; they are not well defined on Windows and Macintosh.
Example 9-3 Determining whether pathnames reference the same file
proc fileeq { path1 path2 } {
file stat $path1 stat1
file stat $path2 stat2
expr {$stat1(ino) == $stat2(ino) && \
$stat1(dev) == $stat2(dev)}
}
The file attributes operation was added in Tcl 8.0 to provide access to platform-specific attributes. Theattributes operation lets you set and
query attributes. The interface uses option-value pairs. With no options, all the current values are returned.
file attributes book.doc
=> -creator FRAM -hidden 0 -readonly 0 -type MAKR
These Macintosh attributes are explained in Table 9-4. The four-character type codes used on Macintosh are illustrated on page 600. With a
single option, only that value is returned:
file attributes book.doc -readonly
=> 0
The attributes are modified by specifying one or more option–value pairs. Setting attributes can raise an error if you do not have the right
permissions:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
file attributes book.doc -readonly 1 -hidden 0
Table 9-4. Platform-specific file attributes
-permissions
File permission bits. mode is an octal number or symbolic representation (e.g.a+x) with bits defined by thechmod
mode
system call, or a simplified ls-style string of the form rwxrwxrwx (must be 9 characters). (UNIX)
-group ID
The group owner of the file. (UNIX)
-owner ID
The owner of the file. (UNIX)
-archive bool
The archive bit, which is set by backup programs. (Windows)
-system bool
If set, then you cannot remove the file. (Windows)
-longname
The long (expanded) version of the pathname. Read-only. (Windows)
-shortname
The short (8.3) version of the pathname. Read-only. (Windows)
-hidden bool
If set, then the file does not appear in listings. (Windows, Macintosh)
-readonly bool
If set, then you cannot write the file. (Windows, Macintosh)
-creator type
type is 4-character code of creating application. (Macintosh)
-type type
type is 4-character type code. (Macintosh)
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Input/Output Command Summary
The following sections describe how to open, read, and write files. The basic model is that you open a file, read or write it, then close the file.
Network sockets also use the commands described here. Socket programming is discussed in Chapter 17, and more advanced event-driven
I/O is described in Chapter 16. Table 9-5 lists the basic commands associated with file I/O:
Table 9-5. Tcl commands used for file access
open what ?access? ?permissions?
Returns channel ID for a file or pipeline.
puts ?-nonewline? ?channel? string
Writes a string.
gets channel ?varname?
Reads a line.
read channel ?numBytes?
Reads numBytes bytes, or all data.
read -nonewline channel
Reads all bytes and discard the last\n.
tell channel
Returns the seek offset.
seek channel offset ?origin?
Sets the seek offset. origin is one of start, current, or end.
eof channel
Queries end-of-file status.
flush channel
Writes buffers of a channel.
close channel
Closes an I/O channel.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Opening Files for I/O
The open command sets up an I/O channel to either a file or a pipeline of processes. The return value ofopen is an identifier for the I/O
channel. Store the result of open in a variable and use the variable as you used thestdout, stdin, and stderr identifiers in the examples so far.
The basic syntax is:
open what ?access? ?permissions?
The what argument is either a file name or a pipeline specification similar to that used by the
exec command. The access argument can take
two forms, either a short character sequence that is compatible with the fopen library routine, or a list of POSIX access flags.Table 9-6
summarizes the first form, while Table 9-7 summarizes the POSIX flags. Ifaccess is not specified, it defaults to read.
Example 9-4 Opening a file for writing
set fileId [open /tmp/foo w 0600]
puts $fileId "Hello, foo!"
close $fileId
The permissions argument is a value used for the permission bits on a newly created file. UNIX uses three bits each for the owner, group,
and everyone else. The bits specify read, write, and execute permission. These bits are usually specified with an octal number, which has a
leading zero, so that there is one octal digit for each set of bits. The default permission bits are 0666, which grant read/write access to
everybody. Example 9-4 specifies 0600 so that the file is readable and writable only by the owner.0775 would grant read, write, and execute
permissions to the owner and group, and read and execute permissions to everyone else. You can set other special properties with additional
high-order bits. Consult the UNIX manual page on chmod command for more details.
Table 9-6. Summary of the open access arguments
r
Opens for reading. The file must exist.
r+
Opens for reading and writing. The file must exist.
w
Opens for writing. Truncate if it exists. Create if it does not exist.
w+
Opens for reading and writing. Truncate or create.
a
Opens for writing. Data is appended to the file.
a+
Opens for reading and writing. Data is appended.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 9-7. Summary of POSIX flags for the access argument
RDONLY
Opens for reading.
WRONLY
Opens for writing.
RDWR
Opens for reading and writing.
APPEND
Opens for append.
CREAT
Creates the file if it does not exist.
EXCL
If CREAT is also specified, then the file cannot already exist.
NOCTTY
Prevents terminal devices from becoming the controlling terminal.
NONBLOCK
Does not block during the open.
TRUNC
Truncates the file if it exists.
The following example illustrates how to use a list of POSIX access flags to open a file for reading and writing, creating it if needed, and not
truncating it. This is something you cannot do with the simpler form of the access argument:
set fileId [open /tmp/bar {RDWR CREAT}]
Catch errors from open.
In general, you should check for errors when opening files. The following example illustrates a catch phrase used to open files. Recall that
catch returns 1 if it catches an error; otherwise, it returns zero. It treats its second argument as the name of a variable. In the error case, it
puts the error message into the variable. In the normal case, it puts the result of the command into the variable:
Example 9-5 A more careful use of open
if [catch {open /tmp/data r} fileId] {
puts stderr "Cannot open /tmp/data: $fileId"
} else {
# Read and process the file, then...
close $fileId
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Opening a Process Pipeline
You can open a process pipeline by specifying the pipe character, |, as the first character of the first argument. The remainder of the pipeline
specification is interpreted just as with the exec command, including input and output redirection. The second argument determines which
end of the pipeline open returns. The following example runs the UNIXsort program on the password file, and it uses thesplit command to
separate the output lines into list elements:
Example 9-6 Opening a process pipeline
set input [open "|sort /etc/passwd" r]
set contents [split [read $input] \n]
close $input
You can open a pipeline for both read and write by specifying the r+ access mode. In this case, you need to worry about buffering. After aputs,
the data may still be in a buffer in the Tcl library. Use the flush command to force the data out to the spawned processes before you try to
read any output from the pipeline. You can also use the fconfigure command described on page 233 to force line buffering. Remember that
read-write pipes will not work at all with Windows 3.1 because pipes are simulated with files. Event-driven I/O is also very useful with pipes. It
means you can do other processing while the pipeline executes, and simply respond when the pipe generates data. This is described in
Chapter 16.
Expect
If you are trying to do sophisticated things with an external application, you will find that the Expect extension provides a much more powerful
interface than a process pipeline. Expect adds Tcl commands that are used to control interactive applications. It is extremely useful for
automating a variety of applications such as ssh, Telnet, and programs under test. Tcl is able to handle simple FTP sessions, telnet and many
command line controllable applications, but Expect has extra control at the tty level that is essential for certain applications. It comes on some
systems as a specially built Tcl shell named expect, and it is also available as an extension that you can dynamically load into Tcl shells with:
package require Expect
Expect was created by Don Libes at the National Institute of Standards and Technology (NIST). Expect is described in Exploring Expect
(Libes, O'Reilly & Associates, Inc., 1995). You can find the software on the CD and on the web at:
http://expect.nist.gov/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Reading and Writing
The standard I/O channels are already open for you. There is a standard input channel, a standard output channel, and a standard error
output channel. These channels are identified by stdin, stdout, and stderr, respectively. Other I/O channels are returned by theopen command,
and by the socket command described on page 239.
There may be cases when the standard I/O channels are not available. The wish shells on Windows and Macintosh have no standard I/O
channels. Some UNIX window managers close the standard I/O channels when you start programs from window manager menus. You can
also close the standard I/O channels with close.
The puts and gets Commands
The puts command writes a string and a newline to the output channel. There are a couple of details about the
puts command that we have not
yet used. It takes a -nonewline argument that prevents the newline character that is normally appended to the output channel. This is used in
the prompt example below. The second feature is that the channel identifier is optional, defaulting to stdout if not specified. Note that you
must use flush to force output of a partial line. This is illustrated inExample 9-7.
Example 9-7 Prompting for input
puts -nonewline "Enter value: "
flush stdout ;# Necessary to get partial line output
set answer [gets stdin]
The gets command reads a line of input, and it has two forms. In the previous example, with just a single argument,
gets returns the line read
from the specified I/O channel. It discards the trailing newline from the return value. If end of file is reached, an empty string is returned. You
must use the eof command to tell the difference between a blank line and end-of-file.eof returns 1 if there is end of file. Given a second
varName argument, gets stores the line into a named variable and returns the number of bytes read. It discards the trailing newline, which is
not counted. A -1 is returned if the channel has reached the end of file.
Example 9-8 A read loop using gets
while {[gets $channel line] >= 0} {
# Process line
}
close $channel
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The read Command
The read command reads blocks of data, and this capability is often more efficient. There are two forms forread: You can specify the
-nonewline argument or the numBytes argument, but not both. Without numBytes, the whole file (or what is left in the I/O channel) is read and
returned. The -nonewline argument causes the trailing newline to be discarded. Given a byte count argument,read returns that amount, or less
if there is not enough data in the channel. The trailing newline is not discarded in this case.
Example 9-9 A read loop using read and split
foreach line [split [read $channel] \n] {
# Process line
}
close $channel
For moderate-sized files, it is about 10 percent faster to loop over the lines in a file using the read loop in the second example. In this case,
read returns the whole file, andsplit chops the file into list elements, one for each line. For small files (less than 1K) it doesn't really matter. For
large files (megabytes) you might induce paging with this approach.
Platform-Specific End of Line Characters
Tcl automatically detects different end of line conventions. On UNIX, text lines are ended with a newline character (\n). On Macintosh, they
are terminated with a carriage return (\r). On Windows, they are terminated with a carriage return, newline sequence (\r\n). Tcl accepts any of
these, and the line terminator can even change within a file. All these different conventions are converted to the UNIX style so that once read,
text lines are always terminated with a newline character (\n). Both the read and gets commands do this conversion.
During output, text lines are generated in the platform-native format. The automatic handling of line formats means that it is easy to convert a
file to native format. You just need to read it in and write it out:
puts -nonewline $out [read $in]
To suppress conversions, use the fconfigure command, which is described in more detail on page 234.
Example 9-10 demonstrates a File_Copy procedure that translates files to native format. It is complicated because it handles directories.
Example 9-10 Copy a file and translate to native format
proc File_Copy {src dest} {
if {[file isdirectory $src]} {
file mkdir $dest
foreach f [glob -nocomplain [file join $src *]] {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
File_Copy $f [file join $dest [file tail $f]]
}
return
}
if {[file isdirectory $dest]} {
set dest [file join $dest [file tail $src]]
}
set in [open $src]
set out [open $dest w]
puts -nonewline $out [read $in]
close $out ; close $in
}
Random Access I/O
The seek and tell commands provide random access to I/O channels. Each channel has a current position called theseek offset. Each read or
write operation updates the seek offset by the number of bytes transferred. The current value of the offset is returned by the tell command.
The seek command sets the seek offset by an amount, which can be positive or negative, from an origin which is either
start, current, or end. If
you are dealing with files greater than 2GB in size, you will need Tcl 8.4 for its 64-bit file system support.
Closing I/O Channels
The close command is just as important as the others because it frees operating system resources associated with the I/O channel. If you
forget to close a channel, it will be closed when your process exits. However, if you have a long-running program, like a Tk script, you might
exhaust some operating system resources if you forget to close your I/O channels.
The close command can raise an error.
If the channel was a process pipeline and any of the processes wrote to their standard error channel, then Tcl believes this is an error. The
error is raised when the channel to the pipeline is finally closed. Similarly, if any of the processes in the pipeline exit with a nonzero status,
close raises an error.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The Current Directory — cd and
pwd
Every process has a current directory that is used as the starting point when resolving a relative pathname. The pwd command returns the
current directory, and the cd command changes the current directory. Example 9-11 uses these commands.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Matching File Names with glob
The glob command expands a pattern into the set of matching file names. The general form of theglob command is:
glob ?options? pattern ?pattern? ...
The pattern syntax is similar to the string match patterns:
* matches zero or more characters.
? matches a single character.
[abc] matches a set of characters.
{a,b,c} matches any of a, b, or c.
All other characters must match themselves.
Table 9-8 lists the options for the glob command.
Table 9-8. glob command options
-directory dir
Search for files in the directorydir. (Tcl 8.3)
-join
The remaining pattern arguments are treated as a single pattern obtained by joining them with directory separators. (Tcl 8.3)
-nocomplain
Causes glob to return an empty list if no files match. Otherwise an error is raised.
-path path
Search for files in the given path prefix path. Allows you to search in areas that may contain glob-sensitive characters. (Tcl
8.3)
-tails
Only return the part of each file found that follows the last directory named in the-directory or -path argument. (Tcl 8.4)
-types types
Only return files matching the types specified.
--
Signifies the end of flags. Must be used ifpattern begins with a-.
Unlike the glob matching in csh, the Tcl glob command matches only the names of existing files. Incsh, the {a,b} construct can match
nonexistent names. In addition, the results of glob are not sorted. Use thelsort command to sort its result if you find it important.
Example 9-11 shows the FindFile procedure, which traverses the file system hierarchy using recursion. At each iteration it saves its current
directory and then attempts to change to the next subdirectory. A catch guards against bogus names. The glob command matches file names:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
Example 9-11 Finding a file by name
proc FindFile { startDir namePat } {
set pwd [pwd]
if {[catch {cd $startDir} err]} {
puts stderr $err
return
}
foreach match [glob -nocomplain -- $namePat] {
puts stdout [file join $startDir $match]
}
foreach file {[glob -nocomplain *]} {
if [file isdirectory $file] {
FindFile [file join $startDir $file] $namePat
}
}
cd $pwd
}
The -types option allows for special filtered matching similar to the UNIXfind command. The first form is like the -type option of find: b (block
special file), c (character special file), d (directory), f (plain file), l (symbolic link), p (named pipe), or s (socket), where multiple types may be
specified in the list. Glob will return all files which match at least one of the types given.
The second form specifies types where all the types given must match. These are r (readable), w (writable) and x (executable) as file
permissions, and readonly and hidden as special cases. On the Macintosh, MacOS types and creators are also supported, where any item
which is four characters long is assumed to be a MacOS type (e.g. TEXT). Items which are of the form{macintosh type XXXX} or {macintosh
creator XXXX} will match types or creators respectively. Unrecognized types, or specifications of multiple MacOS types/creators will signal an
error.
The two forms may be mixed, so-types {d f r w} will find all regular files OR directories that have both read AND write permissions.
Expanding Tilde in File Names
The glob command also expands a leading tilde (~) in filenames. There are two cases:
~/ expands to the current user's home directory.
~user expands to the home directory of user.
If you have a file that starts with a literal tilde, you can avoid the tilde expansion by adding a leading
./ (e.g., ./~foobar).
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The exit and
pid
Commands
The exit command terminates your script. Note thatexit causes termination of the whole process that was running the script. If you supply an
integer-valued argument to exit, then that becomes the exit status of the process.
The pid command returns the process ID of the current process. This can be useful as the seed for a random number generator because it
changes each time you run your script. It is also common to embed the process ID in the name of temporary files.
You can also find out the process IDs associated with a process pipeline withpid:
set pipe [open "|command"]
set pids [pid $pipe]
There is no built-in mechanism to control processes in the Tcl core. On UNIX systems you canexec the kill program to terminate a process:
exec kill $pid
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Environment Variables
Environment variables are a collection of string-valued variables associated with each process. The process's environment variables are
available through the global array env. The name of the environment variable is the index, (e.g.,env(PATH)), and the array element contains
the current value of the environment variable. If assignments are made to env, they result in changes to the corresponding environment
variable. Environment variables are inherited by child processes, so programs run with the exec command inherit the environment of the Tcl
script. The following example prints the values of environment variables.
Example 9-12 Printing environment variable values
proc printenv { args } {
global env
set maxl 0
if {[llength $args] == 0} {
set args [lsort [array names env]]
}
foreach x $args {
if {[string length $x] > $maxl} {
set maxl [string length $x]
}
}
incr maxl 2
foreach x $args {
puts stdout [format "%*s = %s" $maxl $x $env($x)]
}
}
printenv USER SHELL TERM
=>
USER = welch
SHELL = /bin/csh
TERM = tx
Note: Environment variables can be initialized for Macintosh applications by editing a resource of typeSTR# whose name is Tcl Environment
Variables. This resource is part of the tclsh and wish applications. Follow the directions on page 28 for usingResEdit. The format of the
resource values is NAME=VALUE.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The registry Command
Windows uses the registry to store various system configuration information. The Windows tool to browse and edit the registry is called
regedit. Tcl provides a registry command. It is a loadable package that you must load by using:
package require registry
The registry structure has keys, value names, and typed data. The value names are stored under a key, and each value name has data
associated with it. The keys are organized into a hierarchical naming system, so another way to think of the value names is as an extra level
in the hierarchy. The main point is that you need to specify both a key name and a value name in order to get something out of the registry.
The key names have one of the following formats:
\\hostname\rootname\keypath
rootname\keypath
rootname
The rootname is one of HKEY_LOCAL_MACHINE, HKEY_PERFORMANCE_DATA, HKEY_USERS, HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER, HKEY_CURRENT_CONFIG, or HKEY_DYN_DATA. Tables 9-9 and 9-10 summarize the registry command and
data types:
Table 9-9. The registry command
registry delete key ?valueName?
Deletes the key and the named value, or it deletes all values under the key ifvalueName is not
specified.
registry get key valueName
Returns the value associated with valueName under key.
registry keys key ?pat?
Returns the list of keys or value names underkey that match pat, which is a string match pattern.
registry set key
Creates key.
registry set key valueName data
?type?
Creates valueName under key with value data of the given type. Types are listed in Table 9-10.
registry type key valueName
Returns the type of valueName under key.
registry values key ?pat?
Returns the names of the values stored underkey that match pat, which is a string match pattern.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 9-10. The registry data types
binary
Arbitrary binary data.
none
Arbitrary binary data.
expand_sz
A string that contains references to environment variables with the%VARNAME% syntax.
dword
A 32-bit integer.
dword_big_endian
A 32-bit integer in the other byte order. It is represented in Tcl as a decimal string.
link
A symbolic link.
multi_sz
An array of strings, which are represented as a Tcl list.
resource_list
A device driver resource list.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Part II: Advanced Tcl
Part II describes advanced programming techniques that support sophisticated applications. The Tcl interfaces remain
simple, so you can quickly construct powerful applications.
Chapter 10 describes eval, which lets you create Tcl programs on the fly. There are tricks with usingeval correctly, and a
few rules of thumb to make your life easier.
Chapter 11 describes regular expressions. This is the most powerful string processing facility in Tcl. This chapter
includes a cookbook of useful regular expressions.
Chapter 12 describes the library and package facility used to organize your code into reusable modules.
Chapter 13 describes introspection and debugging. Introspection provides information about the state of the Tcl
interpreter.
Chapter 14 describes namespaces that partition the global scope for variables and procedures. Namespaces help you
structure large Tcl applications.
Chapter 15 describes the features that support Internationalization, including Unicode, other character set encodings,
and message catalogs.
Chapter 16 describes event-driven I/O programming. This lets you run process pipelines in the background. It is also
very useful with network socket programming, which is the topic of Chapter 17.
Chapter 18 describes TclHttpd, a Web server built entirely in Tcl. You can build applications on top of TclHttpd, or
integrate the server into existing applications to give them a web interface. TclHttpd also supports regular Web sites.
Chapter 19 describes Safe-Tcl and using multiple Tcl interpreters. If an interpreter is safe, then you can grant it
restricted functionality. This is ideal for supporting network applets that are downloaded from untrusted sites, which is
described in Chapter 20.
Chapter 21 describes how to use the Thread extension to create multi-threaded Tcl scripts. The extension provides
threads, synchronization with mutexes and condition variables, shared variables, and thread pools.
Chapter 22 describes how to package and deploy Tcl applications as Starkits. A Virtual File System facility is used to
create a private file system inside the Starkit to hold the scripts, graphics, and documentation that make up your
application.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 10. Quoting Issues and Eval
This chapter describes explicit calls to the interpreter with the eval command. An extra round of substitutions is performed that results in some
useful effects. The chapter describes the quoting problems with eval and the ways to avoid them. Theuplevel command evaluates commands
in a different scope. The subst command does substitutions but no command invocation.
Dynamic evaluation makes Tcl flexible and powerful, but it can be tricky to use properly. The basic idea is that you create a string and then
use the eval command to interpret that string as a command or a series of commands. Creating program code on the fly is easy with an
interpreted language like Tcl, and very hard, if not impossible, with a statically compiled language like C++ or Java. There are several ways
that dynamic code evaluation is used in Tcl:
In some cases, a simple procedure isn't quite good enough, and you need to glue together a command from a few different pieces
and then execute the result using eval. This often occurs withwrappers, which provide a thin layer of functionality over existing
commands.
Callbacks are script fragments that are saved and evaluated later in response to some event. Examples include the commands
associated with Tk buttons, fileevent I/O handlers, and after timer handlers. Callbacks are a flexible way to link different parts of an
application together.
You can add new control structures to Tcl using the uplevel command. For example, you can write a function that applies a
command to each line in a file or each node in a tree.
You can have a mixture of code and data, and just process the code part with the subst command. For example, this is useful in
HTML templates described in Chapter 18. There are also some powerful combinations ofsubst and regsub described in Chapter
11.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Constructing Code with the
list
Command
It can be tricky to assemble a command so that it is evaluated properly by eval. The same difficulties apply to commands likeafter, uplevel,
and the Tk send command, all of which have similar properties toeval, except that the command evaluation occurs later or in a different
context. Constructing commands dynamically is a source of many problems. The worst part is that you can write code that works sometimes
but not others, which can be very confusing.
Use list when constructing commands.
The root of the quoting problems is the internal use of concat by eval and similar commands to concatenate their arguments into one
command string. The concat can lose some important list structure so that arguments are not passed through as you expect. The general
strategy to avoid these problems is to use list and lappend to explicitly form the command callback as a single, well-structured list.
The eval Command
The eval command results in another call to the Tcl interpreter. If you construct a command dynamically, you must useeval to interpret it. For
example, suppose we want to construct the following command now but execute it later:
puts stdout "Hello, World!"
In this case, it is sufficient to do the following:
set cmd {puts stdout "Hello, World!"}
=> puts stdout "Hello, World!"
# sometime later...
eval $cmd
=> Hello, World!
In this case, the value of cmd is passed to Tcl. All the standard grouping and substitution are done again on the value, which is a
puts
command.
However, suppose that part of the command is stored in a variable, but that variable will not be defined at the time eval is used. We can
artificially create this situation like this:
set string "Hello, World!"
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set cmd {puts stdout $string}
=> puts stdout $string
unset string
eval $cmd
=> can't read "string": no such variable
In this case, the command contains $string. When this is processed byeval, the interpreter looks for the current value ofstring, which is
undefined. This example is contrived, but the same problem occurs if string is a local variable, andcmd will be evaluated later in the global
scope.
A common mistake is to use double quotes to group the command. That will let $string be substituted now. However, this works only if string
has a simple value, but it fails if the value of string contains spaces or other Tcl special characters:
set cmd "puts stdout $string"
=> puts stdout Hello, World!
eval $cmd
=> bad argument "World!": should be "nonewline"
The problem is that we have lost some important structure. The identity of $string as a single argument gets lost in the second round of
parsing by eval. The solution to this problem is to construct the command usinglist, as shown in the following example:
Example 10-1 Using list to construct commands
set string "Hello, World!"
set cmd [list puts stdout $string]
=> puts stdout {Hello, World!}
unset string
eval $cmd
=> Hello, World!
The trick is that list has formed a list containing three elements:puts, stdout, and the value of string. The substitution of $string occurs before
list is called, and list takes care of grouping that value for us. In contrast, using double quotes is equivalent to:
set cmd [concat puts stdout $string]
Double quotes lose list structure.
The problem here is that concat does not preserve list structure. The main lesson is that you should uselist to construct commands if they
contain variable values or command results that must be substituted now. If you use double quotes, the values are substituted but you lose
proper command structure. If you use curly braces, then values are not substituted until later, which may not be in the right context.
Commands That Concatenate Their Arguments
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The uplevel, after and send commands concatenate their arguments into a command and execute it later in a different context. The
uplevel
command is described on page 138, after is described on page 228, and send is described on page 648. Whenever I discover such a
command, I put it on my danger list and make sure I explicitly form a single command argument with list instead of letting the command
concat items for me. Get in the habit now:
after 100 [list doCmd $param1 $param2]
send $interp [list doCmd $param1 $param2] ;# Safe!
The danger here is that concat and list can result in the same thing, so you can be led down the rosy garden path only to get errors later when
values change. The two previous examples always work. The next two work only if param1 and param2 have values that are single list
elements:
after 100 doCmd $param1 $param2
send $interp doCmd $param1 $param2 ;# Unsafe!
If you use other Tcl extensions that provide eval-like functionality, carefully check their documentation to see whether they contain commands
that concat their arguments into a command. For example, Tcl-DP, which provides a network version ofsend, dp_send, also uses concat.
Commands That Use Callbacks
The general strategy of passing out a command or script to call later is a flexible way to assemble different parts of an application, and it is
widely used by Tcl commands. Examples include commands that are called when users click on Tk buttons, commands that are called when
I/O channels have data ready, or commands that are called when clients connect to network servers. It is also easy to write your own
procedures or C extensions that accept scripts and call them later in response to some event.
These other callback situations may not appear to have the "concat problem" because they take a single script argument. However, as soon
as you use double quotes to group that argument, you have created the concat problem all over again. So, all the caveats about usinglist to
construct these commands still apply.
Command Prefix Callbacks
There is a variation on command callbacks called a command prefix. In this case, the command is given additional arguments when it is
invoked. In other words, you provide only part of the command, the command prefix, and the module that invokes the callback adds additional
arguments before using eval to invoke the command.
For example, when you create a network server, you supply a procedure that is called when a client makes a connection. That procedure is
called with three additional arguments that indicate the client's socket, IP address, and port number. This is described in more detail on page
240. The tricky thing is that you can define your callback procedure to take four (or more) arguments. In this case you specify some of the
parameters when you define the callback, and then the socket subsystem specifies the remaining arguments when it makes the callback. The
following command creates the server side of a socket:
set virtualhost www.beedub.com
socket -server [list Accept $virtualhost] 8080
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
However, you define the Accept procedure like this:
proc Accept {myname sock ipaddr port} { ... }
The myname parameter is set when you construct the command prefix. The remaining parameters are set when the callback is invoked. The
use of list in this example is not strictly necessary because "we know" thatvirtualhost will always be a single list element. However, usinglist is
just a good habit when forming callbacks, so I always write the code this way.
There are many other examples of callback arguments that are really command prefixes. Some of these include the scrolling callbacks
between Tk scrollbars and their widgets, the command aliases used with Safe Tcl, the sorting functions in lsort, and the completion callback
used with fcopy. Example 13-6 on page 191 shows how to use eval to make callbacks from Tcl procedures.
Constructing Procedures Dynamically
The previous examples have all focused on creating single commands by using list operations. Suppose you want to create a whole
procedure dynamically. Unfortunately, this can be particularly awkward because a procedure body is not a simple list. Instead, it is a
sequence of commands that are each lists, but they are separated by newlines or semicolons. In turn, some of those commands may be
loops and if commands that have their own command bodies. To further compound the problem, you typically have two kinds of variables in
the procedure body: some that are to be used as values when constructing the body, and some that are to be used later when executing the
procedure. The result can be very messy.
The main trick to this problem is to use either format or regsub to process a template for your dynamically generated procedure. If you use
format, then you can put %s into your templates where you want to insert values. You may find the positional notation of the format string (e.g.,
%1$s and %2$s) useful if you need to repeat a value in several places within your procedure body. The following example is a procedure that
generates a new version of other procedures. The new version includes code that counts the number of times the procedure was called and
measures the time it takes to run:
Example 10-2 Generating procedures dynamically with a template
proc TraceGen {procName} {
rename s$procName $procName-orig
set arglist {}
foreach arg [info args $procName-orig] {
append arglist "\$$arg "
}
proc $procName [info args $procName-orig] [format {
global _trace_count _trace_msec
incr _trace_count(%1$s)
incr _trace_msec(%1$s) [lindex [time {
set result [%1$s-orig %2$s]
} 1] 0]
return $result
} $procName $arglist]
}
Suppose that we have a trivial procedure foo:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
proc foo {x y} {
return [expr $x * $y]
}
If you run TraceGen on it and look at the results, you see this:
TraceGen foo
info body foo
=>
global _trace_count _trace_msec
incr _trace_count(foo)
incr _trace_msec(foo) [lindex [time {
set result [foo-orig $x $y]
} 1] 0]
return $result
The tracing provided by TraceGen is similar to what you can achieve with the features of the Tcl 8.4trace command. With command tracing,
which is described on page 194, you can track the calls and results of procedures.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Exploiting the
concat
inside eval
The previous section warns about the danger of concatenation when forming commands. However, there are times when concatenation is
done for good reason. This section illustrates cases where the concat done by eval is useful in assembling a command by concatenating
multiple lists into one list. A concat is done internally by eval when it gets more than one argument:
eval list1 list2 list3 ...
The effect of concat is to join all the lists into one list; a new level of list structure isnot added. This is useful if the lists are fragments of a
command. It is common to use this form of eval with the args construct in procedures. Use theargs parameter to pass optional arguments
through to another command. Invoke the other command with eval, and the values in$args get concatenated onto the command properly.
The special args parameter is illustrated in Example 7-2 on page 88.
Using eval in a Wrapper Procedure.
Here, we illustrate the use of eval and $args with a simple Tk example. In Tk, thebutton command creates a button in the user interface. The
button command can take many arguments, and commonly you simply specify the text of the button and the Tcl command that is executed
when the user clicks on the button:
button .foo -text Foo -command foo
After a button is created, it is made visible by packing it into the display. The pack command can also take many arguments to control screen
placement. Here, we just specify a side and let the packer take care of the rest of the details:
pack .foo -side left
Even though there are only two Tcl commands to create a user interface button, we will write a procedure that replaces the two commands
with one. Our first version might be:
proc PackedButton {name txt cmd} {
button $name -text $txt -command $cmd
pack $name -side left
}
This is not a very flexible procedure. The main problem is that it hides the full power of the Tk button command, which can really take more
than 30 widget configuration options, such as -background, -cursor, -relief, and more. They are listed on page 459. For example, you can
easily make a red button like this:
button .foo -text Foo -command foo -background red
A better version of PackedButton uses args to pass through extra configuration options to the button command. The args parameter is a list of
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
all the extra arguments passed to the Tcl procedure. My first attempt to use $args looked like this, but it was not correct:
proc PackedButton {name txt cmd args} {
button $name -text $txt -command $cmd $args
pack $name -side left
}
PackedButton .foo "Hello, World!" {exit} -background red
=> unknown option "-background red"
The problem is that $args is a list value, andbutton gets the whole list as a single argument. Instead,button needs to get the elements of $args
as individual arguments.
Use eval with $args
In this case, you can use eval because it concatenates its arguments to form a single list before evaluation. The single list is, by definition, the
same as a single Tcl command, so the button command parses correctly. Here we giveeval two lists, which it joins into one command:
eval {button $name -text $txt -command $cmd} $args
The use of the braces in this command is discussed in more detail below. We also generalize our procedure to take some options to the pack
command. This argument, pack, must be a list of packing options. The final version ofPackedButton is shown in Example 10-3:
Example 10-3 Using eval with $args
# PackedButton creates and packs a button.
proc PackedButton {path txt cmd {pack {-side right}} args} {
eval {button $path -text $txt -command $cmd} $args
eval {pack $path} $pack
}
In PackedButton, both pack and args are list-valued parameters that are used as parts of a command. The internalconcat done by eval is
perfect for this situation. The simplest call to PackedButton is:
PackedButton .new "New" { New }
The quotes and curly braces are redundant in this case but are retained to convey some type information. The quotes imply a string label, and
the braces imply a command. The pack argument takes on its default value, and theargs variable is an empty list. The two commands
executed by PackedButton are:
button .new -text New -command New
pack .new -side right
PackedButton creates a horizontal stack of buttons by default. The packing can be controlled with a packing specification:
PackedButton .save "Save" { Save $file } {-side left}
The two commands executed by PackedButton are:
button .new -text Save -command { Save $file }
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
pack .new -side left
The remaining arguments, if any, are passed through to the button command. This lets the caller fine-tune some of the button attributes:
PackedButton .quit Quit { Exit } {-side left -padx 5} \
-background red
The two commands executed by PackedButton are:
button .quit -text Quit -command { Exit } -background red
pack .quit -side left -padx 5
You can see a difference between the pack and args argument in the call to PackedButton. You need to group the packing options explicitly
into a single argument. The args parameter is automatically made into a list of all remaining arguments. In fact, if you group the extra button
parameters, it will be a mistake:
PackedButton .quit Quit { Exit } {-side left -padx 5} \
{-background red}
=> unknown option "-background red"
Correct Quoting with eval
What about the peculiar placement of braces in PackedButton?
eval {button $path -text $txt -command $cmd} $args
By using braces, we control the number of times different parts of the command are seen by the Tcl evaluator. Without any braces,
everything goes through two rounds of substitution. The braces prevent one of those rounds. In the above command, only $args is
substituted twice. Before eval is called, the $args is replaced with its list value. Then,eval is invoked, and it concatenates its two list
arguments into one list, which is now a properly formed command. The second round of substitutions done by eval replaces the txt and cmd
values.
Do not use double quotes with eval.
You may be tempted to use double quotes instead of curly braces in your uses of eval. Don't give in! Using double quotes is, mostly likely,
wrong. Suppose the first eval command is written like this:
eval "button $path -text $txt -command $cmd $args"
Incidentally, the previous is equivalent to:
eval button $path -text $txt -command $cmd $args
These versions happen to work with the following call because txt and cmd have one-word values with no special characters in them:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
PackedButton .quit Quit { Exit }
The button command that is ultimately evaluated is:
button .quit -text Quit -command { Exit }
In the next call, an error is raised:
PackedButton .save "Save As" [list Save $file]
=> unknown option "As"
This is because the button command is this:
button .save -text Save As -command Save /a/b/c
But it should look like this instead:
button .save -text {Save As} -command {Save /a/b/c}
The problem is that the structure of the button command is now wrong. The value of txt and cmd are substituted first, before eval is even
called, and then the whole command is parsed again. The worst part is that sometimes using double quotes works, and sometimes it fails.
The success of using double quotes depends on the value of the parameters. When those values contain spaces or special characters, the
command gets parsed incorrectly.
Braces: the one true way to group arguments toeval.
To repeat, the safe construct is:
eval {button $path -text $txt -command $cmd} $args
The following variations are also correct. The first uses list to do quoting automatically, and the others use backslashes or braces to prevent
the extra round of substitutions:
eval [list button $path -text $txt -command $cmd] $args
eval button \$path -text \$txt -command \$cmd $args
eval button {$path} -text {$txt} -command {$cmd} $args
Finally, here is one more incorrect approach that tries to quote by hand:
eval "button {$path} -text {$txt} -command {$cmd} $args"
The problem is that double quotes disable the quoting you normally expect with curly braces. Consider this little example that uses double
quotes. The curly braces around $blob have no special effect, and the interpreter sees unbalanced braces:
set blob "foo\{bar space"
=> foo{bar space
eval "puts {$blob}"
=> missing close brace
If we group instead with curly braces, then the variable substitution occurs once, after the arguments to puts have been grouped, and there is
no error.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
eval puts {$blob}
=> foo{bar space
You can also be successful using list:
eval puts [list $blob]
Of course, these simple examples are contrived, but they illustrate the need to be careful with your list construction when using
eval!
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
The uplevel Command
The uplevel command is similar to eval, except that it evaluates a command in a different scope than the current procedure. It is useful for
defining new control structures entirely in Tcl. The syntax for uplevel is:
uplevel ?level? command ?list1 list2 ...?
As with upvar, the level parameter is optional, but recommended for good style, and defaults to1, which means to execute the command in the
scope of the calling procedure. The other common use of level is #0, which means to evaluate the command in the global scope. You can
count up farther than one (e.g., 2 or 3), or count down from the global level (e.g.,#1 or #2), but these cases rarely make sense.
When you specify the command argument, you must be aware of any substitutions that might be performed by the Tcl interpreter before
uplevel is called. If you are entering the command directly, protect it with curly braces so that substitutions occur in the other scope. The
following affects the variable x in the caller's scope:
uplevel {set x [expr $x + 1]}
However, the following will use the value of x in the current scope to define the value ofx in the calling scope, which is probably not what was
intended:
uplevel "set x [expr $x + 1]"
If you are constructing the command dynamically, again use list. This fragment is used later inExample 10-4:
uplevel [list foreach $args $valueList {break}]
It is common to have the command in a variable. This is the case when the command has been passed into your new control flow procedure
as an argument In this case, you should evaluate the command one level up. Put the level in explicitly to avoid cases where $cmd looks like a
number!
uplevel 1 $cmd
Another common scenario is reading commands from users as part of an application. In this case, you should evaluate the command at the
global scope. Example 16-2 on page 230 illustrates this use of uplevel:
uplevel #0 $cmd
If you are assembling a command from a few different lists, such as the args parameter, then you can use concat to form the command:
uplevel [concat $cmd $args]
The lists in $cmd and $args are concatenated into a single list, which is a valid Tcl command. Likeeval, uplevel uses concat internally if it is
given extra arguments, so you can leave out the explicit use of concat. The following commands are equivalent:
uplevel [concat $cmd $args]
uplevel "$cmd $args"
uplevel $cmd $args
Example 10-4 shows list assignment using the foreach trick described on Page 81. List assignment is useful if a command returns several
values in a list. The lassign procedure assigns the list elements to several variables. Thelassign procedure hides the foreach trick, but it must
use the uplevel command so that the loop variables get assigned in the correct scope. Thelist command is used to construct theforeach
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
command that is executed in the caller's scope. This is necessary so that $variables and $values get substituted before the command is
evaluated in the other scope.
Example 10-4 lassign: list assignment with foreach
# Assign a set of variables from a list of values.
# If there are more values than variables, they are returned.
# If there are fewer values than variables,
# the variables get the empty string.
proc lassign {valueList args} {
if {[llength $args] == 0} {
error "wrong # args: lassign list varname ?varname..?"
}
if {[llength $valueList] == 0} {
# Ensure one trip through the foreach loop
set valueList [list {}]
}
uplevel 1 [list foreach $args $valueList {break}]
return [lrange $valueList [llength $args] end]
}
Example 10-5 illustrates a new control structure with theFile_Process procedure that applies a callback to each line in a file. The call touplevel
allows the callback to be concatenated with the line to form the command. The list command is used to quote any special characters inline, so
it appears as a single argument to the command.
Example 10-5 The File_Process procedure iterates over lines in a file
proc File_Process {file callback} {
set in [open $file]
while {[gets $in line] >= 0} {
uplevel 1 $callback [list $line]
}
close $in
}
What is the difference between these two commands?
uplevel 1 [list $callback $line]
uplevel 1 $callback [list $line]
The first form limits callback to be the name of the command, while the second form allowscallback to be a command prefix. Once again,
what is the bug with this version?
uplevel 1 $callback $line
The arbitrary value of $line is concatenated to the callback command, and it is likely to be a malformed command when executed.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The subst Command
The subst command is useful when you have a mixture of Tcl commands, Tcl variable references, and plain old data. Thesubst command
looks through the data for square brackets, dollar signs, and backslashes, and it does substitutions on those. It leaves the rest of the data
alone:
set a "foo bar"
subst {a=$a date=[exec date]}
=> a=foo bar date=Thu Dec 15 10:13:48 PST 1994
The subst command does not honor the quoting effect of curly braces. It does substitutions regardless of braces:
subst {a=$a date={[exec date]}}
=> a=foo bar date={Thu Dec 15 10:15:31 PST 1994}
You can use backslashes to prevent variable and command substitution.
subst {a=\$a date=\[exec date]}
=> a=$a date=[exec date]
You can use other backslash substitutions like\uXXXX to get Unicode characters, \n to get newlines, or \-newline to hide newlines.
The subst command takes flags that limit the substitutions it will perform. The flags are-nobackslashes, -nocommands, or -novariables. You
can specify one or more of these flags before the string that needs to be substituted:
subst -novariables {a=$a date=[exec date]}
=> a=$a date=Thu Dec 15 10:15:31 PST 1994
String Processing with subst
The subst command can be used with theregsub command to do efficient, two-step string processing. In the first step,regsub is used to
rewrite an input string into data with embedded Tcl commands. In the second step, subst or eval replaces the Tcl commands with their result.
By artfully mapping the data into Tcl commands, you can dynamically construct a Tcl script that processes the data. The processing is
efficient because the Tcl parser and the regular expression processor have been highly tuned. Chapter 11 has several examples that use this
technique.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 11. Regular Expressions
This chapter describes regular expression pattern matching and string processing based on regular expression substitutions. These features
provide the most powerful string processing facilities in Tcl. Tcl commands described are: regexp and regsub.
Regular expressions are a formal way to describe string patterns. They provide a powerful and compact way to specify patterns in your data.
Even better, there is a very efficient implementation of the regular expression mechanism due to Henry Spencer. If your script does much
string processing, it is worth the effort to learn about the regexp command. Your Tcl scripts will be compact and efficient. This chapter uses
many examples to show you the features of regular expressions.
Regular expression substitution is a mechanism that lets you rewrite a string based on regular expression matching. The regsub command is
another powerful tool, and this chapter includes several examples that do a lot of work in just a few Tcl commands. Stephen Uhler has shown
me several ways to transform input data into a Tcl script with regsub and then use subst or eval to process the data. The idea takes a moment
to get used to, but it provides a very efficient way to process strings.
Tcl 8.1 added a new regular expression implementation that supports Unicode and advanced regular expressions (ARE). This implementation
adds more syntax and escapes that makes it easier to write patterns, once you learn the new features! If you know Perl, then you are already
familiar with these features. The Tcl advanced regular expressions are almost identical to the Perl 5 regular expressions. The new features
include a few very minor incompatibilities with the regular expressions implemented in earlier versions of Tcl 8.0, but these rarely occur in
practice. The new regular expression package supports Unicode, of course, so you can write patterns to match Japanese or Hindi
documents!
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
.
When to Use Regular Expressions
Regular expressions can seem overly complex at first. They introduce their own syntax and their own rules, and you may be tempted to use
simpler commands like string first, string range, or string match to process your strings. However, often a single regular expression command
can replace a sequence of several string commands. Not only do you have to write less code, but you often get a performance improvement
because the regular expression matcher is implemented in optimized C code, so pattern matching is fast.
The regular expression matcher does more than test for a match. It also tells you what part of your input string matches the pattern. This is
useful for picking data out of a large input string. In fact, you can capture several pieces of data in just one match by using subexpressions.
The regexp Tcl command makes this easy by assigning the matching data to Tcl variables. If you find yourself using
string first and string range
to pick out data, remember that regexp can do it in one step instead.
The regular expression matcher is structured so that patterns are first compiled into an form that is efficient to match. If you use the same
pattern frequently, then the expensive compilation phase is done only once, and all your matching uses the efficient form. These details are
completely hidden by the Tcl interface. If you use a pattern twice, Tcl will nearly always be able to retrieve the compiled form of the pattern. As
you can see, the regular expression matcher is optimized for lots of heavy-duty string processing.
Avoiding a Common Problem
Group your patterns with curly braces.
One of the stumbling blocks with regular expressions is that they use some of the same special characters as Tcl. Any pattern that contains
brackets, dollar signs, or spaces must be quoted when used in a Tcl command. In many cases you can group the regular expression with
curly braces, so Tcl pays no attention to it. However, when using Tcl 8.0 (or earlier) you may need Tcl to do backslash substitutions on part of
the pattern, and then you need to worry about quoting the special characters in the regular expression.
Advanced regular expressions eliminate this problem because backslash substitution is now done by the regular expression engine.
Previously, to get \n to mean the newline character (or \t for tab) you had to let Tcl do the substitution. With Tcl 8.1,\n and \t inside a regular
expression mean newline and tab. In fact, there are now about 20 backslash escapes you can use in patterns. Now more than ever,
remember to group your patterns with curly braces to avoid conflicts between Tcl and the regular expression engine.
The patterns in the first sections of this chapter ignore this problem. The sample expressions in Table 11-7 on page 161 are quoted for use
within Tcl scripts. Most are quoted simply by putting the whole pattern in braces, but some are shown without braces for comparison.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Regular Expression Syntax
This section describes the basics of regular expression patterns, which are found in all versions of Tcl. There are occasional references to
features added by advanced regular expressions, but they are covered in more detail starting on page 149. There is enough syntax in regular
expressions that there are five tables that summarize all the options. These tables appear together starting at page 154.
A regular expression is a sequence of the following items:
A literal character.
A matching character, character set, or character class.
A repetition quantifier.
An alternation clause.
A subpattern grouped with parentheses.
Matching Characters
Most characters simply match themselves. The following pattern matches an a followed by ab:
ab
The general wild-card character is the period, ".". It matches any single character. The following pattern matches ana followed by any
character:
a.
Remember that matches can occur anywhere within a string; a pattern does not have to match the whole string. You can change that by
using anchors, which are described on page 147.
Character Sets
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The matching character can be restricted to a set of characters with the [xyz] syntax. Any of the characters between the two brackets is
allowed to match. For example, the following matches either Hello or hello:
[Hh]ello
The matching set can be specified as a range over the character set with the [x-y] syntax. The following matches any digit:
[0-9]
There is also the ability to specify the complement of a set. That is, the matching character can be anything except what is in the set. This is
achieved with the [^xyz] syntax. Ranges and complements can be combined. The following matches anything except the uppercase and
lowercase letters:
[^a-zA-Z]
Using special characters in character sets.
If you want a ] in your character set, put it immediately after the initial opening bracket. You do not need to do anything special to include
[ in
your character set. The following matches any square brackets or curly braces:
[][{}]
Most regular expression syntax characters are no longer special inside character sets. This means you do not need to backslash anything
inside a bracketed character set except for backslash itself. The following pattern matches several of the syntax characters used in regular
expressions:
[][+*?()|\\]
Advanced regular expressions add names and backslash escapes as shorthand for common sets of characters like white space, alpha,
alphanumeric, and more. These are described on page 149 and listed in Table 11-3 on page 156.
Quantifiers
Repetition is specified with *, for zero or more,+, for one or more, and?, for zero or one. Thesequantifiers apply to the previous item, which is
either a matching character, a character set, or a subpattern grouped with parentheses. The following matches a string that contains b
followed by zero or more a's:
ba*
You can group part of the pattern with parentheses and then apply a quantifier to that part of the pattern. The following matches a string that
has one or more sequences of ab:
(ab)+
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The pattern that matches anything, even the empty string, is:
.*
These quantifiers have a greedy matching behavior: They match as many characters as possible. Advanced regular expressions add
nongreedy matching, which is described on page 151. For example, a pattern to match a single line might look like this:
.*\n
However, as a greedy match, this will match all the lines in the input, ending with the last newline in the input string. The following pattern
matches up through the first newline.
[^\n]*\n
We will shorten this pattern even further on page 151 by using nongreedy quantifiers. There are also special newline sensitive modes you
can turn on with some options described on page 153.
Alternation
Alternation lets you test more than one pattern at the same time. The matching engine is designed to be able to test multiple patterns in
parallel, so alternation is efficient. Alternation is specified with |, the pipe symbol. Another way to match eitherHello or hello is:
hello|Hello
You can also write this pattern as:
(h|H)ello
or as:
[hH]ello
Anchoring a Match
By default a pattern does not have to match the whole string. There can be unmatched characters before and after the match. You can
anchor the match to the beginning of the string by starting the pattern with ^, or to the end of the string by ending the pattern with$. You can
force the pattern to match the whole string by using both. All strings that begin with spaces or tabs are matched with:
^[ \t]+
If you have many text lines in your input, you may be tempted to think of ^ as meaning "beginning of line" instead of "beginning of string." By
default, the ^ and $ anchors are relative to the whole input, and embedded newlines are ignored. Advanced regular expressions support
options that make the ^ and $ anchors line-oriented. They also add the\A and \Z anchors that always match the beginning and end of the
string, respectively.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Backslash Quoting
Use the backslash character to turn off these special characters :
.*?+[]()^$|\
For example, to match the plus character, you will need:
\+
Remember that this quoting is not necessary inside a bracketed expression (i.e., a character set definition.) For example, to match either plus
or question mark, either of these patterns will work:
(\+|\?)
[+?]
To match a single backslash, you need two. You must do this everywhere, even inside a bracketed expression. Or you can use \B, which was
added as part of advanced regular expressions. Both of these match a single backslash:
\\
\B
Unknown backslash sequences are an error.
Versions of Tcl before 8.1 ignored unknown backslash sequences in regular expressions. For example, \= was just =, and \w was just w. Even
\n was just n, which was probably frustrating to many beginners trying to get a newline into their pattern. Advanced regular expressions add
backslash sequences for tab, newline, character classes, and more. This is a convenient improvement, but in rare cases it may change the
semantics of a pattern. Usually these cases are where an unneeded backslash suddenly takes on meaning, or causes an error because it is
unknown.
Matching Precedence
If a pattern can match several parts of a string, the matcher takes the match that occurs earliest in the input string. Then, if there is more than
one match from that same point because of alternation in the pattern, the matcher takes the longest possible match. The rule of thumb is:
first, then longest. This rule gets changed by nongreedy quantifiers that prefer a shorter match.
Watch out for *, which means zero or more, because zero of anything is pretty easy to match. Suppose your pattern is:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[a-z]*
This pattern will match against 123abc, but not how you expect. Instead of matching on the letters in the string, the pattern will match on the
zero-length substring at the very beginning of the input string! This behavior can be seen by using the -indices option of the regexp command
described on page 158. This option tells you the location of the matching string instead of the value of the matching string.
Capturing Subpatterns
Use parentheses to capture a subpattern. The string that matches the pattern within parentheses is remembered in a matching variable,
which is a Tcl variable that gets assigned the string that matches the pattern. Using parentheses to capture subpatterns is very useful.
Suppose we want to get everything between the <td> and </td> tags in some HTML. You can use this pattern:
<td>([^<]*)</td>
The matching variable gets assigned the part of the input string that matches the pattern inside the parentheses. You can capture many
subpatterns in one match, which makes it a very efficient way to pick apart your data. Matching variables are explained in more detail on page
158 in the context of the regexp command.
Sometimes you need to introduce parentheses but you do not care about the match that occurs inside them. The pattern is slightly more
efficient if the matcher does not need to remember the match. Advanced regular expressions add noncapturing parentheses with this syntax:
(?:pattern)
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Advanced Regular Expressions
The syntax added by advanced regular expressions is mostly just shorthand notation for constructs you can make with the basic syntax
already described. There are also some new features that add additional power: nongreedy quantifiers, back references, look-ahead patterns,
and named character classes. If you are just starting out with regular expressions, you can ignore most of this section, except for the one
about backslash sequences. Once you master the basics, of if you are already familiar with regular expressions in Tcl (or the UNIX vi editor
or grep utility), then you may be interested in the new features of advanced regular expressions.
Compatibility with Patterns in Tcl 8.0
Advanced regular expressions add syntax in an upward compatible way. Old patterns continue to work with the new matcher, but advanced
regular expressions will raise errors if given to old versions of Tcl. For example, the question mark is used in many of the new constructs, and
it is artfully placed in locations that would not be legal in older versions of regular expressions. The added syntax is summarized in Table 11-2
on page 155.
If you have unbraced patterns from older code, they are very likely to be correct in Tcl 8.1 and later versions. For example, the following
pattern picks out everything up to the next newline. The pattern is unbraced, so Tcl substitutes the newline character for each occurrence of
\n. The square brackets are quoted so that Tcl does not think they delimit a nested command:
regexp "(\[^\n\]+)\n" $input
The above command behaves identically when using advanced regular expressions, although you can now also write it like this:
regexp {([^\n]+)\n} $input
The curly braces hide the brackets from the Tcl parser, so they do not need to be escaped with backslash. This saves us two characters and
looks a bit cleaner.
Backslash Escape Sequences
The most significant change in advanced regular expression syntax is backslash substitutions. In Tcl 8.0 and earlier, a backslash is only used
to turn off special characters such as: . + * ? [ ]. Otherwise it was ignored. For example,\n was simply n to the Tcl 8.0 regular expression
engine. This was a source of confusion, and it meant you could not always quote patterns in braces to hide their special characters from Tcl's
parser. In advanced regular expressions, \n now means the newline character to the regular expression engine, so you should never need to
let Tcl do backslash processing.
Again, always group your pattern with curly braces to avoid confusion.
Advanced regular expressions add a lot of new backslash sequences. They are listed in Table 11-4 on page 156. Some of the more useful
ones include \s, which matches space-like characters, \w, which matches letters, digit, and the underscore,\y, which matches the beginning or
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
end of a word, and \B, which matches a backslash.
Character Classes
Character classes are names for sets of characters. The named character class syntax is valid only inside a bracketed character set. The
syntax is:
[:identifier:]
For example, alpha is the name for the set of uppercase and lowercase letters. The following two patterns arealmost the same:
[A-Za-z]
[[:alpha:]]
The difference is that the alpha character class also includes accented characters like è. If you match data that contains nonASCII characters,
the named character classes are more general than trying to name the characters explicitly.
There are also backslash sequences that are shorthand for some of the named character classes. The following patterns to match digits are
equivalent:
[0-9]
[[:digit:]]
\d
The following patterns match space-like characters including backspace, form feed, newline, carriage return, tag, and vertical tab:
[ \b\f\n\r\t\v]
[[:space:]]
\s
The named character classes and the associated backslash sequence are listed in Table 11-3 on page 156.
You can use character classes in combination with other characters or character classes inside a character set definition. The following
patterns match letters, digits, and underscore:
[[:digit:][:alpha:]_]
[\d[:alpha:]_]
[[:alnum:]_]
\w
Note that \d, \s and \w can be used either inside or outside character sets. When used outside a bracketed expression, they form their own
character set. There are also \D, \S, and \W, which are the complement of\d, \s, and \w. These escapes (i.e., \D for not-a-digit) cannot be used
inside a bracketed character set.
There are two special character classes, [[:<:] and [[:>:]], that match the beginning and end of a word, respectively. A word is defined as one
or more characters that match \w.
Nongreedy Quantifiers
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The *, +, and ? characters are quantifiers that specify repetition. By default these match as many characters as possible, which is called
greedy
matching. A nongreedy match will match as few characters as possible. You can specify nongreedy matching by putting a question mark after
these quantifiers. Consider the pattern to match "one or more of not-a-newline followed by a newline." The not-a-newline must be explicit with
the greedy quantifier, as in:
[^\n]+\n
Otherwise, if the pattern were just
.+\n
then the "." could well match newlines, so the pattern would greedily consume everything until the very last newline in the input. A nongreedy
match would be satisfied with the very first newline instead:
.+?\n
By using the nongreedy quantifier we've cut the pattern from eight characters to five. Another example that is shorter with a nongreedy
quantifier is the HTML example from page 148. The following pattern also matches everything between <td> and </td>:
<td>(.*?)</td>
Even ? can be made nongreedy, ??, which means it prefers to match zero instead of one. This only makes sense inside the context of a larger
pattern. Send me email if you have a compelling example for it!
Bound Quantifiers
The {m,n} syntax is a quantifier that means match at leastm and at most n of the previous matching item. There are two variations on this
syntax. A simple {m} means match exactly m of the previous matching item. A{m,} means match m or more of the previous matching item. All
of these can be made nongreedy by adding a ? after them.
Back References
A back reference is a feature you cannot easily get with basic regular expressions. A back reference matches the value of a subpattern
captured with parentheses. If you have several sets of parentheses you can refer back to different captured expressions with \1, \2, and so
on. You count by left parentheses to determine the reference.
For example, suppose you want to match a quoted string, where you can use either single or double quotes. You need to use an alternation
of two patterns to match strings that are enclosed in double quotes or in single quotes:
("[^"]*"|'[^']*')
With a back reference, \1, the pattern becomes simpler:
('|").*?\1
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than
The first set of parenthesis matches the leading quote, and then the \1 refers back to that particular quote character. The nongreedy
quantifier ensures that the pattern matches up to the first occurrence of the matching quote.
Look-ahead
Look-ahead patterns are subexpressions that are matched but do not consume any of the input. They act like constraints on the rest of the
pattern, and they typically occur at the end of your pattern. A positive look-ahead causes the pattern to match if it also matches. A negative
look-ahead causes the pattern to match if it would not match. These constraints make more sense in the context of matching variables and in
regular expression substitutions done with the regsub command. For example, the following pattern matches a filename that begins withA and
ends with .txt
^A.*\.txt$
The next version of the pattern adds parentheses to group the file name suffix.
^A.*(\.txt$)
The parentheses are not strictly necessary, but they are introduced so that we can compare the pattern to one that uses look-ahead. A
version of the pattern that uses look-ahead looks like this:
^A.*(?=\.txt$)
The pattern with the look-ahead constraint matches only the part of the filename before the .txt, but only if the .txt is present. In other words,
the .txt is not consumed by the match. This is visible in the value of the matching variables used with the
regexp command. It would also affect
the substitutions done in the regsub command.
There is negative look-ahead too. The following pattern matches a filename that begins with A and does not end with.txt.
^A.*(?!\.txt$)
Writing this pattern without negative look-ahead is awkward.
Character Codes
The \nn and \mmm syntax, where n and m are digits, can also mean an 8-bit character code corresponding to the octal valuenn or mmm. This
has priority over a back reference. However, I just wouldn't use this notation for character codes. Instead, use the Unicode escape sequence,
\unnnn, which specifies a 16-bit value. The\xnn sequence also specifies an 8-bit character code. Unfortunately, the\x escape consumes all
hex digits after it (not just two!) and then truncates the hexadecimal value down to 8 bits. This misfeature of \x is not considered a bug and will
probably not change even in future versions of Tcl.
The \Uyyyyyyyy syntax is reserved for 32-bit Unicode, but I don't expect to see that implemented anytime soon.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Collating Elements
Collating elements are characters or long names for characters that you can use inside character sets. Currently, Tcl only has some long
names for various ASCII punctuation characters. Potentially, it could support names for every Unicode character, but it doesn't because the
mapping tables would be huge. This section will briefly mention the syntax so that you can understand it if you see it. But its usefulness is still
limited.
Within a bracketed expression, the following syntax is used to specify a collating element:
[.identifier.]
The identifier can be a character or a long name. The supported long names can be found in the generic/regc_locale.c file in the Tcl source
code distribution. A few examples are shown below:
[.c.]
[.#.]
[.number-sign.]
Equivalence Classes
An equivalence class is all characters that sort to the same position. This is another feature that has limited usefulness in the current version
of Tcl. In Tcl, characters sort by their Unicode character value, so there are no equivalence classes that contain more than one character!
However, you could imagine a character class for 'o', 'ò', and other accented versions of the letter o. The syntax for equivalence classes
within bracketed expressions is:
[=char=]
where char is any one of the characters in the character class. This syntax is valid only inside a character class definition.
Newline Sensitive Matching
By default, the newline character is just an ordinary character to the matching engine. You can make the newline character special with two
options: lineanchor and linestop. You can set these options with flags to theregexp and regsub Tcl commands, or you can use the embedded
options described later in Table 11-5 on page 157.
The lineanchor option makes the ^ and $ anchors work relative to newlines. The ^ matches immediately after a newline, and$ matches
immediately before a newline. These anchors continue to match the very beginning and end of the input, too. With or without the lineanchor
option, you can use \A and \Z to match the beginning and end of the string.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
The linestop option prevents . (i.e., period) and character sets that begin with^ from matching a newline character. In other words, unless you
explicitly include \n in your pattern, it will not match across newlines.
Embedded Options
You can start a pattern with embedded options to turn on or off case sensitivity, newline sensitivity, and expanded syntax, which is explained
in the next section. You can also switch from advanced regular expressions to a literal string, or to older forms of regular expressions. The
syntax is a leading:
(?chars)
where chars is any number of option characters. The option characters are listed inTable 11-5 on page 157.
Expanded Syntax
Expanded syntax lets you include comments and extra white space in your patterns. This can greatly improve the readability of complex
patterns. Expanded syntax is turned on with a regexp command option or an embedded option.
Comments start with a # and run until the end of line. Extra white space and comments can occur anywhere except inside bracketed
expressions (i.e., character sets) or within multicharacter syntax elements like (?=. When you are in expanded mode, you can turn off the
comment character or include an explicit space by preceding them with a backslash. Example 11-1 shows a pattern to match URLs. The
leading (?x) turns on expanded syntax. The whole pattern is grouped in curly braces to hide it from Tcl. This example is considered again in
more detail in Example 11-3 on page 159:
Example 11-1 Expanded regular expressions allow comments
regexp {(?x)
# A pattern to match URLS
([^:]+): # The protocol before the initial colon
//([^:/]+) # The server name
(:([0-9]+))? # The optional port number
(/.*)
# The trailing pathname
} $input
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Syntax Summary
Table 11-1 summarizes the syntax of regular expressions available in all versions of Tcl:
Table 11-1. Basic regular expression syntax
.
Matches any character.
*
Matches zero or more instances of the previous pattern item.
+
Matches one or more instances of the previous pattern item.
?
Matches zero or one instances of the previous pattern item.
()
Groups a subpattern. The repetition and alternation operators apply to the preceding subpattern.
|
Alternation.
[]
Delimit a set of characters. Ranges are specified as [x-y]. If the first character in the set is^, then there is a match if the remaining
characters in the set are not present.
^
Anchor the pattern to the beginning of the string. Only when first.
$
Anchor the pattern to the end of the string. Only when last.
Advanced regular expressions, which were introduced in Tcl 8.1, add more syntax that is summarized in Table 11-2:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 11-2. Additional advanced regular expression syntax
{m}
Matches m instances of the previous pattern item.
{m}?
Matches m instances of the previous pattern item. Nongreedy.
{m,}
Matches m or more instances of the previous pattern item.
{m,}?
Matches m or more instances of the previous pattern item. Nongreedy.
{m,n}
Matches m through n instances of the previous pattern item.
{m,n}?
Matches m through n instances of the previous pattern item. Nongreedy.
*?
Matches zero or more instances of the previous pattern item. Nongreedy.
+?
Matches one or more instances of the previous pattern item. Nongreedy.
??
Matches zero or one instances of the previous pattern item. Nongreedy.
(?:re)
Groups a subpattern, re, but does not capture the result.
(?=re)
Positive look-ahead. Matches the point where re begins.
(?!re)
Negative look-ahead. Matches the point wherere does not begin.
(?abc)
Embedded options, where abc is any number of option letters listed in Table 11-5.
\c
One of many backslash escapes listed inTable 11-4.
[: :]
Delimits a character class within a bracketed expression. SeeTable 11-3.
[. .]
Delimits a collating element within a bracketed expression.
[= =]
Delimits an equivalence class within a bracketed expression.
Table 11-3 lists the named character classes defined in advanced regular expressions and their associated backslash sequences, if any.
Character class names are valid inside bracketed character sets with the [:class:] syntax.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 11-3. Character classes
alnum
Upper and lower case letters and digits.
alpha
Upper and lower case letters.
blank
Space and tab.
cntrl
Control characters: \u0001 through \u001F.
digit
The digits zero through nine. Also\d.
graph
Printing characters that are not incntrl or space.
lower
Lowercase letters.
print
The same as alnum.
punct
Punctuation characters.
space
Space, newline, carriage return, tab, vertical tab, form feed. Also\s.
upper
Uppercase letters.
xdigit
Hexadecimal digits: zero through nine, a-f, A-F.
Table 11-4 lists backslash sequences supported in Tcl 8.1.
Table 11-4. Backslash escapes in regular expressions
\a
Alert, or "bell", character.
\A
Matches only at the beginning of the string.
\b
Backspace character, \u0008.
\B
Synonym for backslash.
\cX
Control-X.
\d
Digits. Same as [[:digit:]]
\D
Not a digit. Same as[^[:digit:]]
\e
Escape character, \u001B.
\f
Form feed, \u000C.
\m
Matches the beginning of a word.
\M
Matches the end of a word.
\n
Newline, \u000A.
\r
Carriage return, \u000D.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
\s
Space. Same as [[:space:]]
\S
Not a space. Same as[^[:space:]]
\t
Horizontal tab, \u0009.
\uXXXX
A 16-bit Unicode character code.
\v
Vertical tab, \u000B.
\w
Letters, digit, and underscore. Same as [[:alnum:]_]
\W
Not a letter, digit, or underscore. Same as[^[:alnum:]_]
\xhh
An 8-bit hexadecimal character code. Consumes all hex digits after\x.
\y
Matches the beginning or end of a word.
\Y
Matches a point that is not the beginning or end of a word.
\Z
Matches the end of the string.
\0
NULL, \u0000
\x
Where x is a digit, this is a back-reference.
\xy
Where x and y are digits, either a decimal back-reference, or an 8-bit octal character code.
\xyz
Where x, y and z are digits, either a decimal back-reference or an 8-bit octal character code.
Table 11-5 lists the embedded option characters used with the(?abc) syntax.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Table 11-5. Embedded option characters used with the (?x) syntax
b
The rest of the pattern is a basic regular expression (a lavi or grep).
c
Case sensitive matching. This is the default.
e
The rest of the pattern is an extended regular expression (a la Tcl 8.0).
i
Case insensitive matching.
m
Synonym for the n option.
n
Newline sensitive matching . Bothlineanchor and linestop mode.
p
Partial newline sensitive matching. Only linestop mode.
q
The rest of the pattern is a literal string.
s
No newline sensitivity. This is the default.
t
Tight syntax; no embedded comments. This is the default.
w
Inverse partial newline-sensitive matching. Only lineanchor mode.
x
Expanded syntax with embedded white space and comments.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The regexp Command
The regexp command provides direct access to the regular expression matcher. Not only does it tell you whether a string matches a pattern, it
can also extract one or more matching substrings. The return value is 1 if some part of the string matches the pattern; it is 0 otherwise. Its
syntax is:
regexp ?flags? pattern string ?match sub1 sub2...?
The flags are described in Table 11-6:
Table 11-6. Options to the regexp command
-nocase
Lowercase characters in pattern can match either lowercase or uppercase letters instring.
-indices
The match variables each contain a pair of numbers that are in indices delimiting the match within string. Otherwise, the
matching string itself is copied into the match variables.
-expanded
The pattern uses the expanded syntax discussed on page 154.
-line
The same as specifying both-lineanchor and -linestop.
-lineanchor
Change the behavior of ^ and $ so they are line-oriented as discussed on page 153.
-linestop
Change matching so that. and character classes do not match newlines as discussed on page 153.
-about
Useful for debugging. It returns information about the pattern instead of trying to match it against the input.
--
Signals the end of the options. You must use this if your pattern begins with-.
The pattern argument is a regular expression as described earlier. Ifstring matches pattern, then regexp stores the results of the match in the
variables provided. These match variables are optional. If present, match is set to the part of the string that matched the pattern. The
remaining variables are set to the substrings of string that matched the corresponding subpatterns in pattern. The correspondence is based
on the order of left parentheses in the pattern to avoid ambiguities that can arise from nested subpatterns.
Example 11-2 uses regexp to pick the hostname out of the DISPLAY environment variable, which has the form:
hostname:display.screen
Example 11-2 Using regular expressions to parse a string
set env(DISPLAY) sage:0.1
regexp {([^:]*):} $env(DISPLAY) match host
=> 1
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set match
=> sage:
set host
=> sage
The pattern involves a complementary set, [^:], to match anything except a colon. It uses repetition,*, to repeat that zero or more times. It
groups that part into a subexpression with parentheses. The literal colon ensures that the DISPLAY value matches the format we expect. The
part of the string that matches the complete pattern is stored into the match variable. The part that matches the subpattern is stored intohost.
The whole pattern has been grouped with braces to quote the square brackets. Without braces it would be:
regexp (\[^:\]*): $env(DISPLAY) match host
With advanced regular expressions the nongreedy quantifier *? can replace the complementary set:
regexp (.*?): $env(DISPLAY) match host
This is quite a powerful statement, and it is efficient. If we had only had the string command to work with, we would have needed to resort to
the following, which takes roughly twice as long to interpret:
set i [string first : $env(DISPLAY)]
if {$i >= 0} {
set host [string range $env(DISPLAY) 0 [expr $i-1]]
}
A Pattern to Match URLs
Example 11-3 demonstrates a pattern with several subpatterns that extract the different parts of a URL. There are lots of subpatterns, and you
can determine which match variable is associated with which subpattern by counting the left parenthesis. The pattern will be discussed in
more detail after the example:
Example 11-3 A pattern to match URLs
set url http://www.beedub.com:80/index.html
regexp {([^:]+)://([^:/]+)(:([0-9]+))?(/.*)} $url \
match protocol server x port path
=> 1
set match
=> http://www.beedub.com:80/index.html
set protocol
=> http
set server
=> www.beedub.com
set x
=> :80
set port
=> 80
set path
=> /index.html
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Let's look at the pattern one piece at a time. The first part looks for the protocol, which is separated by a colon from the rest of the URL. The
first part of the pattern is one or more characters that are not a colon, followed by a colon. This matches the http: part of the URL:
[^:]+:
Using nongreedy +? quantifier, you could also write that as:
.+?:
The next part of the pattern looks for the server name, which comes after two slashes. The server name is followed either by a colon and a
port number, or by a slash. The pattern uses a complementary set that specifies one or more characters that are not a colon or a slash. This
matches the //www.beedub.com part of the URL:
//[^:/]+
The port number is optional, so a subpattern is delimited with parentheses and followed by a question mark. An additional set of parentheses
are added to capture the port number without the leading colon. This matches the :80 part of the URL:
(:([0-9]+))?
The last part of the pattern is everything else, starting with a slash. This matches the /index.html part of the URL:
/.*
Use subpatterns to parse strings.
To make this pattern really useful, we delimit several subpatterns with parentheses:
([^:]+)://([^:/]+)(:([0-9]+))?(/.*)
These parentheses do not change the way the pattern matches. Only the optional port number really needs the parentheses in this example.
However, the regexp command gives us access to the strings that match these subpatterns. In one stepregexp can test for a valid URL and
divide it into the protocol part, the server, the port, and the trailing path.
The parentheses around the port number include the : before the digits. We've used a dummy variable that gets the : and the port number,
and another match variable that just gets the port number. By using noncapturing parentheses in advanced regular expressions, we can
eliminate the unused match variable. We can also replace both complementary character sets with a nongreedy .+? match. Example 11-4
shows this variation:
Example 11-4 An advanced regular expression to match URLs
set url http://www.beedub.com:80/book/
regexp {(.+?)://(.+?)(?::([0-9]+))?(/.*)$} $url \
match protocol server port path
=> 1
set match
=> http://www.beedub.com:80/book/
set protocol
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
=> http
set server
=> www.beedub.com
set port
=> 80
set path
=> /book/
Bugs When Mixing Greedy and Non-Greedy Quantifiers
If you have a regular expression pattern that uses both greedy and non-greedy quantifiers, then you can quickly run into trouble. The problem
is that in complex cases there can be ambiguous ways to resolve the quantifiers. Unfortunately, what happens in practice is that Tcl tends to
make all the quantifiers either greedy, or all of them non-greedy. Example 11-4 has a $ at the end to force the last greedy term to go to the
end of the string. In theory, the greediness of the last subpattern should match all the characters out to the end of the string. In practice, Tcl
makes all the quantifiers non-greedy, so the anchor is necessary to force the pattern to match to the end of the string.
Sample Regular Expressions
The table in this section lists regular expressions as you would use them in Tcl commands. Most are quoted with curly braces to turn off the
special meaning of square brackets and dollar signs. Other patterns are grouped with double quotes and use backslash quoting because the
patterns include backslash sequences like \n and \t. In Tcl 8.0 and earlier, these must be substituted by Tcl before theregexp command is
called. In these cases, the equivalent advanced regular expression is also shown.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 11-7. Sample regular expressions
{^[yY]}
Begins with y or Y, as in a Yes answer.
{^(yes|YES|Yes)$}
Exactly "yes", "Yes", or "YES".
{^[^ \t:\]+:}
Begins with colon-delimited field that has no spaces or tabs.
{^\S+?:}
Same as above, using \S for "not space".
"^\[ \t]*$"
A string of all spaces or tabs.
{(?n)^\s*$}
A blank line using newline sensitive mode.
"(\n|^)\[^\n\]*(\n|$)"
A blank line, the hard way.
{^[A-Za-z]+$}
Only letters.
{^[[:alpha:]]+$}
Only letters, the Unicode way.
{[A-Za-z0-9_]+}
Letters, digits, and the underscore.
{\w+}
Letters, digits, and the underscore using \w.
{[][${}\\]}
The set of Tcl special characters: ] [ $ { } \
"\[^\n\]*\n"
Everything up to a newline.
{.*?\n}
Everything up to a newline using nongreedy *?
{\.}
A period.
{[][$^?+*()|\\]}
The set of regular expression special characters:
][$^?+*()|\
<H1>(.*?)</H1>
An H1 HTML tag. The subpattern matches the string between the tags.
<!--.*?-->
HTML comments.
{[0-9a-hA-H][0-9a-hA-H]}
2 hex digits.
{[[:xdigit:]]{2}}
2 hex digits, using advanced regular expressions.
{\d{1,3}}
1 to 3 digits, using advanced regular expressions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
The regsub Command
The regsub command does string substitution based on pattern matching. It is very useful for processing your data. It can perform simple
tasks like replacing sequences of spaces and tabs with a single space. It can perform complex data transforms, too, as described in the next
section. Its syntax is:
regsub ?switches? pattern string subspec varname
The regsub command returns the number of matches and replacements, or 0 if there was no match.regsub copies string to varname, replacing
occurrences of pattern with the substitution specified bysubspec. If the pattern does not match, thenstring is copied to varname without
modification. The optional switches include:
-all, which means to replace all occurrences of the pattern. Otherwise, only the first occurrence is replaced.
The -nocase, -expanded, -line, -linestop, and -lineanchor switches are the same as inthe regexp command. They are described on
page 158.
The -- switch separates the pattern from the switches, which is necessary if your pattern begins with a
-.
The replacement pattern, subspec, can contain literal characters as well as the following special sequences:
& is replaced with the string that matched the pattern.
\x , where x is a number, is replaced with the string that matched the corresponding subpattern inpattern. The correspondence is
based on the order of left parentheses in the pattern specification.
The following replaces a user's home directory with a ~:
regsub ^$env(HOME)/ $pathname ~/ newpath
The following constructs a C compile command line given a filename:
set file tclIO.c
regsub {([^\.]*)\.c$} $file {cc -c & -o \1.o} ccCmd
The matching pattern captures everything before the trailing .c in the file name. The & is replaced with the complete match, tclIO.c, and \1 is
replaced with tclIO, which matches the pattern between the parentheses. The value assigned toccCmd is:
cc -c tclIO.c -o tclIO.o
We could execute that with:
eval exec $ccCmd
The following replaces sequences of multiple space characters with a single space:
regsub -all {\s+} $string " " string
It is perfectly safe to specify the same variable as the input value and the result. Even if there is no match on the pattern, the input string is
copied into the output variable.
The regsub command can count things for us. The following command counts the newlines in some text. In this case the substitution is not
important:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set numLines [regsub -all \n $text {} ignore]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Transforming Data to Program with regsub
One of the most powerful combinations of Tcl commands is regsub and subst. This section describes a few examples that useregsub to
transform data into Tcl commands, and then use subst to replace those commands with a new version of the data. This technique is very
efficient because it relies on two subsystems that are written in highly optimized C code: the regular expression engine and the Tcl parser.
These examples are primarily written by Stephen Uhler.
URL Decoding
When a URL is transmitted over the network, it is encoded by replacing special characters with a %xx sequence, where xx is the hexadecimal
code for the character. In addition, spaces are replaced with a plus (+). It would be tedious and very inefficient to scan a URL one character at
a time with Tcl statements to undo this encoding. It would be more efficient to do this with a custom C program, but still very tedious. Instead,
a combination of regsub and subst can efficiently decode the URL in just a few Tcl commands.
Replacing the + with spaces requires quoting the + because it is the one-or-more special character in regular expressions:
regsub -all {\+} $url { } url
The %xx are replaced with aformat command that will generate the right character:
regsub -all {%([0-9a-hA-H][0-9a-hA-H])} $url \
{[format %c 0x\1]} url
The %c directive to format tells it to generate the character from a character code number. We force a hexadecimal interpretation with a
leading 0x. Advanced regular expressions let us write the "2 hex digits" pattern a bit more cleanly:
regsub -all {%([[:xdigit:]]{2})} $url \
{[format %c 0x\1]} url
The resulting string is passed to subst to get the format commands substituted:
set url [subst $url]
For example, if the input is %7ewelch, the result of the regsub is:
[format %c 0x7e]welch
And then subst generates:
~welch
Example 11-5 encapsulates this trick in the Url_Decode procedure.
Example 11-5 The Url_Decode procedure
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
proc Url_Decode {url} {
regsub -all {\+} $url { } url
regsub -all {%([:xdigit:]]{2})} $url \
{[format %c 0x\1]} url
return [subst $url]
}
CGI Argument Parsing
Example 11-6 builds upon Url_Decode to decode the inputs to a CGI program that processes data from an HTML form. Each form element is
identified by a name, and the value is URL encoded. All the names and encoded values are passed to the CGI program in the following
format:
name1=value1&name2=value2&name3=value3
Example 11-6 shows Cgi_List and Cgi_Query. Cgi_Query receives the form data from the standard input or theQUERY_STRING environment
variable, depending on whether the form data is transmitted with a POST or GET request. These HTTP operations are described in detail in
Chapter 17. Cgi_List uses split to get back a list of names and values, and then it decodes them withUrl_Decode. It returns a Tcl-friendly
name, value list that you can either iterate through with a foreach command, or assign to an array witharray set:
Example 11-6 The Cgi_List and Cgi_Query procedures
proc Cgi_List {} {
set query [Cgi_Query]
regsub -all {\+} $query { } query
set result {}
foreach {x} [split $query &=] {
lappend result [Url_Decode $x]
}
return $result
}
proc Cgi_Query {} {
global env
if {![info exists env(QUERY_STRING)] ||
[string length $env(QUERY_STRING)] == 0} {
if {[info exists env(CONTENT_LENGTH)] &&
[string length $env(CONTENT_LENGTH)] != 0} {
set query [read stdin $env(CONTENT_LENGTH)]
} else {
gets stdin query
}
set env(QUERY_STRING) $query
set env(CONTENT_LENGTH) 0
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
return $env(QUERY_STRING)
}
An HTML form can have several form elements with the same name, and this can result in more than one value for each name. If you blindly
use array set to map the results of Cgi_List into an array, you will lose the repeated values.Example 11-7 shows Cgi_Parse and Cgi_Value that
store the query data in a global cgi array. Cgi_Parse adds list structure whenever it finds a repeated form value. The globalcgilist array keeps a
record of how many times a form value is repeated. The Cgi_Value procedure returns elements of the globalcgi array, or the empty string if
the requested value is not present.
Example 11-7 Cgi_Parse and Cgi_Value store query data in the cgi array
proc Cgi_Parse {} {
global cgi cgilist
catch {unset cgi cgilist}
set query [Cgi_Query]
regsub -all {\+} $query { } query
foreach {name value} [split $query &=] {
set name [CgiDecode $name]
if {[info exists cgilist($name)] &&
($cgilist($name) == 1)} {
# Add second value and create list structure
set cgi($name) [list $cgi($name) \
[Url_Decode $value]]
} elseif {[info exists cgi($name)]} {
# Add additional list elements
lappend cgi($name) [CgiDecode $value]
} else {
# Add first value without list structure
set cgi($name) [CgiDecode $value]
set cgilist($name) 0 ;# May need to listify
}
incr cgilist($name)
}
return [array names cgi]
}
proc Cgi_Value {key} {
global cgi
if {[info exists cgi($key)]} {
return $cgi($key)
} else {
return {}
}
}
proc Cgi_Length {key} {
global cgilist
if {[info exist cgilist($key)]} {
return $cgilist($key)
} else {
return 0
}
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Decoding HTML Entities
The next example is a decoder for HTML entities. In HTML, special characters are encoded as entities. If you want a literal< or > in your
document, you encode them as the entities < and >, respectively, to avoid conflict with the<tag> syntax used in HTML. HTML syntax is
briefly described in Chapter 3 on page 34. Characters with codes above 127 such as copyright © and egrave è are also encoded. There are
named entities, such as < for < and è for è. You can also use decimal-valued entities such as© for ©. Finally, the trailing
semicolon is optional, so &lt or < can both be used to encode<.
The entity decoder is similar to Url_Decode. In this case, however, we need to be more careful withsubst. The text passed to the decoder
could contain special characters like a square bracket or dollar sign. With Url_Decode we can rely on those special characters being encoded
as, for example, %24. Entity encoding is different (do not ask me why URLs and HTML have different encoding standards), and dollar signs
and square brackets are not necessarily encoded. This requires an additional pass to quote these characters. This regsub puts a backslash in
front of all the brackets, dollar signs, and backslashes.
regsub -all {[][$\\]} $text {\\&} new
The decimal encoding (e.g., ©) is also more awkward than the hexadecimal encoding used in URLs. We cannot force a decimal
interpretation of a number in Tcl. In particular, if the entity has a leading zero (e.g., 
) then Tcl interprets the value (e.g.,010) as octal.
The scan command is used to do a decimal interpretation. It scans into a temporary variable, andset is used to get that value:
regsub -all {&#([0-9][0-9]?[0-9]?);?} $new \
{[format %c [scan \1 %d tmp; set tmp]]} new
With advanced regular expressions, this could be written as follows using bound quantifiers to specify one to three digits:
regsub -all {&#(\d{1,3});?} $new \
{[format %c [scan \1 %d tmp;set tmp]]} new
The named entities are converted with an array that maps from the entity names to the special character. The only detail is that unknown
entity names (e.g., &foobar;) are not converted. This mapping is done inside HtmlMapEntity, which guards against invalid entities.
regsub -all {&([a-zA-Z]+)(;?)} $new \
{[HtmlMapEntity \1 \\\2 ]} new
If the input text contained:
[x < y]
then the regsub would transform this into:
\[x [HtmlMapEntity lt \; ] y\]
Finally, subst will result in:
[x < y]
Example 11-8 Html_DecodeEntity
proc Html_DecodeEntity {text} {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
if {![regexp & $text]} {return $text}
regsub -all {[][$\\]} $text {\\&} new
regsub -all {&#([0-9][0-9]?[0-9]?);?} $new {\
[format %c [scan \1 %d tmp;set tmp]]} new
regsub -all {&([a-zA-Z]+)(;?)} $new \
{[HtmlMapEntity \1 \\\2 ]} new
return [subst $new]
}
proc HtmlMapEntity {text {semi {}}} {
global htmlEntityMap
if {[info exist htmlEntityMap($text)]} {
return $htmlEntityMap($text)
} else {
return $text$semi
}
}
# Some of the htmlEntityMap
array set htmlEntityMap {
lt < gt > amp&
aring \xe5 atilde \xe3
copy \xa9 ecirc \xea egrave \xe8
}
A Simple HTML Parser
The following example is the brainchild of Stephen Uhler. It uses regsub to transform HTML into a Tcl script. When it is evaluated the script
calls a procedure to handle each tag in an HTML document. This provides a general framework for processing HTML. Different callback
procedures can be applied to the tags to achieve different effects. For example, the html_library-0.3 package on the CD-ROM uses
Html_Parse to display HTML in a Tk text widget.
Example 11-9 Html_Parse
proc Html_Parse {html cmd {start {}}} {
# Map braces and backslashes into HTML entities
regsub -all \{ $html {\&ob;} html
regsub -all \} $html {\&cb;} html
regsub -all {\\} $html {\&bsl;} html
# This pattern matches the parts of an HTML tag
set s" \t\r\n" ;# white space
set exp <(/?)(\[^$s>]+)\[$s]*(\[^>]*)>
# This generates a call to cmd with HTML tag parts
# \1 is the leading /, if any
# \2 is the HTML tag name
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
# \3 is the parameters to the tag, if any
# The curly braces at either end group of all the text
# after the HTML tag, which becomes the last arg to $cmd.
set sub "\}\n$cmd {\\2} {\\1} {\\3} \{"
regsub -all $exp $html $sub html
# This balances the curly braces,
# and calls $cmd with $start as a pseudo-tag
# at the beginning and end of the script.
eval "$cmd {$start} {} {} {$html}"
eval "$cmd {$start} / {} {}"
}
The main regsub pattern can be written more simply with advanced regular expressions:
set exp {<(/?)(\S+?)\s*(.*?)>}
An example will help visualize the transformation. Given this HTML:
<Title>My Home Page</Title>
<Body bgcolor=white text=black>
<H1>My Home</H1>
This is my <b>home</b> page.
and a call to Html_Parse that looks like this:
Html_Parse $html {Render .text} hmstart
then the generated program is this:
Render .text {hmstart} {} {} {}
Render .text {Title} {} {} {My Home Page}
Render .text {Title} {/} {} {
}
Render .text {Body} {} {bgcolor=white text=black} {
}
Render .text {H1} {} {} {My Home}
Render .text {H1} {/} {} {
This is my }
Render .text {b} {} {} {home}
Render .text {b} {/} {} { page.
}
Render .text {hmstart} / {} {}
One overall point to make about this example is the difference between using eval and subst with the generated script. The decoders shown
in Examples 11-5 and 11-8 use subst to selectively replace encoded characters while ignoring the rest of the text. InHtml_Parse we must
process all the text. The main trick is to replace the matching text (e.g., the HTML tag) with some Tcl code that ends in an open curly brace
and starts with a close curly brace. This effectively groups all the unmatched text.
When eval is used this way you must do something with any braces and backslashes in the unmatched text. Otherwise, the resulting script
does not parse correctly. In this case, these special characters are encoded as HTML entities. We can afford to do this because the cmd that
is called must deal with encoded entities already. It is not possible to quote these special characters with backslashes because all this text is
inside curly braces, so no backslash substitution is performed. If you try that the backslashes will be seen by the cmd callback.
Finally, I must admit that I am always surprised that this works:
eval "$cmd {$start} {} {} {$html}"
I always forget that $start and $html are substituted in spite of the braces. This is because double quotes are being used to group the
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
argument, so the quoting effect of braces is turned off.
Stripping HTML Comments
The Html_Parse procedure does not correctly handle HTML comments. The problem is that the syntax for HTML commands allows tags
inside comments, so there can be > characters inside the comment. HTML comments are also used to hide Javascript inside pages, which
can also contain >. We can fix this with a pass that eliminates the comments.
The comment syntax is this:
<!-- HTML comment, could contain <markup> -->
Using nongreedy quantifiers, we can strip comments with a singleregsub:
regsub -all <!--.*?--> $html {} html
Using only greedy quantifiers, it is awkward to match the closing --> without getting stuck on embedded > characters, or without matching too
much and going all the way to the end of the last comment. Time for another trick:
regsub -all --> $html \x81 html
This replaces all the end comment sequences with a single character that is not allowed in HTML. Now you can delete the comments like this:
regsub -all "<!--\[^\x81\]*\x81" $html {} html
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Other Commands That Use Regular Expressions
Several Tcl commands use regular expressions.
lsearch takes a -regexp flag so that you can search for list items that match a regular expression. Thelsearch command is
described on page 69.
switch takes a -regexp flag, so you can branch based on a regular expression match instead of an exact match or astring match
style match. The switch command is described on page 77.
The Tk text widget can search its contents based on a regular expression match. Searching in the text widget is described on
page 542.
The Expect Tcl extension can match the output of a program with regular expressions.Expect is the subject of its own book,
Exploring Expect (O'Reilly, 1995) by Don Libes.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 12. Script Libraries and Packages
Collections of Tcl commands are kept in libraries and organized into packages. Tcl automatically loads libraries as an application uses their
commands. Tcl commands discussed are: package, pkg_mkIndex, auto_mkindex, unknown, and tcl_findLibrary.
Libraries group useful sets of Tcl procedures so that they can be used by multiple applications. For example, you could use any of the code
examples that come with this book by creating a script library and then directing your application to check in that library for missing
procedures. One way to structure a large application is to have a short main script and a library of support scripts. The advantage of this
approach is that not all the Tcl code needs to be loaded to start the application. Applications start up quickly, and as new features are
accessed, the code that implements them is loaded automatically.
The Tcl package facility supports version numbers and has a provide/require model of use. Typically, each file in a library provides one
package with a particular version number. Packages also work with shared object libraries that implement Tcl commands in compiled code,
which are described in Chapter 47. A package can be provided by a combination of script files and object files. Applications specify which
packages they require and the libraries are loaded automatically. The package facility is an alternative to the auto loading scheme used in
earlier versions of Tcl. You can use either mechanism, and this chapter describes them both.
If you create a package you may wish to use the namespace facility to avoid conflicts between procedures and global variables used in
different packages. Namespaces are the topic of Chapter 14. Before Tcl 8.0 you had to use your own conventions to avoid conflicts. This
chapter explains a simple coding convention for large Tcl programs. I use this convention in exmh, a mail user interface that has grown from
about 2,000 to over 35,000 lines of Tcl code. A majority of the code has been contributed by the exmh user community. Such growth might
not have been possible without coding conventions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Locating Packages: The auto_path Variable
The package facility assumes that Tcl libraries are kept in well-known directories. The list of well-known directories is kept in the auto_path Tcl
variable. This is initialized by tclsh and wish to include the Tcl script library directory, the Tk script library directory (forwish), and the parent
directory of the Tcl script library directory. For example, on my Macintosh auto_path is a list of these three directories:
Disk:System Folder:Extensions:Tool Command Language:tcl8.4
Disk:System Folder:Extensions:Tool Command Language
Disk:System Folder:Extensions:Tool Command Language:tk8.4
On my Windows 95 machine the auto_path lists these directories:
c:\Program Files\Tcl\lib\Tcl8.4
c:\Program Files\Tcl\lib
c:\Program Files\Tcl\lib\Tk8.4
On my UNIX workstation the auto_path lists these directories:
/usr/local/tcl/lib/tcl8.4
/usr/local/tcl/lib
/usr/local/tcl/lib/tk8.4
The package facility searches these directories and their subdirectories for packages. The easiest way to manage your own packages is to
create a directory at the same level as the Tcl library:
/usr/local/tcl/lib/welchbook
Packages in this location, for example, will be found automatically because the auto_path list includes /usr/local/tcl/lib. You can also add
directories to the auto_path explicitly:
lappend auto_path directory
One trick I often use is to put the directory containing the main script into theauto_path. The following command sets this up:
lappend auto_path [file dirname [info script]]
If your code is split into bin and lib directories, then scripts in thebin directory can add the adjacent lib directory to their auto_path with this
command:
lappend auto_path \
[file join [file dirname [info script]] ../lib]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Using Packages
Each script file in a library declares what package it implements with the package provide command:
package provide name version
The name identifies the package, and theversion has a major.minor format. The convention is that the minor version number can change and
the package implementation will still be compatible. If the package changes in an incompatible way, then the major version number should
change. For example, Chapter 17 defines several procedures that use the HTTP network protocol. These includehttp::geturl, http::wait, and
http::cleanup. The file that contains the procedures starts with this command:
package provide http 2.4
Case is significant in package names. In particular, the package that comes with Tcl is named http — all lowercase.
More than one file can contribute to the same package simply by specifying the same name and version. In addition, different versions of the
same package can be kept in the same directory but in different files.
An application specifies the packages it needs with the package require command:
package require name ?version? ?-exact?
If the version is left off, then the highest available version is loaded. Otherwise the highest version with the same major number is loaded. For
example, if the client requires version 1.1, version 1.2 could be loaded if it exists, but versions 1.0 and 2.0 would not be loaded. You can
restrict the package to a specific version with the -exact flag. If no matching version can be found, then thepackage require command raises
an error.
Loading Packages Automatically
The package require command depends on an index to record which files implement which packages. The index must be maintained by you,
your project librarian, or your system administrator when packages change. The index is created by the pkg_mkIndex command, which puts
the index into a pkgIndex.tcl file in each library directory. The pkg_mkIndex command takes the name of a directory and one or moreglob
patterns that specify files within that directory. File name patterns are described on page 122. The syntax is:
pkg_mkIndex ?options? directory pattern ?pattern ...?
For example:
pkg_mkIndex /usr/local/lib/welchbook *.tcl
pkg_mkIndex -lazy /usr/local/lib/Sybtcl *.so
The pkg_mkIndex command sources or loads all the files matched by the pattern, detects what packages they provide, and computes the
index. You should be aware of this behavior because it works well only for libraries. If the pkg_mkIndex command hangs or starts random
applications, it is because it sourced an application file instead of a library file.
The package index, pkgIndex.tcl, is sourced in response to apackage require command. The index instructs the package loading mechanism
how to define the package. By default, source or load commands are specified so that packages are defined immediately as a side effect of
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
package require. This is called direct loading. However, the original package index system used adeferred loading scheme layered on the
auto_load mechanism and the unknown command hook, which is described on page 178. If you want deferred loading, use the-lazy option to
pkg_mkIndex. The default behavior of pkg_mkIndex switched from -lazy to -direct in Tcl 8.3. The pkg_mkIndex options are summarized in
Table 12-1.
Table 12-1. Options to the pkg_mkIndex command
-direct
Generates an index with source and load commands in it. This results in packages being loaded directly as a result of
package
require. This is the default starting with Tcl 8.3.
-lazy
Generates an index that populates the auto_index array for deferred loading of commands. This behavior was the default
prior to Tcl 8.3.
-load
Dynamically loads packages that match pattern into the slave interpreter used to compute the index. A common reason to
pattern
need this is with the tcbload package needed to load .tbc files compiled with TclPro Compiler.
-verbose
Displays the name of each file processed and any errors that occur.
Packages Implemented in C Code
The files in a library can be either script files that define Tcl procedures or binary files in shared library format that define Tcl commands in
compiled code (i.e., a Dynamic Link Library (DLL)). Chapter 47 describes how to implement Tcl commands in C. There is a C API to the
package facility that you use to declare the package name for your commands. This is shown in Example 47-1 on page 698. Chapter 37 also
describes the Tcl load command that is used instead of source to link in shared libraries. The pkg_mkIndex command also handles shared
libraries:
pkg_mkIndex directory *.tcl *.so *.shlib *.dll
In this example, .so, .shlib, and .dll are file suffixes for shared libraries on UNIX, Macintosh, and Windows systems, respectively. You can
have packages that have some of their commands implemented in C, and some implemented as Tcl procedures. The script files and the
shared library must simply declare that they implement the same package. The pkg_mkIndex procedure will detect this and set up the
auto_index, so some commands are defined by sourcing scripts, and some are defined by loading shared libraries.
If your file servers support more than one machine architecture, such as Solaris and Linux systems, you probably keep the shared library files
in machine-specific directories. In this case the auto_path should also list the machine-specific directory so that the shared libraries there can
be loaded automatically. If your system administrator configured the Tcl installation properly, this should already be set up. If not, or you have
your shared libraries in a nonstandard place, you must append the location to the auto_path variable.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Summary of Package Loading
The basic structure of package loading works like this:
An application does a package require command. If the package is already loaded, the command just returns the version number
of the already loaded package. If is not loaded, the following steps occur.
The package facility checks to see if it knows about the package. If it does, then it runs the Tcl scripts registered with the package
ifneeded command. These commands either load the package or set it up to be loaded automatically when its commands are first
used.
If the package is unknown, the tclPkgUnknown procedure is called to find it. Actually, you can specify what procedure to call to do
the lookup with the package unknown command, but the standard one istclPkgUnknown.
The tclPkgUnknown procedure looks through the auto_path directories and their subdirectories forpkgIndex.tcl files. It sources
those to build an internal database of packages and version information. The pkgIndex.tcl files contain calls to package ifneeded
that specify what to do to define the package. You can use the pkg_mkIndex command to create your pkgIndex.tcl files, or you can
create them by hand.
In the case of deferred package loading, the tclPkgSetup procedure defines the auto_index array to contain the correct source or
load commands to define each command in the package. Automatic loading and theauto_index array are described in more detail
later.
As you can see, there are several levels of processing involved in finding packages. The system is flexible enough that you can change the
way packages are located and how packages are loaded. The -lazy scenario is complicated because it uses the delayed loading of source
code that is described in the next section. Using the -direct flag to pkg_mkIndex simplifies the situation. In any case, it all boils down to three
key steps:
Use pkg_mkIndex to maintain your index files. Decide at this time whether or not to use direct or lazy package loading.
Put the appropriate package require and package provide commands in your code.
Ensure that your library directories, or their parent directories, are listed in theauto_path variable.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The package Command
The package command has several operations that are used primarily by thepkg_mkIndex procedure and the automatic loading facility. These
operations are summarized in Table 12-2.
Table 12-2. The package command
package forget package
Deletes registration information for package.
package ifneeded package
?command?
Queries or sets the command used to set up automatic loading of a package.
package names
Returns the set of registered packages.
package provide package version
Declares that a script file defines commands forpackage with the given version.
package present package
Equivalent to package require, except that no attempt to load the package is made if it is not loaded.
?version? ?-exact?
package require package
Declares that a script uses package. The -exact flag specifies that the exact version must be loaded.
?version? ?-exact?
Otherwise, the highest matching version is loaded.
package unknown ?command?
Queries or sets the command used to locate packages.
package vcompare v1 v2
Compares version v1 and v2. Returns 0 if they are equal, -1 ifv1 is less than v2, or 1 if v1 is greater than
v2.
package versions package
Returns which versions of the package are registered.
package vsatisfies v1 v2
Returns 1 if v1 is greater or equal to v2 and still has the same major version number. Otherwise
returns 0.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Libraries Based on the tclIndex File
You can create libraries without using the package command. The basic idea is that a directory has a library of script files, and an index of the
Tcl commands defined in the library is kept in a tclIndex file. The drawback is that versions are not supported and you may need to adjust the
auto_path to list your library directory. The main advantage of this approach is that this mechanism has been part of Tcl since the earliest
versions. If you currently maintain a library using tclIndex files, it will still work.
You must generate the index that records what procedures are defined in the library. The auto_mkindex procedure creates the index, which is
stored in a file named tclIndex that is kept in the script library directory. (Watch out for the difference in capitalization betweenauto_mk index
and pkg_mkIndex!) Suppose all the examples from this book are in the directory/usr/local/tcl/welchbook. You can make the examples into a
script library by creating the tclIndex file:
auto_mkindex /usr/local/tcl/welchbook *.tcl
You will need to update the tclIndex file if you add procedures or change any of their names. A conservative approach to this is shown in the
next example. It is conservative because it re-creates the index if anything in the library has changed since the tclIndex file was last
generated, whether or not the change added or removed a Tcl procedure.
Example 12-1 Maintaining a tclIndex file
proc Library_UpdateIndex { libdir } {
set index [file join $libdir tclIndex]
if {![file exists $index]} {
set doit 1
} else {
set age [file mtime $index]
set doit 0
# Changes to directory may mean files were deleted
if {[file mtime $libdir] > $age} {
set doit 1
} else {
# Check each file for modification
foreach file [glob [file join $libdir *.tcl]] {
if {[file mtime $file] > $age} {
set doit 1
break
}
}
}
}
if { $doit } {
auto_mkindex $libdir *.tcl
}
}
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The auto_path variable contains a list of directories to search for unknown commands. To continue our example, you can make the
procedures in the book examples available by putting this command at the beginning of your scripts:
lappend auto_path /usr/local/tcl/welchbook
This has no effect if you have not created the tclIndex file. If you want to be extra careful, you can callLibrary_UpdateIndex. This will update
the index if you add new things to the library.
lappend auto_path /usr/local/tcl/welchbook
Library_UpdateIndex /usr/local/tcl/welchbook
This will not work if there is no tclIndex file at all because Tcl won't be able to find the implementation ofLibrary_UpdateIndex. Once the
tclIndex has been created for the first time, then this will ensure that any new procedures added to the library will be installed into
tclIndex. In
practice, if you want this sort of automatic update, it is wise to include something like the Library_UpdateIndex procedure directly into your
application as opposed to loading it from the library it is supposed to be maintaining.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The unknown Command
The unknown command implements automatic loading of Tcl commands. Whenever the Tcl interpreter encounters a command that it does
not know about, it calls the unknown command with the name of the missing command. Theunknown command is implemented in Tcl, so you
are free to provide your own mechanism to handle unknown commands. This chapter describes the behavior of the default implementation of
unknown, which can be found in theinit.tcl file in the Tcl library. The info library command returns the location of the library.
How Auto Loading Works
The unknown command uses an array named auto_index. One element of the array is defined for each procedure that can be automatically
loaded. The auto_index array is initialized by the package mechanism or bytclIndex files. The value of an auto_index element is a command
that defines the procedure. Typical commands are:
source [file join $dir bind_ui.tcl]
load [file join $dir mime.so] Mime
The $dir gets substituted with the name of the directory that contains the library file, so the result is asource or load command that defines the
missing Tcl command. The substitution is done with eval, so you could initializeauto_index with any commands at all. Example 12-2 is a
simplified version of the code that reads the tclIndex file.
Example 12-2 Loading a tclIndex file
# This is a simplified part of the auto_load_index procedure.
# Go through auto_path from back to front.
set i [expr [llength $auto_path]-1]
for {} {$i >= 0} {incr i -1} {
set dir [lindex $auto_path $i]
if [catch {open [file join $dir tclIndex]} f] {
# No index
continue
}
# eval the file as a script. Because eval is
# used instead of source, an extra round of
# substitutions is performed and $dir gets expanded
# The real code checks for errors here.
eval [read $f]
close $f
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Disabling the Library Facility: auto_noload
If you do not want the unknown procedure to try and load procedures, you can set theauto_noload variable to disable the mechanism:
set auto_noload anything
Auto loading is quite fast. I use it regularly on applications both large and small. A large application will start faster if you only need to load the
code necessary to start it up. As you access more features of your application, the code will load automatically. Even a small application
benefits from auto loading because it encourages you to keep commonly used code in procedure libraries.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Interactive Conveniences
The unknown command provides a few other conveniences. These are used only when you are typing commands directly. They are disabled
once execution enters a procedure or if the Tcl shell is not being used interactively. The convenience features are automatic execution of
programs, command history, and command abbreviation. These options are tried, in order, if a command implementation cannot be loaded
from a script library.
Auto Execute
The unknown procedure implements a second feature: automatic execution of external programs. This makes a Tcl shell behave more like
other UNIX shells that are used to execute programs. The search for external programs is done using the standard PATH environment
variable that is used by other shells to find programs. If you want to disable the feature all together, set the auto_noexec variable:
set auto_noexec anything
History
The history facility described in Chapter 13 is implemented by the unknown procedure.
Abbreviations
If you type a unique prefix of a command, unknown recognizes it and executes the matching command for you. This is done after automatic
program execution is attempted and history substitutions are performed.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Tcl Shell Library Environment
Tcl searches for its script library directory when it starts up. In early versions of Tcl you had to compile in the correct location, set a Windows
registry value, or set the TCL_LIBRARY environment variable to the correct location. Recent versions of Tcl use a standard searching
scheme to locate the script library. The search understands the standard installation and build environments for Tcl, and it should eliminate
the need to use the TCL_LIBRARY environment variable. On Windows the search for the library used to depend on registry values, but this
has also been discontinued in favor of a standard search. In summary, "it should just work." However, this section explains how Tcl finds its
script library so that you can troubleshoot problems.
Locating the Tcl Script Library
The default library location is defined when you configure the source distribution, which is explained on page 732. At this time an initial value
for the auto_path variable is defined. (This default value appears intcl_pkgPath, but changing this variable has no effect once Tcl has started.
I just pretend tcl_pkgPath does not exist.) These values are just hints; Tcl may use other directories depending on what it finds in the file
system.
When Tcl starts up, it searches for a directory that contains its init.tcl startup script. You can short-circuit the search by defining the
TCL_LIBRARY environment variable. If this is defined, Tcl uses it only for its script library directory. However, you should not need to define
this with normal installations of Tcl 8.0.5 or later. In my environment I'm often using several different versions of Tcl for various applications
and testing purposes, so setting TCL_LIBRARY is never correct for all possibilities. If I find myself setting this environment variable, I know
something is wrong with my Tcl installations!
The standard search starts with the default value that is compiled into Tcl (e.g., /usr/local/lib/tcl8.4.) After that, the following directories are
examined for an init.tcl file. These example values assume Tcl version 8.4 and patch level 8.4.1:
../lib/tcl8.4
../../lib/tcl8.4
../library
../../tcl8.4.1/library
../../../tcl8.4.1/library
The first two directories correspond to the standard installation directories, while the last three correspond to the standard build environment
for Tcl or Tk. The first directory in the list that contains a valid init.tcl file becomes the Tcl script library. This directory location is saved in the
tcl_library global variable, and it is also returned by theinfo library command.
The primary thing defined by init.tcl is the implementation of the unknown procedure. It also initializes auto_path to contain $tcl_library and the
parent directory of $tcl_library. There may be additional directories added to auto_path depending on the compiled in value oftcl_pkgPath.
tcl_findLibrary
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A generalization of this search is implemented by tcl_findLibrary. This procedure is designed for use by extensions like Tk and [incr Tcl]. Of
course, Tcl cannot use tcl_findLibrary itself because it is defined ininit.tcl!
The tcl_findLibrary procedure searches relative to the location of the main program (e.g.,tclsh or wish) and assumes a standard installation or
a standard build environment. It also supports an override by an environment variable, and it takes care of sourcing an initialization script. The
usage of tcl_findLibrary is:
tcl_findLibrary base version patch script enVar varName
The base is the prefix of the script library directory name. Theversion is the main version number (e.g., "8.0"). The patch is the full patch level
(e.g., "8.0.3"). The script is the initialization script to source from the directory. TheenVar names an environment variable that can be used to
override the default search path. The varName is the name of a variable to set to name of the directory found bytcl_findLibrary. A side effect
of tcl_findLibrary is to source the script from the directory. An example call is:
tcl_findLibrary tk 8.0 8.0.3 tk.tcl TK_LIBRARY tk_library
This call first checks to see whether TK_LIBRARY is defined in the environment. If so, it uses its value. Otherwise, it searches the following
directories for a file named tk.tcl. It sources the script and sets thetk_library variable to the directory containing that file. The search is relative
to the value returned by info nameofexecutable:
../lib/tk8.0
../../lib/tk8.0
../library
../../tk8.0.3/library
../../../tk8.0.3/library
Tk also adds $tk_library to the end of auto_path, so the other script files in that directory are available to the application:
lappend auto_path $tk_library
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Coding Style
If you supply a package, you need to follow some simple coding conventions to make your library easier to use by other programmers. You
can use the namespace facility introduced in Tcl 8.0. You can also use conventions to avoid name conflicts with other library packages and
the main application. This section describes the conventions I developed before namespaces were added to Tcl.
A Module Prefix for Procedure Names
The first convention is to choose an identifying prefix for the procedures in your package. For example, the preferences package in Chapter
45 uses Pref as its prefix. All the procedures provided by the library begin withPref. This convention is extended to distinguish between private
and exported procedures. An exported procedure has an underscore after its prefix, and it is acceptable to call this procedure from the main
application or other library packages. Examples include Pref_Add, Pref_Init, and Pref_Dialog. A private procedure is meant for use only by the
other procedures in the same package. Its name does not have the underscore. Examples include PrefDialogItem and PrefXres.
This naming convention precludes casual names like doit, setup, layout, and so on. Without using namespaces, there is no way to hide
procedure names, so you must maintain the naming convention for all procedures in a package.
A Global Array for State Variables
You should use the same prefix on the global variables used by your package. You can alter the capitalization; just keep the same prefix. I
capitalize procedure names and use lowercase letters for variables. By sticking with the same prefix you identify what variables belong to the
package and you avoid conflict with other packages.
Collect state in a global or namespaced array.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
In general, I try to use a single global or namespaced array for a package (namespaces are discussed in Chapter 14). The array provides a
convenient place to collect a set of related variables, much as a struct is used in C. For example, the preferences package uses the pref array
to hold all its state information. It is also a good idea to keep the use of the array private. It is better coding practice to provide exported
procedures than to let other modules access your data structures directly. This makes it easier to change the implementation of your package
without affecting its clients. When choosing a namespace name, try to make it significant to your application.
If you do need to export a few key variables from your module, use the underscore convention to distinguish exported variables. If you need
more than one global variable, just stick with the prefix convention to avoid conflicts, or provide accessor functions instead.
The Official Tcl Style Guide
John Ousterhout has published two programming style guides, one for C programming known as The Engineering Manual and one for Tcl
scripts known as The Style Guide. These describe details about file structure as well as naming conventions for modules, procedures, and
variables. The Tcl Style Guide conventions use Tcl namespaces to separate packages. Namespaces automatically provide a way to avoid
conflict between procedure names. Namespaces also support collections of variables without having to use arrays for grouping.
You can find these style guides on the CD-ROM and also in ftp://ftp.tcl.tk/pub/tcl/doc. The Engineering Manual is distributed as a compressed
tar file, engManual.tar.Z, that contains sample files as well as the main document.The Style Guide is distributed as styleGuide.ps (or .pdf).
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 13. Reflection and Debugging
This chapter describes commands that give you a view into the interpreter. The history command and a simple debugger are useful during
development and debugging. The info command provides a variety of information about the internal state of the Tcl interpreter. Thetime
command measures the time it takes to execute a command. Tcl commands discussed are: clock, info, history, and time.
Reflection provides feedback to a script about the internal state of the interpreter. This is useful in a variety of cases, from testing to see
whether a variable exists to dumping the state of the interpreter. The info command provides lots of different information about the interpreter.
The clock command returns the time, formats time values, does time calculations, and parses time strings. It is a great tool all by itself. It also
provides high-resolution timer information for precise measurements.
Interactive command history is the third topic of the chapter. The history facility can save you some typing if you spend a lot of time entering
commands interactively.
Debugging is the last topic. The old-fashioned approach of adding puts commands to your code is often quite useful. For tough problems,
however, a real debugger is invaluable. The Tcl Dev Kit toolset from ActiveState include a high quality debugger and static code checker. The
tkinspect program is an inspector that lets you look into the state of a Tk application. It can hook up to any Tk application dynamically, so it
proves quite useful.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
The clock Command
The clock command has facilities for getting the current time, formatting time values, and scanning printed time strings to get an integer time
value. Table 13-1 summarizes the clock command:
Table 13-1. The clock command
clock clicks ?-milliseconds?
A high resolution counter. The precision is milliseconds, if specified (Tcl 8.4), or a
system-dependent value.
clock format value ?-format str?
Formats a clock value according to str. See Table 13-2.
clock scan string ?-base clock? ?-gmt
Parses date string and return seconds value. The clock value determines the date.
boolean?
clock seconds
Returns the current time in seconds.
The following command prints the current time:
clock format [clock seconds]
=> Fri Nov 22 4:09:14 PM PST 2002
The clock seconds command returns the current time, in seconds since a starting epoch. Theclock format command formats an integer value
into a date string. It takes an optional argument that controls the format. The format strings contains % keywords that are replaced with the
year, month, day, date, hours, minutes, and seconds, in various formats. The default string is:
%a %b %d %H:%M:%S %Z %Y
Tables 13-2 summarizes the clock formatting strings:
Table 13-2. clock format keywords
%%
Inserts a %.
%a
Abbreviated weekday name (Mon, Tue, etc.).
%A
Full weekday name (Monday, Tuesday, etc.).
%b
Abbreviated month name (Jan, Feb, etc.).
%B
Full month name.
%c
Locale specific date and time (e.g.,Nov 24 16:00:59 1996).
%C
First two digits of the four-digit year (19 or 20).
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
%d
Day of month (01 – 31).
%D
Date as %m/%d/%y (e.g., 02/19/97).
%e
Day of month (1 – 31), no leading zeros.
%h
Abbreviated month name.
%H
Hour in 24-hour format (00 – 23).
%I
Hour in 12-hour format (01 – 12).
%j
Day of year (001 – 366).
%k
Hour in 24-hour format, without leading zeros (0 - 23).
%l
Hour in 12-hour format, without leading zeros (1 – 12).
%m
Month number (01 – 12).
%M
Minute (00 – 59).
%n
Inserts a newline.
%p
AM/PM indicator.
%r
Time as %I:%M:%S %p (e.g., 02:39:29 PM).
%R
Time as %H:%M (e.g., 14:39).
%s
Seconds since the epoch.
%S
Seconds (00 – 59).
%t
Inserts a tab.
%T
Time as %H:%M:%S (e.g., 14:34:29).
%u
Weekday number (Monday = 1, Sunday = 7).
%U
Week of year (00 – 52) when Sunday starts the week.
%V
Week of year according to ISO-8601 rules (Week 1 contains January 4).
%w
Weekday number (Sunday = 0).
%W
Week of year (00 – 52) when Monday starts the week.
%x
Locale specific date format (e.g., Feb 19 1997).
%X
Locale specific time format (e.g., 20:10:13).
%y
Year without century (00 – 99).
%Y
Year with century (e.g. 1997).
%Z
Time zone name.
The clock clicks command returns the value of the system's highest resolution clock. The units of the clicks is milliseconds if-milliseconds is
specified, otherwise it is undefined. The main use of this command is to measure the relative time of different performance tuning trials. The
-milliseconds flag was added in Tcl 8.4.Example 13-1 shows how to calibrate the clicks value by counting the clicks per second over 10
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
seconds, which will vary from system to system:
Example 13-1 Calculating clicks per second
set t1 [clock clicks]
after 10000 ;# See page 228
set t2 [clock clicks]
puts "[expr ($t2 - $t1)/10] Clicks/second"
=> 1001313 Clicks/second
The clock scan command parses a date string and returns a seconds value. The command handles a variety of date formats. If you leave off
the year, the current year is assumed.
Year 2000 Compliance
Tcl implements the standard interpretation of two-digit year values, which is that 70–99 are 1970–1999, 00–69 are 2000–2069. Versions of
Tcl before 8.0 did not properly deal with two-digit years in all cases. Note, however, that Tcl is limited by your system's time epoch and the
number of bits in an integer. On Windows, Macintosh, and most UNIX systems, the clock epoch is January 1, 1970. A 32-bit integer can count
enough seconds to reach forward into the year 2037, and backward to the year 1903. If you try to clock scan a date outside that range, Tcl
will raise an error because the seconds counter will overflow or underflow. In this case, Tcl is just reflecting limitations of the underlying
system. Some 64-bit systems (such as Solaris 8 64-bit) use 64-bit integers for the system clock, which Tcl 8.4 supports. This extends the
recognized range into the billions of years.
If you leave out a date, clock scan assumes the current date. You can also use the -base option to specify a date. The following example uses
the current time as the base, which is redundant:
clock scan "10:30:44 PM" -base [clock seconds]
=> 2931690644
The date parser allows these modifiers: year, month, fortnight (two weeks), week, day, hour, minute, second. You can put a positive or
negative number in front of a modifier as a multiplier. For example:
clock format [clock scan "10:30:44 PM 1 week"]
=> Fri Nov 29 10:30:44 PM PST 2002
clock format [clock scan "10:30:44 PM -1 week"]
Fri Nov 15 10:30:44 PM PST 2002
You can also use tomorrow, yesterday, today, now, last, this, next, and ago, as modifiers.
clock format [clock scan "3 years ago"]
=> Mon Nov 22 4:18:34 PM PST 1999
Both clock format and clock scan take a -gmt option that uses Greenwich Mean Time. Otherwise, the local time zone is used.
clock format [clock seconds] -gmt true
=> Sat Nov 23 12:19:13 AM GMT 2002
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
clock format [clock seconds] -gmt false
=> Fri Nov 22 4:19:35 PM PST 2002
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The info Command
Table 13-3 summarizes the info command. The operations are described in more detail later.
Table 13-3. The info command
info args procedure
A list of procedure's arguments.
info body procedure
The commands in the body of procedure.
info cmdcount
The number of commands executed so far.
info commands ?pattern?
A list of all commands, or those matchingpattern. Includes built-ins and Tcl procedures.
info complete string
True if string contains a complete Tcl command.
info default proc arg var
True if arg has a default parameter value in procedureproc. The default value is stored into var.
info exists variable
True if variable is defined.
info functions ?pattern?
A list of all math functions, or those matchingpattern. (Tcl 8.4)
info globals ?pattern?
A list of all global variables, or those matchingpattern.
info hostname
The name of the machine. This may be the empty string if networking is not initialized.
info level
The stack level of the current procedure, or 0 for the global scope.
info level number
A list of the command and its arguments at the specified level of the stack.
info library
The pathname of the Tcl library directory.
info loaded ?interp?
A list of the libraries loaded into the interpreter namedinterp, which defaults to the current one.
info locals ?pattern?
A list of all local variables, or those matchingpattern.
info nameofexecutable
The file name of the program (e.g., of tclsh or wish).
info patchlevel
The release patch level for Tcl.
info procs ?pattern?
A list of all Tcl procedures, or those that matchpattern.
info script ?filename?
The name of the file being processed, or the empty string.
info sharedlibextension
The file name suffix of shared libraries.
info tclversion
The version number of Tcl.
info vars ?pattern?
A list of all visible variables, or those matchingpattern.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Variables
There are three categories of variables: local, global, and visible. Information about these categories is returned by thelocals, globals, and vars
operations, respectively. The local variables include procedure arguments as well as locally defined variables. The global variables include all
variables defined at the global scope. The visible variables include locals, plus any variables made visible via global or upvar commands. A
pattern can be specified to limit the returned list of variables to those that match the pattern. The pattern is interpreted according to the rules
of string match, which is described on page 53:
info globals auto*
=> auto_index auto_noexec auto_path
Namespaces, which are the topic of the next chapter, partition global variables into different scopes. You query the variables visible in a
namespace with:
info vars namespace::*
Remember that a variable may not be defined yet even though a global or upvar command has declared it visible in the current scope. Use
the info exists command to test whether a variable or an array element is defined or not. An example is shown on page 96.
Procedures
You can find out everything about a Tcl procedure with the args, body, and default operations. This is illustrated in the followingProc_Show
example. The puts commands use the -nonewline flag because the newlines in the procedure body, if any, are retained:
Example 13-2 Printing a procedure definition
proc Proc_Show {{namepat *} {file stdout}} {
foreach proc [info procs $namepat] {
set space ""
puts -nonewline $file "proc $proc {"
foreach arg [info args $proc] {
if [info default $proc $arg value] {
puts -nonewline $file "$space{$arg $value}"
} else {
puts -nonewline $file $space$arg
}
set space " "
}
# Double quotes allow substitution
# of [info body $proc]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
puts $file "} {[info body $proc]}"
}
}
Example 13-3 is a more elaborate example of procedure introspection that comes from thedirect.tcl file, which is part of the Tcl Web Server
described in Chapter 18. This code is used to map URL requests and the associated query data directly into Tcl procedure calls. This is
discussed in more detail on page 262. The Web server collects Web form data into an array called form. Example 13-3 matches up elements
of the form array with procedure arguments, and it collects extra elements into anargs parameter. If a form value is missing, then the default
argument value or the empty string is used:
Example 13-3 Mapping form data onto procedure arguments
# cmd is the name of the procedure to invoke
# form is an array containing form values
set cmdOrig $cmd
set params [info args $cmdOrig]
# Match elements of the form array to parameters
foreach arg $params {
if {![info exists form($arg)]} {
if {[info default $cmdOrig $arg value]} {
lappend cmd $value
} elseif {[string equal $arg "args"]} {
set needargs yes
} else {
lappend cmd {}
}
} else {
lappend cmd $form($arg)
}
}
# If args is a parameter, then append the form data
# that does not match other parameters as extra parameters
if {[info exists needargs]} {
foreach {name value} [array get form] {
if {[lsearch $params $name] < 0} {
lappend cmd $name $value
}
}
}
# Eval the command
set code [catch $cmd result]
The info commands operation returns a list of all commands, which includes both built-in commands defined in C and Tcl procedures. There
is no operation that just returns the list of built-in commands. Example 13-4 finds the built-in commands by removing all the procedures from
the list of commands.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Example 13-4 Finding built-in commands
proc Command_Info {{pattern *}} {
# Create a table of procedures for quick lookup
foreach p [info procs $pattern] {
set isproc($p) 1
}
# Look for command not in the procedure table
set result {}
foreach c [info commands $pattern] {
if {![info exists isproc($c)]} {
lappend result $c
}
}
return [lsort $result]
}
The Call Stack
The info level operation returns information about the Tcl evaluation stack, orcall stack. The global level is numbered zero. A procedure called
from the global level is at level one in the call stack. A procedure it calls is at level two, and so on. The info level command returns the current
level number of the stack if no level number is specified.
If a positive level number is specified (e.g., info level 3), then the command returns the procedure name and argument values at that level in
the call stack. If a negative level is specified, then it is relative to the current call stack. Relative level -1 is the level of the current procedure's
caller, and relative level 0 is the current procedure. The following example prints the call stack. The Call_trace procedure avoids printing
information about itself by starting at one less than the current call stack level:
Example 13-5 Getting a trace of the Tcl call stack
proc Call_Trace {{file stdout}} {
puts $file "Tcl Call Trace"
for {set x [expr [info level]-1]} {$x > 0} {incr x -1} {
puts $file "$x: [info level $x]"
}
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Command Evaluation
If you want to know how many Tcl commands are executed, use the info cmdcount command. This counts all commands, not just top-level
commands. The counter is never reset, so you need to sample it before and after a test run if you want to know how many commands are
executed during a test.
Command tracing provides detailed information about the execution of commands. It is described along with variable tracing on page 193.
The info complete operation figures out whether a string is a complete Tcl command. This is useful for command interpreters that need to wait
until the user has typed in a complete Tcl command before passing it to eval. Example 13-6 defines Command_Process that gets a line of
input and builds up a command. When the command is complete, the command is executed at the global scope. Command_Process takes
two callbacks as arguments. The inCmd is evaluated to get the line of input, and theoutCmd is evaluated to display the results. Chapter 10
describes callbacks why the curly braces are used with eval as they are in this example:
Example 13-6 A procedure to read and evaluate commands
proc Command_Process {inCmd outCmd} {
global command
append command(line) [eval $inCmd]
if {[info complete $command(line)]} {
set code [catch {uplevel #0 $command(line)} result]
eval $outCmd {$result $code}
set command(line) {}
}
}
proc Command_Read {{in stdin}} {
if {[eof $in]} {
if {$in != "stdin"} {
close $in
}
return {}
}
return [gets $in]
}
proc Command_Display {file result code} {
puts stdout $result
}
while {![eof stdin]} {
Command_Process {Command_Read stdin} \
{Command_Display stdout}
}
Scripts and the Library
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The name of the current script file is returned with the info script command. For example, if you use thesource command to read commands
from a file, then info script returns the name of that file if it is called during execution of the commands in that script. This is true even if the
info
script command is called from a procedure that is not defined in the script.
Use info script to find related files.
I often use info script to source or process files stored in the same directory as the script that is running. A few examples are shown Example
in
13-7.
Example 13-7 Using info script to find related files
# Get the directory containing the current script.
set dir [file dirname [info script]]
# Source a file in the same directory
source [file join $dir helper.tcl]
# Add an adjacent script library directory to auto_path
# The use of ../lib with file join is cross-platform safe.
lappend auto_path [file join $dir ../lib]
The pathname of the Tcl library is stored in the tcl_library variable, and it is also returned by theinfo library command. While you could put
scripts into this directory, it might be better to have a separate directory and use the script library facility described in Chapter 12. This makes
it easier to deal with new releases of Tcl and to package up your code if you want other sites to use it.
Version Numbers
Each Tcl release has a version number such as 7.4 or 8.0. This number is returned by the info tclversion command. If you want your script to
run on a variety of Tcl releases, you may need to test the version number and take different actions in the case of incompatibilities between
releases.
The Tcl release cycle starts with one or two alpha and beta releases before the final release, and there may even be a patch release after
that. The info patchlevel command returns a qualified version number, like 8.0b1 for the first beta release of 8.0. We switched from using "p"
(e.g., 8.0p2) to a three-level scheme (e.g., 8.0.3) for patch releases. The patch level is zero for the final release (e.g., 8.2.0). In general, you
should be prepared for feature changes during the beta cycle, but there should only be bug fixes in the patch releases. Another rule of thumb
is that the Tcl script interface remains quite compatible between releases; feature additions are upward compatible.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Execution Environment
The file name of the program being executed is returned with info nameofexecutable. This is more precise than the name in theargv0 variable,
which could be a relative name or a name found in a command directory on your command search path. It is still possible for info
nameofexecutable to return a relative pathname if the user runs your program as./foo, for example. The following construct always returns the
absolute pathname of the current program. If info nameofexecutable returns an absolute pathname, then the value of the current directory is
ignored. The pwd command is described on page 122:
file join [pwd] [info nameofexecutable]
A few operations support dynamic loading of shared libraries, which are described in Chapter 47. The info sharedlibextension returns the file
name suffix of dynamic link libraries. The info loaded command returns a list of libraries that have been loaded into an interpreter. Multiple
interpreters are described in Chapter 19.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Cross-Platform Support
Tcl is designed so that you can write scripts that run unchanged on UNIX, Macintosh, and Windows platforms. In practice, you may need a
small amount of code that is specific to a particular platform. You can find out information about the platform via the tcl_platform variable. This
is an array with these elements defined:
tcl_platform(platform) is one of unix, macintosh, or windows.
tcl_platform(os) identifies the operating system. Examples includeMacOS, Solaris, Linux, Win32s (Windows 3.1 with the Win32
subsystem), Windows 95, Windows NT, and SunOS.
tcl_platform(osVersion) gives the version number of the operating system.
tcl_platform(machine) identifies the hardware. Examples includeppc (Power PC), 68k (68000 family), sparc, intel, mips, and alpha.
tcl_platform(byteOrder) identifies the byte order of this machine and is one oflittleEndian or bigEndian.
tcl_platform(wordSize) identifies the size of the native machine word in bytes. This was introduced in Tcl 8.4.
tcl_platform(isWrapped) indicates that the application has been wrapped up into a single executable withTclPro Wrapper. This is
not defined in normal circumstances.
tcl_platform(user) gives the login name of the current user.
tcl_platform(debug) indicates that Tcl was compiled with debugging symbols.
tcl_platform(threaded) indicates that Tcl was compiled with thread support enabled.
On some platforms a hostname is defined. If available, it is returned with theinfo hostname command. This command may return an empty
string.
One of the most significant areas affected by cross-platform portability is the file system and the way files are named. This topic is discussed
on page 110.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Tracing Variables and Commands
The trace command registers a command to be called whenever a variable is accessed, modified, or unset. Tcl 8.4 introduced an updated
trace command which includes support for command tracing. The original (and still supported) form of the command applies only to variable
traces:
trace variable name ops command
trace vdelete name ops command
trace vinfo name
The name is a Tcl variable name, which can be a simple variable, an array, or an array element. If a whole array is traced, the trace is invoked
when any element is used according to ops. The ops argument is one or more of the letters r, for read traces, w, for write traces, u, for unset
traces, and a for array traces. The command is executed when one of these events occurs. It is invoked as:
command name1 name2 op
The name1 argument is the variable or array name. Thename2 argument is the name of the array index, or null if the trace is on a simple
variable. If there is an unset trace on an entire array and the array is unset, name2 is also null. The value of the variable is not passed to the
procedure. The traced variable is one level up the Tcl call stack. The upvar, uplevel, or global commands need to be used to make the
variable visible in the scope of command. These commands are described in more detail inChapter 7.
A read trace is invoked before the value of the variable is returned, so if it changes the variable itself, the new value is returned. A write trace
is called after the variable is modified. The unset trace is called after the variable is unset. The array trace, which was added in Tcl 8.4, is
called before the array command (e.g., array names) is used on the variable. A variable trace is automatically deleted when the variable is
unset.
Command Tracing
The new form of trace supports both variable and command tracing:
trace add type name ops command
trace remove type name ops command
trace info type name
The type is one of command, execution or variable. For command, ops is a list and may contain rename, to trace the renaming of a Tcl
command, or delete, to trace the deletion of a command. Command tracing cannot be used to prevent the actual deletion of a command, it
just receives the notification. No command traces are triggered when an interpreter is deleted. The command is invoked as:
command oldName newName op
For execution, the ops may be any of enter, leave, enterstep, and leavestep. enter invokes command immediately before the command name
is executed, and leave will invoke command immediately following each execution.enterstep and leavestep are similar but they operate on the
Tcl procedure name, invoking command for each Tcl command inside the procedure. In order to do this, they prevent the bytecode
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
compilation of that procedure. This allows you to create a simple debugger in pure Tcl. The enter and enterstep operations invoke command
as:
command command-string op
The leave and leavestep operations invoke command as:
command command-string code result op
The command-string is the current command being executed, code is the result code of the execution andresult is the result string. Example
6-16 on page 84 illustrates the different result codes.
For variable tracing, the ops may be one or more of read, write, unset, or array. This is an alternate way to set up the variable traces described
earlier.
Read-Only Variables
Example 13-8 uses traces to implement a read-only variable. A variable is modified before the trace procedure is called, so theReadOnly
variable is needed to preserve the original value. When a variable is unset, the traces are automatically removed, so the unset trace action
reestablishes the trace explicitly. Note that the upvar alias (e.g., var) cannot be used to set up the trace. Instead,uplevel is used to create the
trace in the original context of the variable. In general, essentially all traces are on global or namespace variables.
Example 13-8 Tracing variables
proc ReadOnlyVar {varName} {
upvar 1 $varName var
global ReadOnly
set ReadOnly($varName) $var
uplevel 1 [list trace variable $varName wu ReadOnlyTrace]
}
proc ReadOnlyTrace { varName index op } {
global ReadOnly
upvar 1 $varName var
switch $op {
w{
set var $ReadOnly($varName)
}
u{
set var $ReadOnly($varName)
# Re-establish the trace using the true name
uplevel 1 [list ReadOnlyVar $varName]
}
}
}
This example merely overrides the new value with the saved value. Another alternative is to raise an error with the error command. This will
cause the command that modified the variable to return the error. Another common use of trace is to update a user interface widget in
response to a variable change. Several of the Tk widgets have this feature built into them.
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
If more than one trace is set on a variable, then they are invoked in reverse order; the most recent trace is executed first. If there is a trace on
an array and on an array element, then the trace on the array is invoked first.
Creating an Array with Traces
Example 13-9 uses an array trace to dynamically create array elements:
Example 13-9 Creating array elements with array traces
# make sure variable is an array
set dynamic() {}
trace variable dynamic r FixupDynamic
proc FixupDynamic {name index op} {
upvar 1 $name dynArray
if {![info exists dynArray($index)]} {
set dynArray($index) 0
}
}
Information about traces on a variable is returned with thevinfo option:
trace vinfo dynamic
=> {r FixupDynamic}
A trace is deleted with the vdelete option, which has the same form as thevariable option. The trace in the previous example can be removed
with the following command:
trace vdelete dynamic r FixupDynamic
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Interactive Command History
Table 13-4. The history command
history
Short for history info with no count.
history add command ?exec?
Adds the command to the history list. Ifexec is specified, then execute the command.
history change new ?event?
Changes the command specified byevent to new in the command history.
history event ?event?
Returns the command specified byevent.
history info ?count?
Returns a formatted history list of the lastcount commands, or of all commands.
history keep count
Limits the history to the lastcount commands.
history nextid
Returns the number of the next event.
history redo ?event?
Repeats the specified command.
The Tcl shell programs keep a log of the commands that you type by using a history facility. The log is controlled and accessed via the history
command. The history facility uses the term event to mean an entry in its history log. The events are just commands, and they have an event
ID that is their index in the log. You can also specify an event with a negative index that counts backwards from the end of the log. Event -1 is
the previous event. Table 13-4 summarizes the Tcl history command. In the table, event defaults to -1.
In practice you will want to take advantage of the ability to abbreviate the history options and even the name of the history command itself.
For the command, you need to type a unique prefix, and this depends on what other commands are already defined. For the options, there
are unique one-letter abbreviations for all of them. For example, you could reuse the last word of the previous command with [history w $].
This works because a $ that is not followed by alphanumerics or an open brace is treated as a literal$.
Several of the history operations update the history list. They remove the actual history command and replace it with the command that
resulted from the history operation. The event and redo operations all behave in this manner. This makes perfect sense because you would
rather have the actual command in the history, instead of the history command used to retrieve the command.
History Syntax
Some extra syntax is supported when running interactively to make the history facility more convenient to use. Table 13-5 shows the special
history syntax supported by tclsh and wish.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 13-5. Special history syntax
!!
Repeats the previous command.
!n
Repeats command number n.If n is negative it counts backward from the current command. The previous command is event -1.
!prefix
Repeats the last command that begins withprefix.
!pattern
Repeats the last command that matchespattern.
^old^new
Globally replaces old with new in the last command.
The next example shows how some of the history operations work:
Example 13-10 Interactive history usage
% set a 5
5
% set a [expr $a+7]
12
% history
1 set a 5
2 set a [expr $a+7]
3 history
% !2
19
% !!
26
% ^7^13
39
% !h
1 set a 5
2 set a [expr $a+7]
3 history
4 set a [expr $a+7]
5 set a [expr $a+7]
6 set a [expr $a+13]
7 history
A Comparison to C Shell History Syntax
The history syntax shown in the previous example is simpler than the history syntax provided by the C shell. Not all of the history operations
are supported with special syntax. The substitutions (using ^old^new) are performed globally on the previous command. This is different from
the quick-history of the C shell. Instead, it is like the !:gs/old/new/ history command. So, for example, if the example had included^a^b in an
attempt to set b to 39, an error would have occurred because the command would have usedb before it was defined:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set b [expr $b+7]
If you want to improve the history syntax, you will need to modify the unknown command, which is where it is implemented. This command is
discussed in more detail in Chapter 12. Here is the code from theunknown command that implements the extra history syntax. The main
limitation in comparison with the C shell history syntax is that the ! substitutions are performed only when! is at the beginning of the command:
Example 13-11 Implementing special history syntax
# Excerpts from the standard unknown command
# uplevel is used to run the command in the right context
if {$name == "!!"} {
set newcmd [history event]
} elseif {[regexp {^!(.+)$} $name dummy event]} {
set newcmd [history event $event]
} elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name x old new]} {
set newcmd [history event -1]
catch {regsub -all -- $old $newcmd $new newcmd}
}
if {[info exists newcmd]} {
history change $newcmd 0
return [uplevel $newcmd]
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Debugging
The rapid turnaround with Tcl coding means that it is often sufficient to add a few puts statements to your script to gain some insight about its
behavior. This solution doesn't scale too well, however. A slight improvement is to add a Debug procedure that can have its output controlled
better. You can log the information to a file, or turn it off completely. In a Tk application, it is simple to create a text widget to hold the contents
of the log so that you can view it from the application. Here is a simple Debug procedure. To enable it you need to set thedebug(enable)
variable. To have its output go to your terminal, set debug(file) to stderr.
Example 13-12 A Debug procedure
proc Debug { args } {
global debug
if {![info exists debug(enabled)]} {
# Default is to do nothing
return
}
puts $debug(file) [join $args " "]
}
proc DebugOn {{file {}}} {
global debug
set debug(enabled) 1
if {[string length $file] == 0} {
set debug(file) Stderr
} else {
if [catch {open $file w} fileID] {
puts stderr "Cannot open $file: $fileID"
set debug(file) stderr
} else {
puts stderr "Debug info to $file"
set debug(file) $fileID
}
}
}
proc DebugOff {} {
global debug
if {[info exists debug(enabled)]} {
unset debug(enabled)
flush $debug(file)
if {$debug(file) != "stderr" &&
$debug(file) != "stdout"} {
close $debug(file)
unset debug(file)
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Tcl Dev Kit
Tcl Dev Kit is a commercial development environment for Tcl based on the originalTclPro created by Scriptics. TclPro was released to the
open-source community in November 2001. ActiveState has enhanced Tcl Dev Kit with new tools and more features. The development
[*]
environment includes ActiveTcl , which is an extended Tcl platform that includes [incr Tcl], Expect, and TclX. These extensions and Tcl/Tk
are distributed in source and binary form for Windows and a variety of UNIX platforms. More information is available at this URL:
[*]
ActiveTcl is a trademark of ActiveState Corporation.
http://www.activestate.com/Tcl
The current version of the Tcl Dev Kit contains these tools:
Debugger with Coverage
The Debugger provides a nice graphical user interface with all the features you expect from a traditional debugger. You can set breakpoints,
single step, examine variables, and look at the call stack. It understands a subtle issue that can arise from using the update command: nested
call stacks. It is possible to launch a new Tcl script as a side effect of the update command, which pushes the current state onto the execution
stack. This shows up clearly in the debugger stack trace. It maintains project state, so it will remember breakpoint settings and other
preference items between runs. One of the most interesting features is that it can debug remotely running applications. The debugger also
has built-in code coverage and hotspot profiling analysis. I use it regularly to debug Tcl code running inside the Tcl Web Server.
Checker
The Checker is a static code checker. This is a real win for large program development. It examines every line of your program looking for
syntax errors and dubious coding practices. It has detailed knowledge of Tcl, Tk, Expect, [incr Tcl], and TclX commands and validates your
use of them. It checks that you call Tcl procedures with the correct number of arguments, and can cross-check large groups of Tcl files. It
knows about changes between Tcl versions, and it can warn you about old code that needs to be updated.
Compiler
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The Compiler is really just a reader and writer for the byte codes that the Tcl byte-code compiler generates internally. It lets you precompile
scripts and save the results, and then load the byte-code later instead of raw source. This provides a great way to hide your source code, if
that is important to you. It turns out to save less time than you might think, however. By the time it reads the file from disk, decodes it, and
builds the necessary Tcl data structures, it is not much faster than reading a source file and compiling it on the fly.
TclApp
TclApp assembles a collection of Tcl scripts, data files, and a Tcl/Tk interpreter into Starkits and Starpacks, which are described in
Chapter 22.
TclApp provides a more friendly user interface than thesdx command line tool described in that Chapter. TheTcl Dev Kit comes with pre-built
Starkit runtimes that include Metakit, Expect, [incr Tcl], and TclX.
Tcl Service Manager
The Tcl Service Manager helps you turn your Tcl application into a service for Windows NT/2000/XP. Services have to implement special OS
interfaces that are not supported by tclsh or wish. You can create services that use the DLLs and scripts from an existing Tcl/Tk installation, or
create stand alone services that have no external dependencies.
Inspector
The Inspector is an improved version of the tkinspect application that lets you look at the state of other Tk applications. It displays procedures,
variables, and the Tk widget hierarchy. You can issue commands to another application to change variables or test out commands. This turns
out to be a very useful way to debug Tk applications. The original tkinspect was written by Sam Shen.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Other Tools
The Tcl community has built many interesting and useful tools to help your Tcl development. Only two of them are mentioned below, but you
can find many more at the Tcl Resource Center:
http://www.tcl.tk/resource/
The tkcon Console
Tkcon is an enhanced Tk console application written purely in Tcl. It includes many useful interactive control features, and may be embedded
in other Tcl applications. It was written by Jeff Hobbs and you can find it at:
http://tkcon.sourceforge.net/
Critcl
Critcl is a tool that lets you mix C code right into your Tcl scripts. When thecproc command encounters its code for the first time, it
automatically compiles it with gcc and loads it into your application. This provides an easy way to recode small parts of your application in C
to get a performance boost. It's home page is:
http://www.equi4.com/critcl
The bgerror Command
When a Tcl script encounters an error during background processing, such as handling file events or during the command associated with a
button, it signals the error by calling the bgerror procedure. A default implementation displays a dialog and gives you an opportunity to view
the Tcl call stack at the point of the error. You can supply your own version of bgerror. For example, when my exmh mail application gets an
error it offers to send mail to me with a few words of explanation from the user and a copy of the stack trace. I get interesting bug reports from
all over the world!
The bgerror command is called with one argument that is the error message. The global variableerrorInfo contains the stack trace information.
There is an example tkerror implementation in the on-line sources associated with this book.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The tkerror Command
The bgerror command used to be called tkerror. When event processing shifted from Tk into Tcl with Tcl 7.5 and Tk 4.1, the nametkerror was
changed to bgerror. Backwards compatibility is provided so that iftkerror is defined, then tkerror is called instead of bgerror. I have run into
problems with the compatibility setup and have found it more reliable to update my applications to use bgerror instead of tkerror. If you have
an application that runs under either Tk 4.0 or Tk 4.1, you can simply define both:
proc bgerror [info args tkerror] [info body tkerror]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Performance Tuning
The time command measures the execution time of a Tcl command. It takes an optional parameter that is a repetition count:
time {set a "Hello, World!"} 1000
=> 28 microseconds per iteration
If you need the result of the command being timed, use set to capture the result:
puts $log "command: [time {set result [command]}]"
An extensive benchmark suite that compares various Tcl versions is available at:
http://wiki.tcl.tk/Tcl%20Benchmarks
Time stamps in a Log
Another way to gain insight into the performance of your script is to generate log records that contain time stamps. The clock seconds value is
too coarse, but you can couple it with the clock clicks value to get higher resolution measurements. Use the code shown inExample 13-1 on
page 185 to calibrate the clicks per second on your system. Example 13-13 writes log records that contain the current time and the number of
clicks since the last record. There will be occasional glitches in the clicks value when the system counter wraps around or is reset by the
system clock, but it will normally give pretty accurate results. The Log procedure adds overhead, too, so you should take several
measurements in a tight loop to see how long each Log call takes:
Example 13-13 Time Stamps in log records
proc Log {args} {
global log
if [info exists log(file)] {
set now [clock clicks]
puts $log(file) [format "%s (%d)\t%s" \
[clock format [clock seconds]] \
[expr $now - $log(last)] \
[join $args " "]]
set log(last) $now
}
}
proc Log_Open {file} {
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
global log
catch {close $log(file)}
set log(file) [open $file w]
set log(last) [clock clicks]
}
proc Log_Flush {} {
global log
catch {flush $log(file)}
}
proc Log_Close {} {
global log
catch {close $log(file)}
catch {unset log(file)}
}
A more advanced profile command is part of the Extended Tcl (TclX) package. The TclXprofile command monitors the number of calls, the
CPU time, and the elapsed time spent in different procedures.
The Tcl Compiler
The built-in Tcl compiler improves performance in the following ways:
Tcl scripts are converted into an internal byte-code format that is efficient to process. The byte codes are saved so that cost of
compiling is paid only the first time you execute a procedure or loop. After that, execution proceeds much faster. Compilation is
done as needed, so unused code is never compiled. If you redefine a procedure, it is recompiled the next time it is executed.
Variables and command arguments are kept in a native format as long as possible and converted to strings only when necessary.
There are several native types, including integers, floating point numbers, Tcl lists, byte codes, and arrays. There are C APIs for
implementing new types. Tcl is still dynamically typed, so a variable can contain different types during its lifetime.
Expressions and control structures are compiled into special byte codes, so they are executed more efficiently. Because expr does
its own round of substitutions, the compiler generates better code if you group expressions with braces. This means that
expressions go through only one round of substitutions. The compiler can generate efficient code because it does not have to
worry about strange code like:
set subexpr {$x+$y}
expr 5 * $subexpr
The previous expression is not fully defined until runtime, so it has to be parsed and executed each time it is used. If the expression is
grouped with braces, then the compiler knows in advance what operations will be used and can generate byte codes to implement the
expression more efficiently.
The operation of the compiler is essentially transparent to scripts, but there are some differences in lists and expressions. These are
described in Chapter 54. With lists, the good news is that large lists are more efficient. The problem is that lists are parsed more aggressively,
so syntax errors at the end of a list will be detected even if you access only the beginning of the list. There were also some bugs in the code
generator in the widely used Tcl 8.0p2 release. Most of these were corner cases like unbraced expressions in if and while commands. Most of
these bugs were fixed in the 8.0.3 patch release, and the rest were cleaned up in Tcl 8.1 with the addition of a new internal parsing package.
The internal compiler continues to improve over time, with 8.4 extending the core instruction table to significantly improve performance over
previous versions.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 14. Namespaces
Namespaces group procedures and variables into separate name spaces. Namespaces were added in Tcl 8.0. This chapter describes the
namespace and variable commands.
Namespaces provide new scopes for procedures and global variables. Originally Tcl had one global scope for shared variables, local scopes
within procedures, and one global namespace for procedures. The single global scope for procedures and global variables can become
unmanageable as your Tcl application grows. I describe some simple naming conventions on page 181 that I have used successfully in large
programs. The namespace facility is a more elegant solution that partitions the global scope for procedure names and global variables.
Namespaces help structure large Tcl applications, but they add complexity. In particular, command callbacks may have to be handled
specially so that they execute in the proper namespace. You choose whether or not you need the extra structure and learning curve of
namespaces. If your applications are small, then you can ignore the namespace facility. If you are developing library packages that others will
use, you should pick a namespace for your procedures and data so that they will not conflict with the applications in which they are used.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Using Namespaces
Namespaces add new syntax to procedure and variable names. A double colon, ::, separates the namespace name from the variable or
procedure name. You use this syntax to reference procedures and variables in a different namespace. The namespace import command lets
you name things in other namespaces without the extra syntax. Namespaces can be nested, so you can create a hierarchy of scopes. These
concepts are explained in more detail in the rest of this chapter.
One feature not provided by namespaces is any sort of protection, or a way to enforce access controls between different namespaces. This
sort of thing is awkward, if not impossible, to provide in a dynamic language like Tcl. For example, you are always free to use namespace
eval to reach into any other namespace. Instead of providing strict controls, namespaces are meant to provide structure that enables large
scale programming.
The package facility described in Chapter 12 was designed before namespaces. This chapter illustrates a style that ties the two facilities
together, but they are not strictly related. It is possible to create a package named A that implements a namespaceB, or to use a package
without namespaces, or a namespace without a package. However, it makes sense to use the facilities together.
Example 14-1 repeats the random number generator from Example 7-4 on page 91 using namespaces. The standard naming style
conventions for namespaces use lowercase:
Example 14-1 Random number generator using namespaces
package provide random 1.0
namespace eval random {
# Create a variable inside the namespace
variable seed [clock seconds]
# Make the procedures visible to namespace import
namespace export init random range
# Create procedures inside the namespace
proc init { value } {
variable seed
set seed $value
}
proc random {} {
variable seed
set seed [expr {($seed*9301 + 49297) % 233280}]
return [expr {$seed/double(233280)}]
}
proc range { range } {
expr {int([random]*$range)}
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
Example 14-1 defines three procedures and a variable inside the namespacerandom. From inside the namespace, you can use these
procedures and variables directly. From outside the namespace, you use the :: syntax for namespace qualifiers. For example, the state
variable is just seed within the namespace, but you userandom::seed to refer to the variable from outside the namespace. Using the
procedures looks like this:
random::random
=> 0.3993355624142661
random::range 10
=> 4
If you use a package a lot you can import its procedures. A namespace declares what procedures can be imported with thenamespace export
command. Once you import a procedure, you can use it without a qualified name:
namespace import random::random
random
=> 0.54342849794238679
Importing and exporting are described in more detail later.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Namespace Variables
The variable command defines a variable inside a namespace. It is like theset command because it can define a value for the variable. You
can declare several namespace variables with one variable command. The general form is:
variable name ?value? ?name value? ...
If you have an array, do not assign a value in the variable command. Instead, use regular Tcl commands after you declare the variable. You
can put any commands inside a namespace block:
namespace eval foo {
variable arr
array set arr {name value name2 value2}
}
A namespace variable is similar to a global variable because it is outside the scope of any procedures. Procedures use the variable command
or qualified names to reference namespace variables. For example, the random procedure has a variable command that brings the
namespace variable into the current scope:
variable seed
If a procedure has a variable command that names a new variable, it is created in the namespace when it is firstset.
Watch out for conflicts with global variables.
You need to be careful when you use variables inside a namespace block. If you declare them with a variable command, they are clearly
namespace variables. However, if you forget to declare them, then they will either become namespace variables, or latch onto an existing
global variable by the same name. Consider the following code:
namespace eval foo {
variable table
for {set i 1} {$i <= 256} {incr i} {
set table($i) [format %c $i]
}
}
If there is already a global variable i, then the for loop will use that variable. Otherwise, it will create thefoo::i variable. I found this behavior
surprising, but it does make it easier to access global variables like env without first declaring them withglobal inside the namespace block.
Qualified Names
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A fully qualified name begins with ::, which is the name for the global namespace. A fully qualified name unambiguously names a procedure
or a variable. The fully qualified name works anywhere. If you use a fully qualified variable name, it is not necessary to use a global command.
For example, suppose namespace foo has a namespace variable x, and there is also a global variablex. The global variable x can be named
with this:
::x
The :: syntax does not affect variable substitutions. You can get the value of the global variablex with $::x. Name the namespace variablex with
this:
::foo::x
A partially qualified name does not have a leading ::. In this case the name is resolved from the current namespace. For example, the
following also names the namespace variable x:
foo::x
You can use qualified names with global. Once you do this, you can access the variable with its short name:
global ::foo::x
set x 5
Declaring variables is more efficient than using qualified names.
The Tcl byte-code compiler generates faster code when you declare namespace and global variables. Each procedure context has its own
table of variables. The table can be accessed by a direct slot index, or by a hash table lookup of the variable name. The hash table lookup is
slower than the direct slot access. When you use the variable or global command, then the compiler can use a direct slot access. If you use
qualified names, the compiler uses the more general hash table lookup.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Command Lookup
A command is looked up first in the current name space. If it is not found there, then it is looked up in the global namespace. This means that
you can use all the built-in Tcl commands inside a namespace with no special effort.
You can play games by redefining commands within a namespace. For example, a namespace could define a procedure named set. To get
the built-in set you could use ::set, while set referred to the set defined inside namespace. Obviously you need to be quite careful when you do
this.
You can use qualified names when defining procedures. This eliminates the need to put the proc commands inside a namespace block.
However, you still need to use namespace eval to create the namespace before you can create procedures inside it.Example 14-2 repeats
the random number generator using qualified names. random::init does not need a variable command because it uses a qualified name for
seed:
Example 14-2 Random number generator using qualified names
namespace eval random {
# Create a variable inside the namespace
variable seed [clock seconds]
}
# Create procedures inside the namespace
proc random::init { seed } {
set ::random::seed $seed
}
proc random::random {} {
variable seed
set seed [expr {($seed*9301 + 49297) % 233280}]
return [expr {$seed/double(233280)}]
}
proc random::range { range } {
expr {int([random]*$range)}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Nested Namespaces
Namespaces can be nested inside other namespaces. Example 14-3 shows three namespaces that have their own specific variablex. The
fully qualified names for these variables are ::foo::x, ::bar::x, and ::bar::foo::x.
Example 14-3 Nested namespaces
namespace eval foo {
variable x 1 ;# ::foo::x
}
namespace eval bar {
variable x 2 ;# ::bar::x
namespace eval foo {
variable x 3 ;# ::bar::foo::x
}
puts $foo::x ;# prints 3
}
puts $foo::x
;# prints 1
Partially qualified names can refer to two different objects.
In Example 14-3 the partially qualified name foo::x can reference one of two variables depending on the current namespace. From the global
scope the name foo::x refers to the namespace variable x inside ::foo. From the ::bar namespace, foo::x refers to the variable x inside ::bar::foo.
If you want to unambiguously name a variable in the current namespace, you have two choices. The simplest is to bring the variable into
scope with the variable command:
variable x
set x something
If you need to give out the name of the variable, then you have two choices. The most general solution is to use the namespace current
command to create a fully qualified name:
trace variable [namespace current]::x r \
[namespace current]::traceproc
However, it is simpler to just explicitly write out the namespace as in:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
trace variable ::myname::x r ::myname::traceproc
The drawback of this approach is that it litters your code with references to ::myname::, which might be subject to change during program
development.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Importing and Exporting Procedures
Commands can be imported from namespaces to make it easier to name them. An imported command can be used without its namespace
qualifier. Each namespace specifies exported procedures that can be the target of an import. Variables cannot be imported. Note that
importing is only a convenience; you can always use qualified names to access any procedure. As a matter of style, I avoid importing names,
so I know what package a command belongs to when I'm reading code.
The namespace export command goes inside the namespace block, and it specifies what procedures a namespace exports. The
specification is a list of string match patterns that are compared against the set of commands defined in a namespace. The export list can be
defined before the procedures being exported. You can do more than one namespace export to add more procedures, or patterns, to the
export list for a namespace. Use the -clear flag if you need to reset the export list.
namespace export ?-clear? ?pat? ?pat? ...
Only exported names appear in package indexes.
When you create the pkgIndex.tcl package index file with pkg_mkIndex, which is described Chapter 12, you should be aware that only
exported names appear in the index. Because of this, I often resort to exporting everything. I never plan to import the names, but I do rely on
automatic code loading based on the index files. This exports everything:
namespace export *
The namespace import command makes commands in another namespace visible in the current namespace. Animport can cause conflicts
with commands in the current namespace. The namespace import command raises an error if there is a conflict. You can override this with the
-force option. The general form of the command is:
namespace import ?-force? namespace::pat ?namespace::pat?...
The pat is a string match type pattern that is matched against exported commands defined in namespace. You cannot use patterns to match
namespace. The namespace can be a fully or partially qualified name of a namespace.
If you are lazy, you can import all procedures from a namespace:
namespace import random::*
The drawback of this approach is that random exports an init procedure, which might conflict with another module you import in the same way.
It is safer to import just the procedures you plan on using:
namespace import random::random random::range
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A namespace import takes a snapshot.
If the set of procedures in a namespace changes, or if its export list changes, then this has no effect on any imports that have already
occurred from that namespace.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Callbacks and Namespaces
Commands like after, bind, and button take arguments that are Tcl scripts that are evaluated later. Thesecallback commands execute later in
the global scope by default. If you want a callback to be evaluated in a particular namespace, you can construct the callback with namespace
code. This command does not execute the callback. Instead, it generates a Tcl command that will execute in the current namespace scope
when it is evaluated later. For example, suppose ::current is the current namespace. The namespace code command determines the current
scope and adds that to the namespace inscope command it generates:
set callback [namespace code {set x 1}]
=> namespace inscope ::current {set x 1}
# sometime later ...
eval $callback
When you evaluate $callback later, it executes in the ::current namespace because of the namespace inscope command. In particular, if there
is a namespace variable ::current::x, then that variable is modified. An alternative to using namespace code is to name the variable with a
qualified name:
set callback {set ::current::x 1}
The drawback of this approach is that it makes it tedious to move the code to a different namespace.
If you need substitutions to occur on the command when you define it, use list to construct it. Using list is discussed in more detail on pages
131 and 455. Example 14-4 wraps up the list and the namespace inscope into the code procedure, which is handy because you almost always
want to use list when constructing callbacks. The uplevel in code ensures that the correct namespace is captured; you can usecode anywhere:
Example 14-4 The code procedure to wrap callbacks
proc code {args} {
set namespace [uplevel {namespace current}]
return [list namespace inscope $namespace $args]
}
namespace eval foo {
variable y "y value" x {}
set callback [code set x $y]
=> namespace inscope ::foo {set x {y value}}
}
The example defines a callback that will set ::foo::x to y value. If you want to set x to the value that y has at the time of the callback, then you
do not want to do any substitutions. In that case, the original namespace code is what you want:
set callback [namespace code {set x $y}]
=> namespace inscope ::foo {set x $y}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
If the callback has additional arguments added by the caller, namespace inscope correctly adds them. For example,the scrollbar protocol
described on page 501 adds parameters to the callback that controls a scrollbar.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
[ Team LiB ]
Introspection
The info commands operation returns all the commands that are currently visible. It is described in more detail on page 190. You can limit the
information returned with a string match pattern. You can also include a namespace specifier in the pattern to see what is visible in a
namespace. Remember that global commands and imported commands are visible, so info commands returns more than just what is defined
by the namespace. Example 14-5 uses namespace origin, which returns the original name of imported commands, to sort out the commands
that are really defined in a namespace:
Example 14-5 Listing commands defined by a namespace
proc Namespace_List {{namespace {}}} {
if {[string length $namespace] == 0} {
# Determine the namespace of our caller
set namespace [uplevel {namespace current}]
}
set result {}
foreach cmd [info commands ${namespace}::*] {
if {[namespace origin $cmd] == $cmd} {
lappend result $cmd
}
}
return [lsort $result]
}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The namespace Command
Table 14-1 summarizes the namespace operations:
Table 14-1. The namespace command
namespace current
Returns the current namespace.
namespace children ?name?
Returns names of nested namespaces. name defaults to current namespace. pat is a string match
?pat?
pattern that limits what is returned.
namespace code script
Generates a namespace inscope command that will eval script in the current namespace.
namespace delete name ?name?
Deletes the variables and commands from the specified namespaces.
...
namespace eval name cmd ?args?
Concatenates args, if present, onto cmd and evaluates it in name namespace.
...
namespace exists name
Returns 1 if namespace name exists, 0 otherwise. (Tcl 8.4)
namespace export ?-clear? ?pat?
Adds patterns to the export list for current namespace. Returns export list if no patterns.
?pat? ...
namespace forget pat ?pat? ...
Undoes the import of names matching patterns.
namespace import ?-force? pat
Adds the names matching the patterns to the current namespace.
?pat? ...
namespace inscope name cmd
Appends args, if present, onto cmd as list elements and evaluates it inname namespace.
?args? ...
namespace origin cmd
Returns the original name of cmd.
namespace parent ?name?
Returns the parent namespace of name, or of the current namespace.
namespace qualifiers name
Returns the part of name up to the last :: in it.
namespace which ?flag? name
Returns the fully qualified version of name. The flag is one of -command, -variable, or -namespace.
namespace tail name
Returns the last component of name.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Converting Existing Packages to use Namespaces
Suppose you have an existing set of Tcl procedures that you want to wrap in a namespace. Obviously, you start by surrounding your existing
code in a namespace eval block. However, you need to consider three things: global variables, exported procedures, and callbacks.
Global variables remain global until you change your code to use variable instead of global. Some variables may make sense to
leave at the global scope. Remember that the variables that Tcl defines are global, including env, tcl_platform, and the others
listed in Table 2-2 on page 31. If you use the upvar #0 trick described on page 92, you can adapt this to namespaces by doing this
instead:
upvar #0 [namespace current]::$instance state
Exporting procedures makes it more convenient for users of your package. It is not strictly necessary because they can always
use qualified names to reference your procedures. An export list is a good hint about which procedures are expected to be used
by other packages. Remember that the export list determines what procedures are visible in the index created by pkg_mkIndex.
Callbacks execute at the global scope. If you use variable traces and variables associated with Tk widgets, these are also treated
as global variables. If you want a callback to invoke a namespace procedure, or if you give out the name of a namespace variable,
then you must construct fully qualified variable and procedure names. You can hardwire the current namespace:
button .foo -command ::myname::callback \
-textvariable ::myname::textvar
or you can use namespace current:
button .foo -command [namespace current]::callback \
-textvariable [namespace current]::textvar
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
[incr Tcl]
Object System
The Tcl namespace facility does not provide classes and inheritance. It just provides new scopes and a way to hide procedures and variables
inside a scope. There are Tcl C APIs that support hooks in variable name and command lookup for object systems so that they can
implement classes and inheritance. By exploiting these interfaces, various object systems can be added to Tcl as shared libraries.
The Tcl namespace facility was proposed by Michael McLennan based on his experiences with [incr Tcl], which is the most widely used
object-oriented extension for Tcl. [incr Tcl] provides classes, inheritance, and protected variables and commands. If you are familiar with C++,
[incr Tcl] should feel similar. A complete treatment of [incr Tcl] is not made in this book. [incr Tcl] From The Ground Up (Chad Smith,
Osborn-McGraw Hill, 1999) is an excellent source of information. You can find a version of [incr Tcl] on the CD-ROM. The [incr Tcl] home
page is:
http://www.tcltk.com/itcl/
The [incr Tcl] sources are maintained on SourceForge:
http://incrtcl.sourceforge.net/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
xotcl
Object System
Xotcl is a more recently developed object-oriented extension that blends object-orientation and scripting in a way that preserves the benefits
of both. It includes features such as dynamic object aggregation, per-object mixins, filters, dynamic component loading and more. The xotcl
home page is:
http://www.xotcl.org/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Notes
The final section of this chapter touches on a variety of features of the namespace facility.
Names for Widgets, Images, and Interpreters
There are a number of Tcl extensions that are not affected by the namespaces described in this chapter, which apply only to commands and
variable names. For example, when you create a Tk widget, a Tcl command is also created that corresponds to the Tk widget. This command
is always created in the global command namespace even when you create the Tk widget from inside a namespace eval block. Other
examples include Tcl interpreters, which are described in Chapter 19, and Tk images, which are described inChapter 41.
The variable command at the global scope
It turns out that you can use variable like the global command if your procedures are not inside a namespace. This is consistent because it
means "this variable belongs to the current namespace," which might be the global namespace.
Auto Loading and auto_import
The following sequence of commands can be used to import commands from the foo package:
package require foo
namespace import foo::*
However, because of the default behavior of packages, there may not be anything that matches foo::* after the package require. Instead,
there are entries in the auto_index array that will be used to load those procedures when you first use them. The auto loading mechanism is
described in Chapter 12. To account for this, Tcl calls out to a hook procedure calledauto_import. This default implementation of this
procedure searches auto_index and forcibly loads any pending procedures that match the import pattern. Packages like [incr Tcl] exploit this
hook to implement more elaborate schemes. The auto_import hook was first introduced in Tcl 8.0.3.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Namespaces and uplevel
Namespaces affect the Tcl call frames just like procedures do. If you walk the call stack with info level, the namespace frames are visible.
This means that you can get access to all variables with uplevel and upvar. Level #0 is still the absolute global scope, outside any namespace
or procedure. Try out Call_Trace from Example 13-5 on page 190 on your code that uses namespaces to see the effect.
Naming Quirks
When you name a namespace, you are allowed to have extra colons at the end. You can also have two or more colons as the separator
between namespace name components. These rules make it easier to assemble names by adding to the value returned from namespace
current. These all name the same namespace:
::foo::bar
::foo::bar::
::foo:::::::bar
The name of the global namespace can be either :: or the empty string. This follows from the treatment of:: in namespace names.
When you name a variable or command, a trailing :: is significant. In the following command a variable inside the::foo::bar namespace is
modified. The variable has an empty string for its name!
set ::foo::bar:: 3
namespace eval ::foo::bar { set {} }
=> 3
If you want to embed a reference to a variable just before two colons, use a backslash to turn off the variable name parsing before the colons:
set x xval
set y $x\::foo
=> xval::foo
Miscellaneous
You can remove names you have imported:
namespace forget random::init
You can rename imported procedures to modify their names:
rename range Range
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
You can even move a procedure into another namespace with rename:
rename random::init myspace::init
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 15. Internationalization
This chapter describes features that support text processing for different character sets such as ASCII and Japanese. Tcl can read and write
data in various character set encodings, but it processes data in a standard character set called Unicode. Tcl has a message catalog that lets
you generate different versions of an application for different languages. Tcl commands described are: encoding and msgcat.
Different languages use different alphabets, or character sets. An encoding is a standard way to represent a character set. Tcl hides most of
the issues associated with encodings and character sets, but you need to be aware of them when you write applications that are used in
different countries. You can also write an application using a message catalog so that the strings you display to users can be in the language
of their choice. Using a message catalog is more work, but Tcl makes it as easy as possible.
Most of the hard work in dealing with character set encodings is done "under the covers" by the Tcl C library. The Tcl C library underwent
substantial changes to support international character sets. Instead of using 8-bit bytes to store characters, Tcl uses a 16-bit character set
called Unicode, which is large enough to encode the alphabets of all languages. There is also plenty of room left over to represent special
characters like
and
.
In spite of all the changes to support Unicode, there are few changes visible to the Tcl script writer. Scripts written for Tcl 8.0 and earlier
continue to work fine with Tcl 8.1 and later versions. You only need to modify scripts if you want to take advantage of the features added to
support internationalization.
This chapter begins with a discussion of what a character set is and why different codings are used to represent them. It concludes with a
discussion of message catalogs.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Character Sets and Encodings
If you are from the United States, you've probably never thought twice about character sets. Most computers use the ASCII encoding, which
has 127 characters. That is enough for the 26 letters in the English alphabet, upper case and lower case, plus numbers, various punctuation
characters, and control characters like tab and newline. ASCII fits easily in 8-bit characters, which can represent 256 different values.
European alphabets include accented characters like è, ñ, and ä. The ISO Latin-1 encoding is a superset of ASCII that encodes 256
characters. It shares the ASCII encoding in values 0 through 127 and uses the "high half" of the encoding space to represent accented
characters as well as special characters like ©. There are several ISO Latin encodings to handle different alphabets, and these share the trick
of encoding ASCII in the lower half and other characters in the high half. You might see these encodings referred to as iso8859-1, iso8859-2,
and so on.
Asian character sets are simply too large to fit into 8-bit encodings. There are a number of 16-bit encodings for these languages. If you work
with these, you are probably familiar with the "Big 5" or ShiftJIS encodings.
Unicode is an international standard character set encoding. There are both 16-bit Unicode and 32-bit Unicode standards, but Tcl and just
about everyone else use the 16-bit standard. Unicode has the important property that it can encode all the important character sets without
conflicts and overlap. By converting all characters to the Unicode encoding, Tcl can work with different character sets simultaneously. As of
8.4, Tcl is compliant with Unicode v3.1. For more information on Unicode, see http://www.unicode.org/
The System Encoding
Computer systems are set up with a standard system encoding for their files. If you always work with this encoding, then you can ignore
character set issues. Tcl will read files and automatically convert them from the system encoding to Unicode. When Tcl writes files, it
automatically converts from Unicode to the system encoding. If you are curious, you can find out the system encoding with:
encoding system
=> cp1252
The "cp" is short for "code page," the term that Windows uses to refer to different encodings. On my Unix system, the system encoding is
iso8859-1.
Do not change the system encoding.
You could also change the system encoding with:
encoding system encoding
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
But this is not a good idea. It immediately changes how Tcl passes strings to your operating system, and it is likely to leave Tcl in an unusable
state. Tcl automatically determines the system encoding for you. Don't bother trying to set it yourself.
The encoding names command lists all the encodings that Tcl knows about. The encodings are kept in files stored in the
encoding directory
under the Tcl script library. They are loaded automatically the first time you use an encoding.
[View full width]
lsort [encoding names]
=> ascii big5 cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258 cp437 cp737
cp775 cp850 cp852 cp855 cp857 cp860 cp861 cp862 cp863 cp864 cp865 cp866 cp869 cp874 cp932
cp936 cp949 cp950 dingbats euc-cn euc-jp euc-kr gb12345 gb1988 gb2312 identity iso2022
iso2022-jp iso2022-kr iso8859-1 iso8859-2 iso8859-3 iso8859-4 iso8859-5 iso8859-6
iso8859-7 iso8859-8 iso8859-9 jis0201 jis0208 jis0212 ksc5601 macCentEuro macCroatian
macCyrillic macDingbats macGreek macIceland macJapan macRoman macRomania macThai
macTurkish macUkraine shiftjis symbol unicode utf-8
The encoding names reflect their origin. The "cp" refers to the "code pages" that Windows uses to manage encodings. The "mac" encodings
come from the Macintosh. The "iso," "euc," "gb," and "jis" encodings come from various standards bodies.
File Encodings and fconfigure
The conversion to Unicode happens automatically in the Tcl C library. When Tcl reads and writes files, it translates from the current system
encoding into Unicode. If you have files in different encodings, you can use the fconfigure command to set the encoding. For example, to
read a file in the standard Russian encoding (iso8859-7):
set in [open README.russian]
fconfigure $in -encoding iso8859-7
[*]
Example 15-1 shows a simple utility I use inexmh, a MIME-aware mail reader. MIME has its own convention for specifying the character set
encoding of a mail message that differs slightly from Tcl's naming convention. The procedure launders the name and then sets the encoding.
Exmh was already aware of MIME character sets, so it could choose fonts for message display. Adding this procedure and adding two calls
to it was all I had to do to adapt exmh to Unicode.
[*]
The exmh home page is http://www.beedub.com/exmh/. It is a wonderful tool that helps me manage tons of email. It
is written in Tcl/Tk, of course, and relies on the MH mail system, which limits it to UNIX.
Example 15-1 MIME character sets and file encodings
proc Mime_SetEncoding {file charset} {
regsub -all {(iso|jis|us)-} $charset {\1} charset
set charset [string tolower charset]
regsub usascii $charset ascii charset
fconfigure $file -encoding $charset
}
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Scripts in Different Encodings
If you have scripts that are not in the system encoding, then you cannot use source to load them. However, it is easy to read the files yourself
under the proper encoding and use eval to process them. Example 15-2 adds a -encoding flag to the source command. This is likely to
become a built-in feature in future versions of Tcl so that commands like info script will work properly:
Example 15-2 Using scripts in nonstandard encodings
proc Source {args} {
set file [lindex $args end]
if {[llength $args] == 3 &&
[string equal -encoding [lindex $args 0]]} {
set encoding [lindex $args 1]
set in [open $file]
fconfigure $in -encoding $encoding
set script [read $in]
close $in
return [uplevel 1 $script]
} elseif {[llength $args] == 1} {
return [uplevel 1 [list source $file]]
} else {
return -code error \
"Usage: Source ?-encoding encoding? file?"
}
}
Unicode and UTF-8
UTF-8 is an encoding for Unicode. While Unicode represents all characters with 16 bits, the UTF-8 encoding uses either 8, 16, or 24 bits to
represent one Unicode character. This variable-width encoding is useful because it uses 8 bits to represent ASCII characters. This means
that a pure ASCII string, one with character codes all less than 128, is also a UTF-8 string. Tcl uses UTF-8 internally to make the transition to
Unicode easier. It allows interoperability with Tcl extensions that have not been made Unicode-aware. They can continue to pass ASCII
strings to Tcl, and Tcl will interpret them correctly.
As a Tcl script writer, you can mostly ignore UTF-8 and just think of Tcl as being built on Unicode (i.e., full 16-bit character set support). If you
write Tcl extensions in C or C++, however, the impact of UTF-8 and Unicode is quite visible. This is explained in more detail in Chapter 47.
Tcl lets you read and write files in UTF-8 encoding or directly in Unicode. This is useful if you need to use the same file on systems that have
different system encodings. These files might be scripts, message catalogs, or documentation. Instead of using a particular native format, you
can use Unicode or UTF-8 and read the files the same way on any of your systems. Of course, you will have to set the encoding properly by
using fconfigure as shown earlier.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The Binary Encoding
If you want to read a data file and suppress all character set transformations, use the binary encoding:
fconfigure $in -encoding binary
Under the binary encoding, Tcl reads in each 8-bit byte and stores it into the lower half of a 16-bit Unicode character with the high half set to
zero. During binary output, Tcl writes out the lower byte of each Unicode character. You can see that reading in binary and then writing it out
doesn't change any bits. Watch out if you read something in one encoding and then write it out in binary. Any information in the high byte of
the Unicode character gets lost!
Tcl actually handles the binary encoding more efficiently than just described, but logically the previous description is still accurate. As
described in Chapter 47, Tcl can manage data in several forms, not just strings. When you read a file in binary format, Tcl stores the data as a
ByteArray that is simply 8 bits of data in each byte. However, if you ask for this data as a string (e.g., with the
puts command), Tcl automatically
converts from 8-bit bytes to 16-bit Unicode characters by setting the high byte to all zeros.
The binary command also manipulates data inByteArray format. If you read a file with the binary encoding and then use thebinary command to
process the data, Tcl will keep the data in an efficient form.
The string command also understands the ByteArray format, so you can do operations likestring length, string range, and string index on
binary data without suffering the conversion cost from a ByteArray to a UTF-8 string.
Conversions Between Encodings
The encoding command lets you convert strings between encodings. Theencoding convertfrom command converts data in some other
encoding into a Unicode string. The encoding convertto command converts a Unicode string into some other encoding. For example, the
following two sequences of commands are equivalent. They both read data from a file that is in Big5 encoding and convert it to Unicode:
fconfigure $input -encoding gb12345
set unicode [read $input]
or
fconfigure $input -encoding binary
set unicode [encoding convertfrom gb12345 [read $input]]
In general, you can lose information when you go from Unicode to any other encoding, so you ought to be aware of the limitations of the
encodings you are using. In particular, the binary encoding may not preserve your data if it starts out from an arbitrary Unicode string.
Similarly, an encoding like iso8859-2 may simply not have a representation of a given Unicode character.
The encoding Command
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 15-1 summarizes the encoding command:
Table 15-1. The encoding command
encoding convertfrom
Converts binary data from the specified encoding, which defaults to the system encoding, into
?encoding?data
Unicode.
encoding convertto ?encoding?
Converts string from Unicode into data in theencoding format, which defaults to the system
string
encoding.
encoding names
Returns the names of known encodings.
encoding system ?encoding?
Queries or change the system encoding.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Message Catalogs
A message catalog is a list of messages that your application will display. The main idea is that you can maintain several catalogs, one for
each language you support. Unfortunately, you have to be explicit about using message catalogs. Everywhere you generate output or display
strings in Tk widgets, you need to change your code to go through a message catalog. Fortunately, Tcl uses a nice trick to make this fairly
easy and to keep your code readable. Instead of using keys like "message42" to get messages out of the catalog, Tcl just uses the strings
you would use by default. For example, instead of this code:
puts "Hello, World!"
A version that uses message catalogs looks like this:
puts [msgcat::mc "Hello, World!"]
If you have not already loaded your message catalog, or if your catalog doesn't contain a mapping for "Hello, World!", then msgcat::mc just
returns its argument. Actually, you can define just what happens in the case of unknown inputs by defining your own msgcat::mcunknown
procedure, but the default behavior is quite good.
The message catalog is implemented in Tcl in the msgcat package. You need to use package require to make it available to your scripts:
package require msgcat
In addition, all the procedures in the package begin with "mc," so you can use namespace import to shorten their names further. I am not a
big fan of namespace import, but if you use message catalogs, you will be calling themsgcat::mc function a lot, so it may be worthwhile to
import it:
namespace import msgcat::mc
puts [mc "Hello, World!"]
Specifying a Locale
A locale identifies a language or language dialect to use in your output. A three-level scheme is used in the locale identifier:
language_country_dialect
The language codes are defined by the ISO-3166 standard. For example, "en" is English and "es" is Spanish. The country codes are defined
by the ISO-639 standard. For example, US is for the United States and UK is for the United Kingdom. The dialect is up to you. The country
and dialect parts are optional. Finally, the locale specifier is case insensitive. The following examples are all valid locale specifiers:
es
en
en_US
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
en_us
en_UK
en_UK_Scottish
en_uk_scottish
Users can set their initial locale with the LANG and LOCALE environment variables. If there is no locale information in the environment, then
the "c" locale is used (i.e., the C programming language.) You can also set and query the locale with the msgcat::mclocale procedure:
msgcat::mclocale
=> c
msgcat::mclocale en_US
The msgcat::mcpreferences procedure returns a list of the user's locale preferences from most specific (i.e., including the dialect) to most
general (i.e., only the language). For example:
msgcat::mclocale en_UK_Scottish
msgcat::mcpreferences
=> en_UK_Scottish en_UK en
Managing Message Catalog Files
A message catalog is simply a Tcl source file that contains a series of msgcat::mcset commands that define entries in the catalog. The syntax
of the msgcat::mcset procedure is:
msgcat::mcset locale src-string ?dest-string?
The locale is a locale description like es or en_US_Scottish. The src-string is the string used as the key when callingmsgcat::mc. The
dest-string is the result of msgcat::mc when the locale is in force.
The msgcat::mcload procedure should be used to load your message catalog files. It expects the files to be named according to their locale
(e.g., en_US_Scottish.msg), and it binds the message catalog to the current namespace.
The msgcat::mcload procedure loads files that match themsgcat::mcpreferences and have the .msg suffix. For example, with a locale of
en_UK_Scottish, msgcat::mcload would look for these files:
en_UK_Scottish.msg en_UK.msg en.msg
The standard place for message catalog files is in the msgs directory below the directory containing a package. With this arrangement you
can call msgcat::mcload as shown below. The use of info script to find related files is explained on page 192.
msgcat::mcload [file join [file dirname [info script]] msgs]
The message catalog file is sourced, so it can contain any Tcl commands. You might find it convenient to import the msgcat::mcset
procedure. Be sure to use -force with namespace import because that command might already have been imported as a result of loading
other message catalog files. Example 15-3 shows three trivial message catalog files:
Example 15-3 Three sample message catalog files
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
## en.msg
namespace import -force msgcat::mcset
mcset en Hello Hello_en
mcset en Goodbye Goodbye_en
mcset en String String_en
# end of en.msg
## en_US.msg
namespace import -force msgcat::mcset
mcset en_US Hello Hello_en_US
mcset en_US Goodbye Goodbye_en_US
# end of en_US.msg
## en_US_Texan.msg
namespace import -force msgcat::mcset
mcset en_US_Texan Hello Howdy!
# end of en_US_Texan.msg
Assuming the files from Example 15-3 are all in themsgs directory below your script, you can load all these files with these commands:
msgcat::mclocale en_US_Texan
msgcat::mcload [file join [file dirname [info script]] msgs]
The dialect has the highest priority:
msgcat::mc Hello
=> Howdy!
If the dialect does not specify a mapping, then the country mapping is checked:
msgcat::mc Goodbye
=> Goodbye_en_US
Finally, the lowest priority is the language mapping:
msgcat::mc String
=> String_en
Message Catalogs and Namespaces
What happens if two different library packages have conflicting message catalogs? Suppose the foo package contains this call:
msgcat::set fr Hello Bonjour
But the bar package contains this conflicting definition:
msgcat::mcset fr Hello Ello
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
What happens is that msgcat::mcset and msgcat::mc are sensitive to the current Tcl namespace. Namespaces are described in detail in
Chapter 14. If the foo package loads its message catalog while inside thefoo namespace, then any calls tomsgcat::mc from inside the foo
namespace will see those definitions. In fact, if you call msgcat::mc from inside any namespace, it will find only message catalog definitions
defined from within that namespace.
If you want to share message catalogs between namespaces, you will need to implement your own version of msgcat::mcunknown that looks
in the shared location. Example 15-4 shows a version that looks in the global namespace before returning the default string.
Example 15-4 Using msgcat::mcunknown to share message catalogs
proc msgcat::mcunknown {local src} {
variable insideUnknown
if {![info exist insideUnknown]} {
# Try the global namespace, being careful to note
# that we are already inside this procedure.
set insideUnknown true
set result [namespace eval :: [list \
msgcat::mc $src \
]]
unset insideUnknown
return $result
} else {
# Being called because the message isn't found
# in the global namespace
return $src
}
}
The msgcat package
Table 15-2 summarizes the msgcat package.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 15-2. The msgcat package
msgcat::mc src
Returns the translation of src according to the current locale and namespace.
msgcat::mclocale ?locale?
Queries or set the current locale.
msgcat::mcmax ?src-string src-string
Returns the length of the longest src-string after translation. (Tcl 8.3)
...?
msgcat::mcpreferences
Returns a list of locale preferences ordered from the most specific to the most general.
msgcat::mcload directory
Loads message files for the current locale fromdirectory.
msgcat::mcset locale src translation
Defines a mapping for the src string in locale to the translation string. (Tcl 8.3)
msgcat::mcmset src-trans-list
Define multiple src-translation pairs in a single call.
msgcat::mcunknown locale src
This procedure is called to resolve unknown translations. Applications can provide their own
implementations.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 16. Event-Driven Programming
This chapter describes event-driven programming using timers and asynchronous I/O facilities. The after command causes Tcl commands to
occur at a time in the future, and the fileevent command registers a command to occur in response to file input/output (I/O). Tcl commands
discussed are: after, fblocked, fconfigure, fileevent, and vwait.
Event-driven programming is used in long-running programs like network servers and graphical user interfaces. This chapter introduces
event-driven programming in Tcl. Tcl provides an easy model in which you register Tcl commands, and the system then calls those
commands when a particular event occurs. The after command is used to execute Tcl commands at a later time, and thefileevent command is
used to execute Tcl commands when the system is ready for I/O. The vwait command is used to wait for events. During the wait, Tcl
automatically calls Tcl commands that are associated with different events.
The event model is also used when programming user interfaces using Tk. Originally, event processing was associated only with Tk. The
event loop moved from Tk to Tcl in the Tcl 7.5/Tk 4.1 release.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The Tcl Event Loop
An event loop is built into Tcl, which checks for events and calls out to handlers that have been registered for different types of events. Some
of the events are processed internally to Tcl. You can register Tcl commands to be called in response to events. There are also C APIs for the
event loop, which are described on page 781. Event processing is active all the time in Tk applications. If you do not use Tk, you can start the
event loop with the vwait command as shown inExample 16-2 on page 230. The four event classes are handled in the following order:
Window events. These include keystrokes and button clicks. Handlers are set up for these automatically by the Tk widgets, and
you can register window event handlers with the bind command described in Chapter 29.
File and socket I/O events. The fileevent command registers handlers for these events.
Timer events. The after command registers commands to occur at specific times.
Idle events. These events are processed when there is nothing else to do. The Tk widgets use idle events to display themselves.
The after idle command registers a command to run at the next idle time.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The after Command
The after command sets up commands to happen in the future. In its simplest form, it pauses the application for a specified time, in
milliseconds. The example below waits for half a second:
after 500
During this time, the application does not process events. You can use the vwait command as shown on page 230 to keep the Tcl event loop
active during the waiting period. The after command can register a Tcl command to occur after a period of time, in milliseconds:
after milliseconds cmd arg arg...
The after command treats its arguments like eval; if you give it extra arguments, it concatenates them to form a single command. If your
argument structure is important, use list to build the command. The following example always works, no matter what the value ofmyvariable
is:
after 500 [list puts $myvariable]
The return value of after is an identifier for the registered command. You can cancel this command with theafter cancel operation. You specify
either the identifier returned from after, or the command string. In the latter case, the event that matches the command string exactly is
canceled.
Table 16-1 summarizes the after command:
Table 16-1. The after command
after milliseconds
Pauses for milliseconds.
after ms arg ?arg...?
Concatenates the args into a command and executes it afterms milliseconds. Immediately returns an ID.
after cancel id
Cancels the command registered underid.
after cancel command
Cancels the registered command.
after idle command
Runs command at the next idle moment.
after info ?id?
Returns a list of IDs for outstandingafter events, or the command associated withid.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The fileevent Command
The fileevent command registers a procedure that is called when an I/O channel is ready for read or write events. For example, you can open
a pipeline or network socket for reading, and then process the data from the pipeline or socket using a command registered with fileevent.
The advantage of this approach is that your application can do other things, like update the user interface, while waiting for data from the
pipeline or socket. Network servers use fileevent to manage connections to many clients. You can use fileevent on stdin and stdout, too.
Using network sockets is described in Chapter 17.
The command registered with fileevent uses the regular Tcl commands to read or write data on the I/O channel. For example, if the pipeline
generates line-oriented output, you should use gets to read a line of input. If you try and read more data than is available, your application
may block waiting for more input. For this reason, you should read one line in your fileevent handler, assuming the data is line-oriented. If you
know the pipeline will generate data in fixed-sized blocks, then you can use the read command to read one block.
The fconfigure command, which is described on page 232, can put a channel into nonblocking mode. This is not strictly necessary when using
fileevent. The pros and cons of nonblocking I/O are discussed later.
End of file makes a channel readable.
You should check for end of file in your read handler because it will be called when end of file occurs. It is important to close the channel
inside the handler because closing the channel automatically unregisters the handler. If you forget to close the channel, your read event
handler will be called repeatedly.
Example 16-1 shows a read event handler. A pipeline is opened for reading and its command executes in the background. TheReader
command is invoked when data is available on the pipe. When end of file is detected a variable is set, which signals the application waiting
with vwait. Otherwise, a single line of input is read and processed. Thevwait command is described on the next page. Example 24-1 on page
378 also uses fileevent to read from a pipeline.
Example 16-1 A read event file handler
proc Reader { pipe } {
global done
if {[eof $pipe]} {
catch {close $pipe}
set done 1
return
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
gets $pipe line
# Process the line here...
}
set pipe [open "|some command"]
fileevent $pipe readable [list Reader $pipe]
vwait done
There can be at most one read handler and one write handler for an I/O channel. If you register a handler and one is already registered, then
the old registration is removed. If you call fileevent without a command argument, it returns the currently registered command, or it returns the
empty string if there is none. If you register the empty string, it deletes the current file handler. Table 16-2 summarizes the fileevent command.
Table 16-2. The fileevent command
fileevent fileId readable ?command?
Queries or registers command to be called when fileId is readable.
fileevent fileId writable ?command?
Queries or registers command to be called when fileId is writable.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The vwait Command
The vwait command waits until a variable is modified. For example, you can set variablex at a future time, and then wait for that variable to be
set with vwait.
set x 0
after 500 {set x 1}
vwait x
Waiting with vwait causes Tcl to enter the event loop. Tcl will process events until the variablex is modified. The vwait command completes
when some Tcl code runs in response to an event and modifies the variable. In this case the event is a timer event, and the Tcl code is
simply:
set x 1
In some cases vwait is used only to start the event loop.Example 16-2 sets up a file event handler forstdin that will read and execute
commands. Once this is set up, vwait is used to enter the event loop and process commands until the input channel is closed. The process
exits at that point, so the vwait variable Stdin(wait) is not used:
Example 16-2 Using vwait to activate the event loop
proc Stdin_Start {prompt} {
global Stdin
set Stdin(line) ""
puts -nonewline $prompt
flush stdout
fileevent stdin readable [list StdinRead $prompt]
vwait Stdin(wait)
}
proc StdinRead {prompt} {
global Stdin
if {[eof stdin]} {
exit
}
append Stdin(line) [gets stdin]
if {[info complete $Stdin(line)]} {
catch {uplevel #0 $Stdin(line)} result
puts $result
puts -nonewline $prompt
flush stdout
set Stdin(line) {}
} else {
append Stdin(line) \n
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The fconfigure Command
The fconfigure command sets and queries several properties of I/O channels. The default settings for channels are suitable for most cases. If
you do event-driven I/O you may want to set your channel into nonblocking mode. If you handle binary data, you should turn off end of line
and character set translations. You can query the channel parameters like this:
[View full width]
fconfigure stdin
=> -blocking 1 -buffering none -buffersize 4096 -encoding iso8859-1 -eofchar {}
-translation lf
Table 16-3 summarizes the properties controlled byfconfigure, not including properties for serial lines.
Table 16-3. I/O channel properties controlled by fconfigure
-blocking
Blocks until I/O channel is ready: 0 or 1.
-buffering
Buffer mode: none, line, or full.
-buffersize
Number of characters in the buffer.
-encoding
The character set encoding.
-eofchar
Special end of file character. Control-z (\x1a) for DOS. Null otherwise.
-lasterror
Returns the last POSIX error message associated with a channel.
-translation
End of line translation: auto, lf, cr, crlf, binary.
-peername
Sockets only. IP address of remote host.
-peerport
Sockets only. Port number of remote host.
Serial lines have many additional properties. Before Tcl 8.4, you could only control the baud rate, parity and number of bits using the -mode
property. Many new properties for serial line control were added in Tcl 8.4. Table 16-4 lists the serial line properties set byfconfigure.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 16-4. Serial line properties controlled by fconfigure
-mode
Format: baud,parity,data,stop.
-queue
Returns a list of two integers representing the current number of bytes in the input and output queues. Tcl 8.4.
-timeout
Specifies the timeout in milliseconds for blocking reads. Tcl 8.4.
-ttycontrol
Sets up the handshake output lines. Tcl 8.4.
-ttystatus
Returns the current serial line status. Tcl 8.4.
-xchar
Specifies the software handshake characters. Tcl 8.4.
-handshake
Specifies one of rtscts, xonxoff or (Windows only) dtrdsr. Tcl 8.4.
-pollinterval
Sets the maximum time for polling of fileevents (Windows only.) Tcl 8.4.
-sysbuffer
Specifies the size of system buffers for a serial channel. (Windows only.) Tcl 8.4.
Nonblocking I/O
By default, I/O channels are blocking. A gets or read will wait until data is available before returning. Aputs may also wait if the I/O channel is
not ready to accept data. This behavior is all right if you are using disk files, which are essentially always ready. If you use pipelines or
network sockets, however, the blocking behavior can hang up your application.
The fconfigure command can set a channel into nonblocking mode. A gets or read command may return immediately with no data. This occurs
when there is no data available on a socket or pipeline. A puts to a nonblocking channel will accept all the data and buffer it internally. When
the underlying device (i.e., a pipeline or socket) is ready, then Tcl automatically writes out the buffered data. Nonblocking channels are useful
because your application can do something else while waiting for the I/O channel. You can also manage several nonblocking I/O channels at
once. Nonblocking channels should be used with the fileevent command described earlier. The following command puts a channel into
nonblocking mode:
fconfigure fileID -blocking 0
It is not strictly necessary to put a channel into nonblocking mode if you use fileevent. However, if the channel is in blocking mode, then it is
still possible for the gets or read done by your fileevent procedure to block. For example, an I/O channel might have some data ready, but not
a complete line. In this case, a gets would block, unless the channel is nonblocking. Perhaps the best motivation for a nonblocking channel is
the buffering behavior of a nonblocking puts. You can even close a channel that has buffered data, and Tcl will automatically write out the
buffers as the channel becomes ready. For these reasons, it is common to use a nonblocking channel with fileevent. Example 16-3 shows a
fileevent handler for a nonblocking channel. As described above, thegets may not find a complete line, in which case it doesn't read anything
and returns -1.
Example 16-3 A read event file handler for a nonblocking channel
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set pipe [open "|some command"]
fileevent $pipe readable [list Reader $pipe]
fconfigure $pipe -blocking 0
proc Reader { pipe } {
global done
if {[eof $pipe]} {
catch {close $pipe}
set done 1
return
}
if {[gets $pipe line] < 0} {
# We blocked anyway because only part of a line
# was available for input
} else {
# Process one line
}
}
vwait done
The fblocked Command
The fblocked command returns 1 if a channel does not have data ready. Normally the
fileevent command takes care of waiting for data, so I
have seen fblocked useful only in testing channel implementations.
Buffering
By default, Tcl buffers data, so I/O is more efficient. The underlying device is accessed less frequently, so there is less overhead. In some
cases you may want data to be visible immediately and buffering gets in the way. The following turns off all buffering:
fconfigure fileID -buffering none
Full buffering means that output data is accumulated until a buffer fills; then a write is performed. For reading, Tcl attempts to read a whole
buffer each time more data is needed. The read-ahead for buffering will not block. The -buffersize parameter controls the buffer size:
fconfigure fileID -buffering full -buffersize 8192
Line buffering is used by default on stdin and stdout. Each newline in an output channel causes a write operation. Read buffering is the same
as full buffering. The following command turns on line buffering:
fconfigure fileID -buffering line
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
End of Line Translations
On UNIX, text lines end with a newline character (\n). On Macintosh they end with a carriage return (\r). On Windows they end with a carriage
return, newline sequence (\r\n). Network sockets also use the carriage return, newline sequence. By default, Tcl accepts any of these, and the
line terminator can even change within a channel. All of these different conventions are converted to the UNIX style so that once read, text
lines always end with a newline character (\n). Both the read and gets commands do this conversion. By default, text lines are generated in
the platform-native format during output.
The default behavior is almost always what you want, but you can control the translation with fconfigure. Table 16-5 shows settings for
-translation:
Table 16-5. End of line translation modes
binary
No translation at all.
lf
UNIX-style, which also means no translations.
cr
Macintosh style. On input, carriage returns are converted to newlines. On output, newlines are converted to carriage returns.
crlf
Windows and Network style. On input, carriage return, newline is converted to a newline. On output, a newline is converted to a
carriage return, newline.
auto
The default behavior. On input, all end of line conventions are converted to a newline. Output is in native format.
End of File Character
In DOS file systems, there may be a Control-z character (\x1a) at the end of a text file. By default, this character is ignored on the Windows
platform if it occurs at the end of the file, and this character is output when you close the file. You can turn this off by specifying an empty
string for the end of file character:
fconfigure fileID -eofchar {}
In Tcl 8.4 the end-of-file character trick is used by Tcl_EvalFile and source to allow Tclkit and other tools to append non-script data to script
files. This is enabled by default, and should not normally interfere with your scripts.
Serial Devices
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The -mode attribute specifies the baud rate, parity mode, the number of data bits, and the number of stop bits:
set tty [open /dev/ttya]
fconfigure $tty -mode
=> 9600,0,8,2
Tcl 8.4 added the enhanced control of serial channels for Windows and Unix systems. The options are listed inTable 16-4.
Windows has some special device names that always connect you to the serial line devices when you use open. They are com1 through
com9. To access com devices above 9, use this form:{\\.\comXX}. The Windows system console is namedcon. The Windows null device is
nul.
UNIX has names for serial devices in /dev. The serial devices are /dev/ttya, /dev/ttyb, and so on. The system console is/dev/console. The
current terminal is /dev/tty. The null device is /dev/null.
Macintosh needs a special command to open serial devices. This is provided by a third-party extension that you can find at the Tcl Resource
Center under:
http://www.tcl.tk/resource/software/extensions/macintosh/
Character Set Encodings
Tcl automatically converts various character set encodings into Unicode internally. It cannot automatically detect the encoding for a file or
network socket, however, so you need to use fconfigure -encoding if you are reading data that is not in the system's default encoding.
Character set issues are explained in more detail in Chapter 15.
Configuring Read-Write Channels
If you have a channel that is used for both input and output, you can set the channel parameters independently for input and output. In this
case, you can specify a two-element list for the parameter value. The first element is for the input side of the channel, and the second
element is for the output side of the channel. If you specify only a single element, it applies to both input and output. For example, the
following command forces output end of line translations to be crlf mode, leaves the input channel on automatic, and sets the buffer size for
both input and output:
fconfigure pipe -translation {auto crlf} -buffersize 4096
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 17. Socket Programming
This chapter shows how to use sockets for programming network clients and servers. Advanced I/O techniques for sockets are described,
including nonblocking I/O and control over I/O uffering. Tcl commands discussed are: socket, fconfigure, and http::geturl.
Sockets are network communication channels. The sockets described in this chapter use the TCP network protocol, although you can find Tcl
extensions that create sockets using other protocols. TCP provides a reliable byte stream between two hosts connected to a network. TCP
handles all the issues about routing information across the network, and it automatically recovers if data is lost or corrupted along the way.
TCP is the basis for other protocols like Telnet, FTP, and HTTP.
A Tcl script can use a network socket just like an open file or pipeline. Instead of using the Tcl open command, you use the socket command
to open a socket. Then you use gets, puts, and read to transfer data. The close command closes a network socket.
Network programming distinguishes between clients and servers. A server is a process or program that runs for long periods of time and
controls access to some resource. For example, an FTP server governs access to files, and an HTTP server provides access to hypertext
pages on the World Wide Web. A client typically connects to the server for a limited time in order to gain access to the resource. For example,
when a Web browser fetches a hypertext page, it is acting as a client. The extended examples in this chapter show how to program the client
side of the HTTP protocol.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
Networking Extensions for Tcl
This chapter describes the basic programming techniques for sockets. Socket programing in Tcl is pretty easy, and a variety of extensions
have been created to handle common protocols. This section reviews some of the packages that are available, and then the rest of the
chapter describes how to program sockets yourself.
Scotty
The Scotty extension supports many network protocols.
The Scotty Tcl extension provides access to other network protocols like UDP, DNS, and RPC. It also supports the SNMP network
management protocol and the MIB database associated with SNMP. Scotty is a great extension package that is widely used for network
management applications. It is a C-level extension, so you have to compile it yourself or find a binary distribution. Its home page is:
http://wwwsnmp.cs.utwente.nl/~schoenw/scotty/
Standard Tcl Library
The Standard Tcl Library (tcllib) has several packages that support widely used TCP-based protocols. These are all pure-Tcl implementations.
There are packages for:
DNS client. Map between hostnames and IP addresses.
FTP client. Open FTP connections and download files from FTP servers.
FTP server. Implement a simple, extensible FTP server.
IRC client. Implement a chat client.
NNTP client. Fetch news from a news server.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
POP3 client. Post Office Protocol lets you fetch email from mail servers.
POP3 server. Implement a mail server.
SMTP client. Send email via the SMTP protocol.
SMTP server. Accept incoming email via SMTP.
URI manipulation. Package for parsing URLs.
There is good on-line documentation for these packages at:
http://tcllib.sourceforge.net/tcllib/doc/
HTTP
The Tcl distribution includes an HTTP client, which is described on page 251. You don't need to add tcllib to get this. In addition, there is a
nice web server built in Tcl, which is the topic of Chapter 18.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Client Sockets
A client opens a socket by specifying the host address and port number for the server of the socket. The host address gives the network
location (i.e., which computer), and the port selects a particular server from all the possible servers that may be running on that host. For
example, HTTP servers typically use port 80, while FTP servers use port 20. The following example shows how to open a client socket to a
Web server:
set s [socket www.tcl.tk 80]
There are two forms for host names. The previous example uses a domain name: www.tcl.tk. You can also specify raw IP addresses, which
are specified with four dot-separated integers (e.g., 192.220.75.86). A domain name is mapped into a raw IP address by the system software,
and it is almost always a better idea to use a domain name in case the IP address assignment for the host changes. This can happen when
hosts are upgraded or they move to a different part of the network.
Some systems also provide symbolic names for well-known port numbers. For example, instead of using 20 for the FTP service, you can use
ftp. On UNIX systems, the well-known port numbers are listed in the file named/etc/services.
Client Socket Options
The socket command accepts some optional arguments when opening the client-side socket. The general form of the command is:
socket ?-async? ?-myaddr address? ?-myport myport? host port
Ordinarily the address and port on the client side are chosen automatically. If your computer has multiple network interfaces, you can select
one with the -myaddr option. The address value can be a domain name or an IP address. If your application needs a specific client port, it can
choose one with the -myport option. If the port is in use, thesocket command will raise an error.
The -async option causes connection to happen in the background, and thesocket command returns immediately. The socket becomes
writable when the connection completes, or fails. You can use fileevent to get a callback when this occurs. This is shown inExample 17-1. If
you use the socket before the connection completes, and the socket is in blocking mode, then Tcl automatically blocks and waits for the
connection to complete. If the socket is in nonblocking mode, attempts to use the socket return immediately. The gets and read commands
would return -1, and fblocked would return 1 in this situation.
In some cases, it can take a long time to open the connection to the server. Usually this occurs when the server host is down, and it may take
longer than you want for the connection to time out. The following example sets up a timer with after so that you can choose your own timeout
limit on the connection:
Example 17-1 Opening a client socket with a timeout
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
proc Socket_Client {host port timeout} {
global connected
after $timeout {set connected timeout}
set sock [socket -async $host $port]
fileevent $sock w {set connected ok}
vwait connected
fileevent $sock w {}
if {$connected == "timeout"} {
return -code error timeout
} else {
return $sock
}
}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Server Sockets
A TCP server socket allows multiple clients. The way this works is that the socket command creates a listening socket, and then new sockets
are created when clients make connections to the server. Tcl takes care of all the details and makes this easy to use. You simply specify a
port number and give the socket command a callback to execute when a client connects to your server socket. The callback is just a Tcl
command. A simple example is shown below:
Example 17-2 Opening a server socket
set listenSocket [socket -server Accept 2540]
proc Accept {newSock addr port} {
puts "Accepted $newSock from $addr port $port"
}
vwait forever
The Accept command is the callback made when clients connect to the server. Tcl adds additional arguments to the callback before it calls it.
The arguments are the new socket connection, and the host and port number of the remote client. In this simple example, Accept just prints
out its arguments.
The vwait command puts Tcl into its event loop so that it can do the background processing necessary to accept connections. Thevwait
command will wait until the forever variable is modified, which won't happen in this simple example. The key point is that Tcl processes other
events (e.g., network connections and other file I/O) while it waits. If you have a Tk application (e.g., wish), then it already has an event loop
to handle window system events, so you do not need to use vwait. The Tcl event loop is discussed on page 227.
Server Socket Options
By default, Tcl lets the operating system choose the network interface used for the server socket, and you simply supply the port number. If
your computer has multiple interfaces, you may want to specify a particular one. Use the -myaddr option for this. The general form of the
command to open server sockets is:
socket -server callback ?-myaddr address? port
The last argument to the socket command is the server's port number. For your own unofficial servers, you'll need to pick port numbers higher
than 1024 to avoid conflicts with existing services. UNIX systems prevent user programs from opening server sockets with port numbers less
than 1024. If you use 0 as the port number, then the operating system will pick the listening port number for you. You must use fconfigure to
find out what port you have:
fconfigure $sock -sockname
=> ipaddr hostname port
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
The Echo Service
Example 17-3 The echo service
proc Echo_Server {port} {
global echo
set echo(main) [socket -server EchoAccept $port]
}
proc EchoAccept {sock addr port} {
global echo
puts "Accept $sock from $addr port $port"
set echo(addr,$sock) [list $addr $port]
fconfigure $sock -buffering line
fileevent $sock readable [list Echo $sock]
}
proc Echo {sock} {
global echo
if {[eof $sock] || [catch {gets $sock line}]} {
# end of file or abnormal connection drop
close $sock
puts "Close $echo(addr,$sock)"
unset echo(addr,$sock)
} else {
if {[string compare $line "quit"] == 0} {
# Prevent new connections.
# Existing connections stay open.
close $echo(main)
}
puts $sock $line
}
}
The echo server accepts connections from clients. It reads data from the clients and writes that data back. The example uses fileevent to wait
for data from the client, and it uses fconfigure to adjust the buffering behavior of the network socket. You can useExample 17-3 as a template
for more interesting services.
The Echo_Server procedure opens the socket and saves the result inecho(main). When this socket is closed later, the server stops accepting
new connections but existing connections won't be affected. If you want to experiment with this server, start it and wait for connections like
this:
Echo_Server 2540
vwait forever
The EchoAccept procedure uses the fconfigure command to set up line buffering. This means that eachputs by the server results in a network
transmission to the client. The importance of this will be described in more detail later. A complete description of the fconfigure command is
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
given in Chapter 16. The EchoAccept procedure uses the fileevent command to register a procedure that handles I/O on the socket. In this
example, the Echo procedure will be called whenever the socket is readable. Note that it is not necessary to put the socket into nonblocking
mode when using the fileevent callback. The effects of nonblocking mode are discussed on page 232.
EchoAccept saves information about each client in theecho array. This is used only to print out a message when a client closes its
connection. In a more sophisticated server, however, you may need to keep more interesting state about each client. The name of the socket
provides a convenient handle on the client. In this case, it is used as part of the array index.
The Echo procedure first checks to see whether the socket has been closed by the client or there is an error when reading the socket. Theif
expression only performs the gets if the eof does not return true:
if {[eof $sock] || [catch {gets $sock line}]} {
Closing the socket automatically clears the fileevent registration. If you forget to close the socket upon the end of file condition, the Tcl event
loop will invoke your callback repeatedly. It is important to close it when you detect end of file.
Example 17-4 A client of the echo service
proc Echo_Client {host port} {
set s [socket $host $port]
fconfigure $s -buffering line
return $s
}
set s [Echo_Client localhost 2540]
puts $s "Hello!"
gets $s
=> Hello!
In the normal case, the server simply reads a line with gets and then writes it back to the client withputs. If the line is "quit," then the server
closes its main socket. This prevents any more connections by new clients, but it doesn't affect any clients that are already connected.
Example 17-4 shows a sample client of the Echo service. The main point is to ensure that the socket is line buffered so that each
puts by the
client results in a network transmission. (Or, more precisely, each newline character results in a network transmission.) If you forget to set line
buffering with fconfigure, the client's gets command will probably hang because the server will not get any data; it will be stuck in buffers on
the client.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Fetching a URL with HTTP
The HyperText Transport Protocol (HTTP) is the protocol used on the World Wide Web. This section presents a procedure to fetch pages or
images from a server on the Web. Items in the Web are identified with a Universal Resource Location (URL) that specifies a host, port, and
location on the host. The basic outline of HTTP is that a client sends a URL to a server, and the server responds with some header
information and some content data. The header information describes the content, which can be hypertext, images, postscript, and more.
Example 17-5 Opening a connection to an HTTP server
proc Http_Open {url} {
global http
if {![regexp -nocase {^(http://)?([^:/]+)(:([0-9]+))?(/.*)} \
$url x protocol server y port path]} {
error "bogus URL: $url"
}
if {[string length $port] == 0} {
set port 80
}
set sock [socket $server $port]
puts $sock "GET $path HTTP/1.0"
puts $sock "Host: $server"
puts $sock "User-Agent: Tcl/Tk Http_Open"
puts $sock ""
flush $sock
return $sock
}
The Http_Open procedure uses regexp to pick out the server and port from the URL. This regular expression is described in detail on page
159. The leading http:// is optional, and so is the port number. If the port is left off, then the standard port 80 is used. If the regular expression
matches, then a socket command opens the network connection.
The protocol begins with the client sending a line that identifies the command (GET), the path, and the protocol version. The path is the part of
the URL after the server and port specification. The rest of the request is lines in the following format:
key: value
The Host identifies the server, which supports servers that implement more than one server name. The
User-Agent identifies the client
program, which is often a browser like Netscape Navigator, Mozilla, or Internet Explorer. The key-value lines are terminated with a blank line.
This data is flushed out of the Tcl buffering system with the flush command. The server will respond by sending the URL contents back over
the socket. This is described shortly, but first we consider proxies.
Proxy Servers
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A proxy is used to get through firewalls that many organizations set up to isolate their network from the Internet. The proxy accepts HTTP
requests from clients inside the firewall and then forwards the requests outside the firewall. It also relays the server's response back to the
client. The protocol is nearly the same when using the proxy. The difference is that the complete URL is passed to the GET command so that
the proxy can locate the server. Example 17-6 uses a proxy if one is defined:
Example 17-6 Opening a connection through a HTTP proxy
# Http_Proxy sets or queries the proxy
proc Http_Proxy {{new {}}} {
global http
if ![info exists http(proxy)] {
return {}
}
if {[string length $new] == 0} {
return $http(proxy):$http(proxyPort)
} else {
regexp {^([^:]+):([0-9]+)$} $new x \
http(proxy) http(proxyPort)
}
}
proc Http_Open {url {cmd GET} {query {}}} {
global http
if {![regexp -nocase {^(http://)?([^:/]+)(:([0-9]+))?(/.*)} \
$url x protocol server y port path]} {
error "bogus URL: $url"
}
if {[string length $port] == 0} {
set port 80
}
if {[info exists http(proxy)] &&
[string length $http(proxy)]} {
set sock [socket $http(proxy) $http(proxyPort)]
puts $sock "$cmd http://$server:$port$path HTTP/1.0"
} else {
set sock [socket $server $port]
puts $sock "$cmd $path HTTP/1.0"
}
puts $sock "User-Agent: Tcl/Tk Http_Open"
puts $sock "Host: $server"
if {[string length $query] > 0} {
puts $sock "Content-Length: [string length $query]"
puts $sock ""
puts $sock $query
}
puts $sock ""
flush $sock
fconfigure $sock -blocking 0
return $sock
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The HEAD Request
In Example 17-6, the Http_Open procedure takes a cmd parameter so that the user ofHttp_Open can perform different operations. TheGET
operation fetches the contents of a URL. The HEAD operation just fetches the description of a URL, which is useful to validate a URL. The
POST operation transmits query data to the server (e.g., values from a form) and also fetches the contents of the URL. All of these operations
follow a similar protocol. The reply from the server is a status line followed by lines that have key-value pairs. This format is similar to the
client's request. The reply header is followed by content data with GET and POST operations. Example 17-7 implements the HEAD command,
which does not involve any reply data:
Example 17-7 Http_Head validates a URL
proc Http_Head {url} {
upvar #0 $url state
catch {unset state}
set state(sock) [Http_Open $url HEAD]
fileevent $state(sock) readable [list HttpHeader $url]
# Specify the real name, not the upvar alias, to vwait
vwait $url\(status)
catch {close $state(sock)}
return $state(status)
}
proc HttpHeader {url} {
upvar #0 $url state
if {[eof $state(sock)]} {
set state(status) eof
close $state(sock)
return
}
if {[catch {gets $state(sock) line} nbytes]} {
set state(status) error
lappend state(headers) [list error $nbytes]
close $state(sock)
return
}
if {$nbytes < 0} {
# Read would block
return
} elseif {$nbytes == 0} {
# Header complete
set state(status) head
} elseif {![info exists state(headers)]} {
# Initial status reply from the server
set state(headers) [list http $line]
} else {
# Process key-value pairs
regexp {^([^:]+): *(.*)$} $line x key value
lappend state(headers) [string tolower $key] $value
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
}
}
The Http_Head procedure uses Http_Open to contact the server. The HttpHeader procedure is registered as afileevent handler to read the
server's reply. A global array keeps state about each operation. The URL is used in the array name, and upvar is used to create an alias to
the name (upvar is described on page 92):
upvar #0 $url state
You cannot use the upvar alias as the variable specified tovwait. Instead, you must use the actual name. The backslash turns off the array
reference in order to pass the name of the array element to vwait, otherwise Tcl tries to reference url as an array:
vwait $url\(status)
The HttpHeader procedure checks for special cases: end of file, an error on thegets, or a short read on a nonblocking socket. The very first
reply line contains a status code from the server that is in a different format than the rest of the header lines:
code message
The code is a three-digit numeric code. 200 is OK. Codes in the 400's and 500's indicate an error. The codes are explained fully in RFC 1945
that specifies HTTP 1.0. The first line is saved with the key http:
set state(headers) [list http $line]
The rest of the header lines are parsed into key-value pairs and appended onto state(headers). This format can be used to initialize an array:
array set header $state(headers)
When HttpHeader gets an empty line, the header is complete and it sets thestate(status) variable, which signals Http_Head. Finally,
Http_Head returns the status to its caller. The complete information about the request is still in the global array named by the URL.
Example
17-8 illustrates the use of Http_Head:
Example 17-8 Using Http_Head
set url http://www.sun.com/
set status [Http_Head $url]
=> eof
upvar #0 $url state
array set info $state(headers)
parray info
info(http)
HTTP/1.0 200 OK
info(server)
Apache/1.1.1
info(last-modified) Nov ...
info(content-type) text/html
The GET and POST Requests
Example 17-9 shows Http_Get, which implements the GET and POST requests. The difference between these is thatPOST sends query data
to the server after the request header. Both operations get a reply from the server that is divided into a descriptive header and the content
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
data. The Http_Open procedure sends the request and the query, if present, and reads the reply header.Http_Get reads the content.
The descriptive header returned by the server is in the same format as the client's request. One of the key-value pairs returned by the server
specifies the Content-Type of the URL. The content-types come from the MIME standard, which is described in RFC 1521. Typical
content-types are:
text/html — HyperText Markup Language (HTML), which is introduced inChapter 3.
text/plain — plain text with no markup.
image/gif — image data in GIF format.
image/jpeg — image data in JPEG format.
application/postscript — a postscript document.
application/x-tcl — a Tcl program! This type is discussed inChapter 20.
Example 17-9 Http_Get fetches the contents of a URL
proc Http_Get {url {query {}}} {
upvar #0 $url state
;# Alias to global array
catch {unset state}
;# Aliases still valid.
if {[string length $query] > 0} {
set state(sock) [Http_Open $url POST $query]
} else {
set state(sock) [Http_Open $url GET]
}
set sock $state(sock)
fileevent $sock readable [list HttpHeader $url]
# Specify the real name, not the upvar alias, to vwait
vwait $url\(status)
set header(content-type) {}
set header(http) "500 unknown error"
array set header $state(headers)
# Check return status.
# 200 is OK, other codes indicate a problem.
regsub "HTTP/1.. " $header(http) {} header(http)
if {![string match 2* $header(http)]} {
catch {close $sock}
if {[info exists header(location)] &&
[string match 3* $header(http)]} {
# 3xx is a redirection to another URL
set state(link) $header(location)
return [Http_Get $header(location) $query]
}
return -code error $header(http)
}
# Set up to read the content data
switch -glob -- $header(content-type) {
text/* {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
# Read HTML into memory
fileevent $sock readable [list HttpGetText $url]
}
default {
# Copy content data to a file
fconfigure $sock -translation binary
set state(filename) [File_TempName http]
if [catch {open $state(filename) w} out] {
set state(status) error
set state(error) $out
close $sock
return $header(content-type)
}
set state(fd) $out
fcopy $sock $out -command [list HttpCopyDone $url]
}
}
vwait $url\(status)
return $header(content-type)
}
Http_Get uses Http_Open to initiate the request, and then it looks for errors. It handles redirection errors that occur if a URL has changed.
These have error codes that begin with 3. A common case of this error is when a user omits the trailing slash on a URL (e.g.,
http://www.tcl.tk).
Most servers respond with:
302 Document has moved
Location: http://www.tcl.tk/
If the content-type is text, then Http_Get sets up a fileevent handler to read this data into memory. The socket is in nonblocking mode, so the
read handler can read as much data as possible each time it is called. This is more efficient than using gets to read a line at a time. The text
will be stored in the state(body) variable for use by the caller ofHttp_Get. Example 17-10 shows the HttpGetText fileevent handler:
Example 17-10 HttpGetText reads text URLs
proc HttpGetText {url} {
upvar #0 $url state
if {[eof $state(sock)]} {
# Content complete
set state(status) done
close $state(sock)
} elseif {[catch {read $state(sock)} block]} {
set state(status) error
lappend state(headers) [list error $block]
close $state(sock)
} else {
append state(body) $block
}
}
The content may be in binary format. This poses a problem for Tcl 7.6 and earlier. A null character will terminate the value, so values with
embedded nulls cannot be processed safely by Tcl scripts. Tcl 8.0 supports strings and variable values with arbitrary binary data. Example
17-9 uses fcopy to copy data from the socket to a file without storing it in Tcl variables. This command was introduced in Tcl 7.5 as
unsupported0, and became fcopy in Tcl 8.0. It takes a callback argument that is invoked when the copy is complete. The callback gets
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
additional arguments that are the bytes transferred and an optional error string. In this case, these arguments are added to the url argument
specified in the fcopy command. Example 17-11 shows the HttpCopyDone callback:
Example 17-11 HttpCopyDone is used with fcopy
proc HttpCopyDone {url bytes {error {}}} {
upvar #0 $url state
if {[string length $error]} {
set state(status) error
lappend state(headers) [list error $error]
} else {
set state(status) ok
}
close $state(sock)
close $state(fd)
}
The user of Http_Get uses the information in thestate array to determine the status of the fetch and where to find the content. There are four
cases to deal with:
There was an error, which is indicated by the state(error) element.
There was a redirection, in which case, the new URL is in state(link). The client of Http_Get should change the URL and look at its
state instead. You can use upvar to redefine the alias for thestate array:
upvar #0 $state(link) state
There was text content. The content is in state(body).
There was another content-type that was copied to state(filename).
The fcopy Command
The fcopy command can do a complete copy in the background. It automatically sets upfileevent handlers, so you do not have to usefileevent
yourself. It also manages its buffers efficiently. The general form of the command is:
fcopy input output ?-size size? ?-command callback?
The -command argument makes fcopy work in the background. When the copy is complete or an error occurs, thecallback is invoked with one
or two additional arguments: the number of bytes copied, and, in the case of an error, it is also passed an error string:
fcopy $in $out -command [list CopyDone $in $out]
proc CopyDone {in out bytes {error {}} {
close $in ; close $out
}
With a background copy, the fcopy command transfers data from input until end of file or size bytes have been transferred. If no-size argument
is given, then the copy goes until end of file. It is not safe to do other I/O operations with input or output during a background fcopy. If either
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
input or output gets closed while the copy is in progress, the current copy is stopped. If theinput is closed, then all data already queued for
output is written out.
Without a -command argument, the fcopy command reads as much as possible depending on the blocking mode ofinput and the optional size
parameter. Everything it reads is queued for output before fcopy returns. If output is blocking, then fcopy returns after the data is written out. If
input is blocking, then fcopy can block attempting to read size bytes or until end of file.
The fcopy command had a bug which ignored the encoding on the channels which was corrected in 8.3.4.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The http Package
The standard Tcl library includes an http package that is based on the code I wrote for this chapter. This section documents the package,
which has a slightly different interface. The library version uses namespaces and combines the Http_Get, Http_Head, and Http_Post
procedures into a single http::geturl procedure. The examples in this chapter are still interesting, but you should use the standardhttp package
for your production code.
http::config
The http::config command is used to set the proxy information, time-outs, and theUser-Agent and Accept headers that are generated in the
HTTP request. You can specify the proxy host and port, or you can specify a Tcl command that is run to determine the proxy. With no
arguments, http::config returns the current settings:
http::config
=> -accept */* -proxyfilter http::ProxyRequired
-proxyhost {} -proxyport {}
-useragent {Tcl http client package 2.4}
If you specify just one option, its value is returned:
http::config -proxyfilter
=> http::ProxyRequired
You can set one or more options:
http::config -proxyhost webcache.eng -proxyport 8080
The default proxy filter just returns the -proxyhost and -proxyport values if they are set. You can supply a smarter filter that picks a proxy
based on the host in the URL. The proxy filter is called with the hostname and should return a list of two elements, the proxy host and port. If
no proxy is required, return an empty list.
http::geturl
The http::geturl procedure does a GET, POST, or HEAD transaction depending on its arguments. By default,http::geturl blocks until the
request completes and it returns a token that represents the transaction. As described below, you use the token to get the results of the
transaction. If you supply a -command callback option, then http::geturl returns immediately and invokes callback when the transaction
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
completes. The callback is passed the token that represents the transaction.
For simple applications you can simply block on the transaction:
set token [http::geturl www.beedub.com/index.html]
=> http::1
The leading http:// in the URL is optional. The return value is a token that represents the transaction. There are other
http:: commands that
return information when passed the token. The token is also the name of an array that contains state about the transaction. Make sure to
clean up this array to free memory when you are done:
http::cleanup $token
If you need to access the array directly, use upvar to create an alias:
upvar #0 $token data
Table 17-1 lists the options to http::geturl.
Table 17-1. Options to the http::geturl command
-binary boolean
Specifies whether we should do a binary transfer of the data. (Tcl 8.3)
-blocksize num
Block size when copying to a channel.
-channel fileID
The fileID is an open file or socket. The URL data is copied to this channel instead of saving it in memory.
-command callback
Calls callback when the transaction completes. The token fromhttp::geturl is passed to callback.
-handler command
Called from the event handler to read data from the URL.
-headers list
The list specifies a set of headers that are included in the HTTP request. The list alternates between header
keys and values.
-progress command
Calls command after each block is copied to a channel. It gets called with three parameters:
command token totalsize currentsize
-query codedstring
Issues a POST request with the codedstring form data.
-queryblocksize num
Block size when copying to the query channel.
-querychannel fileID
The fileID is an open file or socket. The query data is copied from this channel instead of passed in a string.
-queryprogress
Calls command after each block is copied from the query channel. It gets called with three parameters:
command
command token totalsize currentsize
-timeout msec
Aborts the request after msec milliseconds have elapsed.
-type mime-type
Use mime-type as the Content-Type value during a POST operation.
-validate bool
If bool is true, a HEAD request is made.
Table 17-2 lists the access functions to the state array.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 17-2. The http support procedures
http::cleanup $token
Unsets the state array named by$token.
http::code $token
Returns state(http).
http::data $token
Returns state(body).
http::error $token
Returns state(error).
http::ncode $token
Returns the numeric return code contained instate(http).
http::size $token
Return the number of bytes read from the URL so far.
http::status $token
Returns state(status).
http::wait $token
Blocks until the transaction completes.
The array elements are listed in Table 17-3:
Table 17-3. Elements of the http::geturl state array
body
The contents of the URL.
charset
The value of the charset attribute from the Content-Type meta-data value. If none was specified, this defaults to the RFC
standard iso8859-1.
coding
A copy of the Content-Encoding meta-data value.
currentsize
The current number of bytes transferred.
error
An explanation of why the transaction was aborted.
http
The HTTP reply status.
meta
A list of the keys and values in the reply header.
posterror
An explanation of why the transaction was aborted when writing post query data, if any.
status
The current status: pending, ok, eof, or reset.
totalsize
The expected size of the returned data.
type
The content type of the returned data.
url
The URL of the request.
You can take advantage of the asynchronous interface by specifying a command that is called when the transaction completes. The callback
is passed the token returned from http::geturl so that it can access the transaction state:
http::geturl $url -command [list Url_Display $text $url]
proc Url_Display {text url token} {
upvar #0 $token state
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Display the url in text
}
You can have http::geturl copy the URL to a file or socket with the-channel option. This is useful for downloading large files or images. In this
case, you can get a progress callback so that you can provide user feedback during the transaction. Example 17-12 shows a simple
downloading script:
Example 17-12 Downloading files with http::geturl
#!/usr/local/bin/tclsh8.4
if {$argc < 2} {
puts stderr "Usage: $argv0 url file"
exit 1
}
package require http
set url [lindex $argv 0]
set file [lindex $argv 1]
set out [open $file w]
proc progress {token total current} {
puts -nonewline "."
}
http::config -proxyhost webcache.eng -proxyport 8080
set token [http::geturl $url -progress progress \
-headers {Pragma no-cache} -channel $out]
close $out
# Print out the return header information
puts ""
upvar #0 $token state
puts $state(http)
foreach {key value} $state(meta) {
puts "$key: $value"
}
exit 0
http::formatQuery
If you specify form data with the -query option, then http::geturl does a POST transaction. You need to encode the form data for safe
transmission. The http::formatQuery procedure takes a list of keys and values and encodes them inx-www-url-encoded format. Pass this result
as the query data:
http::formatQuery name "Brent Welch" title "Tcl Programmer"
=> name=Brent+Welch&title=Tcl+Programmer
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
http::register
and http::unregister
The http::register procedure registers a protocol handler for URL protocols other than HTTP. Thehttp::unregister procedure removes the
handler registration. The primary application is to provide secure web access via HTTPS and the TLS extension.
package require tls
http::register https 443 ::tls::socket
set token [http::geturl https://my.secure.site/]
http::reset
You can cancel an outstanding transaction withhttp::reset:
http::reset $token
This is done automatically when you setup a-timeout with http::config.
http::cleanup
When you are done with the data returned fromhttp::geturl, use the http::cleanup procedure to unset the state variable used to store the data.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Basic Authentication
Web pages are often password protected. The most common form of this uses a protocol called Basic Authentication, which is not very
strong, but easy to implement. With this scheme, the server responds to an HTTP request with a 401 error status and a Www-Authenticate
header, which specifies the authentication protocol the server wants to use. For example, the server response can contain the following
information:
HTTP/1.0 401 Authorization Required
Www-Authenticate: Basic realm="My Pages"
The realm is meant to be an authentication domain. In practice, it is used in the string that gets displayed to the user as part of the password
prompt. For example, a Web browser will display this prompt:
Enter the password for My Pages at www.beedub.com
After getting the user name and password from the user, the Web browser tries its HTTP request again. This time it includes an Authorization
header that contains the user name and password encoded with base64 encoding. There is no encryption at all — anyone can decode the
string, which is why this is not a strong form of protection. The Standard Tcl Library includes a base64 package that has base64::encode and
base64::decode procedures. Example 17-13 illustrates the Basic Authentication protocol. It uses the-headers option to http::geturl that lets
you pass additional headers in the request.
Example 17-13 Basic Authentication using http::geturl
package require base64
package require http
proc BasicAuthentication {url promptProc} {
set token [http::geturl $url]
http::wait $token
if {[string match *401* [http::code $token]]} {
upvar #0 $token data
# Extract the realm from the Www-Authenticate line
array set reply $data(meta)
if {[regexp {realm=(.*)} $reply(Www-Authenticate) \
x realm]} {
# Call back to prompt for username, password
set answer [$promptProc $realm]
http::cleanup $token
# Encode username:password and pass this in
# the Authorization header
set auth [base64::encode \
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[lindex $answer 0]:[lindex $answer 1]]
set token [http::geturl $url -headers \
[list Authorization "Basic $auth"]]
http::wait $token
}
}
return $token
}
Example 17-13 takes a promptProc argument that is the name of a procedure to call to get the username and password. This procedure could
display a Tk dialog box, or prompt for user input from the terminal. In practice, you probably already know the username and password. In this
case, you can skip the initial challenge–response steps and simply supply the Authorization header on the first request:
http::geturl $url -headers \
[list Authorization \
"Basic [base64::encode $username:$password]"]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 18. TclHttpd Web Server
This chapter describes TclHttpd, a Web server built entirely in Tcl. The Web server can be used as a standalone server, or it can be
embedded into applications to Web-enable them. TclHttpd provides a Tcl+HTML template facility that is useful for maintaining site-wide look
and feel, and an Application Direct URL that invokes a Tcl procedure in an application.
TclHttpd started out as about 175 lines of Tcl that could serve up HTML pages and images. The Tcl socket and I/O commands make this
easy, and the C language implementation of the Tcl runtime library makes the server surprisingly fast. Of course, there are lots of features in
Web servers like Apache or Netscape that were not present in the first prototype. Steve Uhler took my prototype, refined the HTTP handling,
and aimed to keep the basic server under 250 lines. I went the other direction, setting up a modular architecture, adding in features found in
other Web servers, and adding some interesting ways to connect TclHttpd to Tcl applications.
Today TclHttpd is used both as a general-purpose Web server, and as a framework for building server applications. It implements www.tcl.tk
and a number of other general purpose Web sites. It is also built into several commercial applications such as license servers and mail spam
filters. The server is freely available, just like Tcl itself, and you can use it in any application without restriction or license fees. Instructions for
setting up the TclHttpd on your platform are given toward the end of the chapter, on page 284. It works on Unix, Windows, and Macintosh.
Using TclHttpd, you can have your own Web server up and running quickly.
This chapter provides an overview of the server and several examples of how you can use it. The chapter is not an exhaustive reference to
every feature. Instead, it concentrates on a very useful subset of server features that I use the most.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Integrating TclHttpd with Your Application
The bulk of this chapter describes the various ways you can extend the server and integrate it into your application. TclHttpd is interesting
because, as a Tcl script, it is easy to add to your application. Suddenly your application has an interface that is accessible to Web browsers in
your company's intranet or the global Internet. The Web server provides several ways you can connect it to your application:
Static pages — As a "normal" Web server, you can serve static documents that describe your application.
Domain handlers — You can arrange for all URL requests in a section of your Web site to be handled by your application. This is a
very general interface where you interpret what the URL means and what sort of pages to return to each request. For example,
http://www.tcl.tk/resource is implemented this way. The URL past/resource selects an index in a simple database, and the server
returns a page describing the pages under that index.
Application Direct URLs — This is a domain handler that maps URLs onto Tcl procedures. The form query data that is part of the
HTTP GET or POST request is automatically mapped onto the parameters of the Application Direct procedure. The procedure
simply computes the page as its return value. This is an elegant and efficient alternative to the CGI interface. For example, in
TclHttpd, the URLs under /status report various statistics about the Web server's operation.
Document handlers — You can define a Tcl procedure that handles all files of a particular type. For example, the server has a
handler for CGI scripts, HTML files, image maps, and HTML+Tcl template files.
HTML+Tcl Templates — These are Web pages that mix Tcl and HTML markup. The server replaces the Tcl using thesubst
command and returns the result. The server can cache the result in a regular HTML file to avoid the overhead of template
processing on future requests. Templates are a great way to maintain the common look and feel to a family of Web pages, as well
as to implement more advanced dynamic HTML features like self-checking forms.
TclHttpd Architecture
You may find it helpful to read the code to learn more about the features of the server. In this section, there are references to Tcl files in the
source, which are in the lib directory of the distribution that is on the CD-ROM.
Figure 18-1 shows the basic components of the server. At the core is theHttpd module (httpd.tcl), which implements the server side of the
HTTP protocol. The "d" in Httpd stands fordaemon, which is the name given to system servers onUNIX. This module manages network
requests, dispatches them to the Url module, and provides routines used to return the results to requests.
Figure 18-1. The dotted box represents one application that embeds TclHttpd. Document
templates and Application Direct URLs provide direct connections from an HTTP request to your
application. You can also implement completely custom URL handlers.
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The Url module (url.tcl) divides the Web site into domains, which are subtrees of the URL hierarchy provided by the server. The idea is that
different domains may have completely different implementations. For example, the Document domain (doc.tcl) maps its URLs into files and
directories on your hard disk, while the Application Direct domain (direct.tcl) maps URLs into Tcl procedure calls within your application. The
CGI domain (cgi.tcl) maps URLs onto other programs that compute Web pages.
Adding Code to TclHttpd
The TclHttpd distribution, which is described in more detail starting at page 284, is set up so you can easily add code for your application into
the server. For simple applications, you simply put your files into a special directory for custom code, and the server loads them automatically
upon startup. These files should define Tcl procedures and register them as Domain Handlers, Direct URL handlers, or Document handlers.
Example 18-1 implements /hello/world:
Example 18-1 The hello.tcl file implements /hello/world
Direct_Url /hello Hello
proc Hello/world {} {
return "<b>Hello, World!</b>"
}
Suppose you put that file into the directory /tmp/tclhttpd_test. Then you can start the server like this:
tclsh8.3 bin/httpd.tcl -library /tmp/tclhttpd_test -debug 1
Now access this URL:
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
http://localhost:8015/hello/world
Custom Main Programs
The TclHttpd main program, bin/httpd.tcl, may conflict with the main program of your existing application. For those applications that embed
Tcl interpreters in a more custom manner, you will need to modify bin/httpd.tcl for use with your application. That script is not very big, and it is
well-commented. The key elements are the Httpd_Server call that opens the listening socket for the Web server, and thevwait at the very end
that activates the event loop. The rest is all about argument parsing and initializing the various modules that support the server. It is those
aspects that may differ for your custom server application.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Domain Handlers
You can implement new kinds of domains that provide your own interpretation of a URL. This is the most flexible interface available to extend
the Web server. You provide a callback that is invoked to handle every request in a domain, or subtree, of the URL hierarchy. The callback
interprets the URL, computes the page content, and returns the data using routines from the Httpd module.
Example 18-2 defines a simple domain that always returns the same page to every request. The domain is registered with the
Url_PrefixInstall
command. The arguments to Url_PrefixInstall are the URL prefix and a callback that is called to handle all URLs that match that prefix. In the
example, all URLs that have the prefix /simple are dispatched to the SimpleDomain procedure.
The SimpleDomain handler illustrates several properties of domain handlers. The sock and suffix arguments to SimpleDomain are appended
by Url_Dispatch when it invokes the domain handler. Thesock is the socket connection to the client. The suffix parameter is the part of the
URL after the prefix. For example, if the server receives a request for the URL /simple/page, then the prefix is/simple and the suffix is /page.
The prefix argument is defined when the callback is registered withUrl_PrefixInstall. You can specify whatever information you need to pass
to the domain handler. In this simple example, we probably don't need the prefix, but if you implement several different URL domains with the
same handler, then you can pass in the prefix to distinguish them.
Example 18-2 A simple URL domain
Url_PrefixInstall /simple [list SimpleDomain /simple]
proc SimpleDomain {prefix sock suffix} {
upvar #0 Httpd$sock data
# Generate page header
set html "<title>A simple page</title>\n"
append html "<h1>$prefix$suffix</h1>\n"
append html "<h1>Date and Time</h1>\n"
append html [clock format [clock seconds]]
# Display connection state
append html "<h1>Connection State</h1>"
append html [html::tableFromArray data border=1]
# Display query data
if {[info exist data(query)]} {
append html "<h1>Query Data</h1>\n"
append html [html::tableFromList [ncgi::nvlist] border=1]
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Httpd_ReturnData $sock text/html $html
}
Connection State and Query Data
The sock parameter is a handle on the socket connection to the remote client. This variable is also used to name a state variable that the
Httpd
module maintains about the connection. The name of the state array is Httpd$sock. In some cases, you may need access to this information,
and the standard idiom is to use upvar to get a more convenient name for this array (i.e.,data):
upvar #0 Httpd$sock data
The html and ncgi Packages
The html package provides many procedures useful for generating fragments of HTML. Thehtml::tableFromArray procedure is used to dump
out the connection state in the data array. Its cousin,html::tableFromList, is used to dump out the query data. The query data is obtained with
the ncgi::nvlist procedure. TclHttpd initializes the ncgi module so you can use ncgi::nvlist, ncgi::value, and other procedures to access query
data in your domain handlers. Note: it is not necessary to call ncgi::parse as you would from a CGI script. Thehtml package has some other
features, which are described later, that are very useful when generating HTML forms. These packages are part of the Standard Tcl Library,
tcllib, which can be found along with Tcl and TclHttpd.
Returning Results
Finally, once the page has been computed, the Httpd_ReturnData procedure is used to return the page to the client. This takes care of the
HTTP protocol as well as returning the data. There are three related procedures, Httpd_ReturnFile, Httpd_Error, and Httpd_Redirect. These
are summarized in Table 18-1 on page 277.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Application Direct URLs
The Application Direct domain implementation provides the simplest way to extend the Web server. It hides the details associated with query
data, decoding URL paths, and returning results. All you do is define Tcl procedures that correspond to URLs. Their arguments are
automatically matched up to the query data, as shown in Example 13-3 on page 189. The Tcl procedures compute a string that is the result
data, which is usually HTML. That's all there is to it.
The name of the Tcl procedure that implements an Application Direct URL is related to the name of the URL. This way, TclHttpd can
automatically look up the Tcl procedure that should implement a given URL. The Tcl procedure name and the URL have distinct prefixes, but
the suffix is the same. For example, if the Tcl procedure prefix is Demo and the URL prefix is/demo, then the Demo/time Tcl procedure
implements the /demo/time URL. The Direct_Url procedure sets up the correspondence between the procedures and URLs. This is shown in
Example 18-3:
Example 18-3 Application Direct URLs
Direct_Url /demo Demo
proc Demo {} {
return "<html><head><title>Demo page</title></head>\n\
<body><h1>Demo page</h1>\n\
<a href=/demo/time>What time is it?</a>\n\
<form action=/demo/echo>\n\
Data: <input type=text name=data>\n\
<br>\n\
<input type=submit name=echo value='Echo Data'>\n\
</form>\n\
</body></html>"
}
proc Demo/time {{format "%H:%M:%S"}} {
return [clock format [clock seconds] -format $format]
}
proc Demo/echo {args} {
# Compute a page that echoes the query data
set html "<head><title>Echo</title></head>\n"
append html "<body>"
append html [html::tableFromList $args "border=1"]
return $html
}
Example 18-3 defines /demo as an Application Direct URL domain that is implemented by procedures that begin withDemo. There are just
three URLs defined:
/demo
/demo/time
/demo/echo
The /demo page displays a hypertext link to the /demo/time page and a simple form that will be handled by the/demo/echo page. This page is
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
static, so there is just one return command in the procedure body. Each line of the string ends with:
\n\
This is just a formatting trick to let me indent each line in the procedure, without having the line indented in the resulting string. Actually, the
\-newline will be replaced by one space, so each line will be indented one space. You can leave those off and the page will display the same
in the browser, but when you view the page source, you'll see the indenting. Or you could not indent the lines in the string, but then your code
looks somewhat odd.
The /demo/time procedure just returns the result ofclock format. It doesn't even bother adding<html>, <head>, or <body> tags, which you can
get away with in today's browsers. A simple result like this is also useful if you are using programs to fetch information via HTTP requests to
your application.
Using Query Data
Application Direct URL handlers have their parameters automatically assigned to values from the query data. Like any Tcl procedure, your
Application Direct URL procedure can have named parameters, named parameters with default values, and the args parameter. The server
matches the names of form values with names of your procedure parameters in order to assign their values. There are three cases:
The name of the procedure parameter matches the name of a query data item. The query value is assigned to the parameter.
The name of the procedure parameter does not appear in the query data. The parameter is assigned the empty string or its default
value, if it has one. The /demo/time procedure is defined with an optional format argument. If a format value is present in the query
data, then it overrides the default value given in the procedure definition.
The query data item does not match any of the parameters. If the procedure has an args parameter as its last parameter, then the
name and value of the query data item are appended to the args value. Otherwise, the query value is simply ignored. For example,
the /demo/echo procedure's args parameter gets filled in with a name-value list of all query data.
You can see that missing arguments or extra arguments do not cause errors. If you want to do strict parameter checking, then just use args
and check the name-value query list yourself.
Here is another example to illustrate the different ways that form data is assigned to procedure parameters. Suppose you have an Application
Direct procedure declared like this:
proc Demo/param { a b {c cdef} args} { body }
You could create an HTML form that had elements named a, b, and c, and specified /demo/param for theACTION parameter of the FORM tag.
Or you could type the following into your browser to embed the query data right into the URL:
/demo/param?a=5&b=7&c=red&d=%7ewelch&e=two+words
The ? separates the query data from the URL, and each query item is separated by&. In this case, when your procedure is called,a is 5, b is 7, c
is red, and the args parameter becomes a list of:
d ~welch e {two words}
The %7e and the + are special codes for nonalphanumeric characters in the query data. The+ becomes a space, and the%xx sequence is
replaced by the character with character code xx (e.g., %7e becomes ~). Normally, this encoding is taken care of automatically by the Web
browser when it gets data from a form and passes it to the Web server. However, if you type query data directly or format URLs with complex
query data in them, then you need to encode special values as we did here. Use the Url_Encode procedure to encode URLs that you put into
Web pages. The Web server automatically decodes the values as it makes the assignments to the Application Direct URL procedure
parameters.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
If a parameter does not match the query data, it gets its default value from the procedure definition, or it gets the empty string. Consider this
example:
/demo/param?b=5
In this case, a is "", b is 5, c is cdef, and args is an empty list.
Returning Other Content Types
The default content type for Application Direct URLs is text/html. You can specify other content types by using a global variable with the same
name as your procedure. (Yes, this is a crude way to craft an interface.) Example 18-4 shows part of the faces.tcl file that implements an
interface to a database of picons — personal icons — that is organized by user and domain names. The idea is that the database contains
images corresponding to your email correspondents. The Faces_ByEmail procedure, which is not shown, looks up an appropriate image file.
The Application Direct procedure is Faces/byemail, and it sets the global variableFaces/byemail to the correct Content-Type value based on
the filename extension. The mapping from extension to content type is implemented by the Mtype procedure (mtype.tcl). MIME is the
multimedia content standard for email, and it originated the various content types now also used in HTTP, hence the term "MIME type."
Example 18-4 Alternate types for Application Direct URLs
Direct_Url /faces Faces
proc Faces/byemail {email} {
global Faces/byemail
set filename [Faces_ByEmail $email]
set Faces/byemail [Mtype $filename]
set in [open $filename]
fconfigure $in -translation binary
set X [read $in]
close $in
return $X
}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Document Types
The Document domain (doc.tcl) maps URLs onto files and directories. It provides more ways to extend the server by registering different
document type handlers. You can make up new types to support your application. Example 18-5 shows the pieces needed to create a
handler for a fictitious document type application/myjunk that is invoked to handle files with the.junk suffix. Use the Mtype_Add procedure to
register the mapping from file suffix to document type:
Example 18-5 A sample document type handler
# Register the mapping from suffix to MIME type
Mtype_Add application/myjunk .junk
# Define the document handler procedure
# path is the name of the file on disk
# suffix is part of the URL after the domain prefix
# sock is the handle on the client connection
proc Doc_application/myjunk {path suffix sock} {
upvar #0 Httpd$sock data
# data(url) is more useful than the suffix parameter.
# Use the contents of file $path to compute a page
set contents [somefunc $path]
# Determine your content type
set type text/html
# Return the page
Httpd_ReturnData $sock $type $data
}
The server finds the document handler in a two-step process. First, the type of a file is determined by its suffix. The mime.types file contains a
map from suffixes to MIME types such as text/html or image/gif. This map is controlled by theMtype module in mtype.tcl. Second, the server
checks for a Tcl procedure with the appropriate name:
Doc_mimetype
The matching procedure, if any, is called to handle the URL request. The procedure should use routines in the Httpd module to return data for
the request. If there is no matching Doc_mimetype procedure, then the default document handler usesHttpd_ReturnFile and specifies the
Content Type based on the file extension. This is the heart of the default document handler:
Httpd_ReturnFile $sock [Mtype $path] $path
As another example, the HTML+Tcl templates use the .tml suffix that is mapped to the application/x-tcl-template type. You can find the
document handler Doc_application/x-tcl-template in doc.tcl. The TclHttpd distribution also includes support for files with a.snmp extension that
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
implements a template-based Web interface to the Scotty SNMP Tcl extension.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
HTML + Tcl Templates
The template system uses HTML pages that embed Tcl commands and Tcl variable references. The server replaces these using the subst
command and returns the results. The server comes with a general template system, but using subst is so easy you could create your own
template system. The TclHttpd template framework has these components:
Each page.html can have a corresponding page.tml template file. This feature is enabled with theDoc_CheckTemplates command
in the server's configuration file. Normally, the server returns the page.html file unless the corresponding page.tml file has been
modified more recently. In this case, the server processes the template with subst, caches the result in thepage.html file, and
returns the result.
A dynamic template (e.g., a form handler) must be processed each time it is requested. If you put the Doc_Dynamic command into
your page, it turns off the caching of the result in the page.html page. The server responds to a request for apage.html page by
processing the page.tml page. Or you can just reference the page.tml file directly and the server will always processes the template.
The server creates a page global Tcl variable that has context about the page being processed.Table 18-6 lists the elements of the
page array.
The server initializes the env global Tcl variable with similar information, but in the standard way for CGI scripts.Table 18-7 lists the
elements of the env array that are set byCgi_SetEnv in cgi.tcl.
The server initializes the ncgi module so you can use thencgi procedures to access query data.
The server supports per-directory .tml files that contain Tcl source code. These files are designed to contain procedure definitions
and variable settings that are shared among pages. The name of the file is simply ".tml", with nothing before the period. This is a
standard way to hide files in UNIX, but it can be confusing to talk about the per-directory .tml files and the page.tml templates that
correspond to page.html pages. Before processing each page.tml file, the server will source the.tml files in all directories leading
down to the directory containing the template file. The server compares the modify time of these files against the template file and
will process the template if these .tml files are newer than the cachedpage.html file. So, by modifying the.tml file in the root of your
URL hierarchy, you invalidate all the cached page.html files.
Where to Put Your Tcl Code
There are three places you can put the code of your application: directly in your template pages, in the per-directory .tml files, or in the library
directory. There are pros and cons to each:
The library directory is where you should put most of your code. The library directory is specified with the -library command line
argument, and the server loads all files in the library upon startup. The advantage of putting procedure definitions in the library is
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
that they are defined one time but executed many times. This works well with the Tcl byte-code compiler. The disadvantage is that
if you modify procedures in these files, you have to explicitly source them into the server for these changes to take effect. You can
restart the server, or you can use the /debug/source URL described on page 282 to reload source files into the running server.
The .tml files are best for variable definitions that you want to share among pages in a directory, or as a staging area for
procedures during development. The advantage of putting code into the per-directory .tml files is that changes are picked up
immediately with no effort on your part. The server automatically checks if these files are modified and sources them each time it
processes your templates. However, using .tml files tends to scatter your code around the URL tree and can make it harder to
maintain.
I try to put as little code as possible directly in my page.tml template files. It is awkward to put lots of code there, and you cannot
share procedures and variable definitions easily with other pages. Instead, my goal is to have only procedure calls in the template
files, and put the procedure definitions elsewhere. If you want control structures in your page, such as if and foreach, you may
want to use the version of those commands provided by the html package, as described on page 277.
Templates for Site Structure
The next few examples show a simple template system used to maintain a common "look and feel" across the pages of a site. The key to a
successful template system is a data structure that defines the structure of the site, and some procedures that generate standard
navigational HTML structure for your pages. Once you do this, then you can easily add new pages by updating your data structure. The
template procedures automatically reformat your site to include the new pages. Example 18-6 shows a simple one-level site definition that is
kept in the root .tml file. This structure lists the title and URL of each page in the site:
Example 18-6 A one-level site structure
set site(pages) {
Home
/index.html
"Ordering Computers"/ordering.html
"New Machine Setup" /setup.html
"Adding a New User" /newuser.html
"Network Addresses" /network.html
}
Of course, your Web site is likely to have more pages and a more elaborate structure. For example, you might have several main sections,
each with a collection of pages, or even a three-level hierarchy of pages. Example 18-7 shows another simple data structure to define a
two-level structure. The site(sections) variable stores the names and URLs of the main sections. For each section, there is an element ofsite
that lists the pages in that section. Only the About section is shown in the example:
Example 18-7 A two-level site structure
set site(sections) {
About /about
Products /products
Support /support
}
set site(About) {
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Company company.html
Contacts contacts.html
Directions directions.html
}
In practice, you may want to include more information in your data structure to help you generate HTML. For example, if you have graphics for
the main sections, you may need to record their size. Whatever you need, collect it into your data structures and then generate the HTML
from procedures. You can quickly give your whole site a face lift with new graphics by changing the template procedures that generate your
pages. In contrast, if you hand-code all your pages, it can take months instead of days.
Example 18-8 shows a sample template file for the one-level structure shown inExample 18-6. Each page includes two commands,SitePage
and SiteFooter, that generate HTML for the navigational part of the page. Between these commands is regular HTML for the page content:
Example 18-8 A HTML + Tcl template file
[SitePage "New Machine Setup"]
This page describes the steps to take when setting up a new
computer in our environment. See
[SiteLink "Ordering Computers"]
for instructions on ordering machines.
<ol>
<li>Unpack and setup the machine.
<li>Use the Network control panel to set the IP address
and hostname.
<!-- Several steps omitted -->
<li>Reboot for the last time.
</ol>
[SiteFooter]
The SitePage procedure takes the page title as an argument. It generates HTML to implement a standard navigational structure.Example 18-9
has a simple implementation of SitePage:
Example 18-9 SitePage template procedure, version 1
proc SitePage {title} {
global site
set html "<html><head><title>$title</title></head>\n"
append html "<body bgcolor=white text=black>\n"
append html "<h1>$title</h1>\n"
set sep ""
foreach {label url} $site(pages) {
append html $sep
if {[string compare $label $title] == 0} {
append html "$label"
} else {
append html "<a href='$url'>$label</a>"
}
set sep " | "
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
return $html
}
The foreach loop that computes the simple menu of links turns out to be useful in many places.Example 18-10 splits out the loop and uses it in
a new version of SitePage along with the SiteFooter procedure. This version of the templates creates a left column for the navigation and a
right column for the page content. The example also puts a few more visual elements (e.g., page background color) into the site array so you
can easily maintain them:
Example 18-10 SiteMenu and SiteFooter template procedures
array set site {
bg
white
fg
black
mainlogo /images/mainLogo.gif
}
proc SitePage {title} {
global site
set html "<html><head><title>$title</title></head>\n\
<body bgcolor=$site(bg) text=$site(fg)>\n\
<!-- Two Column Layout -->\n\
<table cellpadding=0>\n\
<tr><td>\n\
<!-- Left Column -->\n\
<img src='$site(mainlogo)'>\n\
<font size=+1>\n\
[SiteMenu <br> $site(pages)]\n\
</font>\n\
</td><td>\n\
<!-- Right Column -->\n\
<h1>$title</h1>\n\
<p>\n"
return $html
}
proc SiteFooter {} {
global site
set html "<p><hr>\n\
<font size=-1>[SiteMenu | $site(pages)]</font>\n\
<!-- Close Right Column -->\n\
</td></tr></table>\n"
return $html
}
proc SiteMenu {sep list} {
global page
set s ""
set html ""
foreach {label url} $list {
if {[string compare $page(url) $url] == 0} {
append html $s$label
} else {
append html "$s<a href='$url'>$label</a>"
}
set s $sep
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
return $html
}
There are many other applications for "macros" that make repetitive HTML coding chores easy. For example, take the SiteLink procedure call
in Example 18-8. Instead of hand-coding the <A> tag with the link to /ordering.html, the page uses the SiteLink procedure to format the link with
a consistent label for the link. Using the procedure also means that the page will automatically get updated if you change the URL associated
with the ordering page by modifying site(pages). Example 18-11 shows SiteLink:
Example 18-11 The SiteLink procedure
proc SiteLink {label} {
global site
array set map $site(pages)
if {[info exist map($label)]} {
return "<a href='$map($label)'>$label</a>"
} else {
return $label
}
}
Using Variables for Important Site Information
Another useful feature of templates is the ability to embed variable references in your pages. Instead of hard coding the sales phone number,
or the current product version number, or even the product name, you can put variables into your pages. For example, SiteLink and SitePage
take a parameter that is the page title. Instead of hard coding your page titles, you could keep all of your page titles in an array, and use array
references everywhere. That puts all the text in one place and makes it easy to change. The array definition would look something like this:
array set title {
Home Home
Order "Ordering Computers"
Setup "New Machine Setup"
AddUser "Adding a New User"
Network "Network Addresses"
}
And the calls to SitePage or SiteLink could be made like this:
[SitePage $title(Order)]
The .tml pages are a good place to define the variables because the definitions are shared by all pages in that directory, and in any
subdirectories. Also, the definitions in the per-directory .tml override any definitions that come from the top-level.tml file at the root of your
URL tree. Changing the definition of the variable in the .tml file immediately updates all the pages that share it.
The main drawback to variable references is the clash with $ in pricing. If you put $10 into a page.tml file, it will raise an error (unless the
variable 10 is defined). It turns out that you want to generate prices from some database anyway, so you should avoid hard coding prices into
your pages anyway. It is much better to put [price T-shirt] or $price(T-shirt) into your page than $10, although if you must do that, just quote
the $ with a backslash, \$10.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Form Handlers
HTML forms and form-handling programs go together. The form is presented to the user on the client machine. The form handler runs on the
server after the user fills out the form and presses the submit button. The form presents input widgets like radiobuttons, checkbuttons,
selection lists, and text entry fields. Each of these widgets is assigned a name, and each widget gets a value based on the user's input. The
form handler is a program that looks at the names and values from the form and computes the next page for the user to read.
CGI is a standard way to hook external programs to Web servers for the purpose of processing form data. CGI has a special encoding for
values so that they can be transported safely. The encoded data is either read from standard input or taken from the command line. The CGI
program decodes the data, processes it, and writes a new HTML page on its standard output. Chapter 3 describes writing CGI scripts in Tcl.
TclHttpd provides alternatives to CGI that are more efficient because they are built right into the server. This eliminates the overhead that
comes from running an external program to compute the page. Another advantage is that the Web server can maintain state between client
requests in Tcl variables. If you use CGI, you must use some sort of database or file storage to maintain information between requests.
Application Direct Handlers
The server comes with several built-in form handlers that you can use with little effort. The /mail/forminfo URL will package up the query data
and mail it to you. You use form fields to set various mail headers, and the rest of the data is packaged up into a Tcl-readable mail message.
Example 18-12 shows a form that uses this handler. Other built-in handlers are described starting at page 281.
Example 18-12 Mail form results with /mail/forminfo
<form action=/mail/forminfo method=post>
<input type=hidden name=sendto value=mailreader@my.com>
<input type=hidden name=subject value="Name and Address">
<table>
<tr><td>Name</td><td><input name=name></td></tr>
<tr><td>Address</td><td><input name=addr1></td></tr>
<tr><td> </td><td><input name=addr2></td></tr>
<tr><td>City</td><td><input name=city></td></tr>
<tr><td>State</td><td><input name=state></td></tr>
<tr><td>Zip/Postal</td><td><input name=zip></td></tr>
<tr><td>Country</td><td><input name=country></td></tr>
</table>
</form>
The mail message sent by /mail/forminfo is shown in Example 18-13.
Example 18-13 Mail message sent by /mail/forminfo
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
To: mailreader@my.com
Subject: Name and Address
data {
name {Joe Visitor}
addr1 {Acme Company}
addr2 {100 Main Street}
city {Mountain View}
state California
zip 12345
country USA
}
The email message is designed to be easily processed by a Tcl program. You can use a mail processor like procmail to filter all mail with a
given Subject or To field to a program for processing. It is easy to write a script that strips the headers, defines a data procedure, and uses
eval to process the message body. Whenever you send data via email, if you format it with Tcl list structure, you can process it quite easily.
The basic structure of such a mail reader procedure is shown in Example 18-14:
Example 18-14 Processing mail sent by /mail/forminfo
# Assume the mail message is on standard input
set X [read stdin]
# Strip off the mail headers, when end with a blank line
if {[regsub {.*?\n\ndata} $X {data} X] != 1} {
error "Malformed mail message"
}
proc data {fields} {
foreach {name value} $fields {
# Do something
}
}
# Process the message.
eval $X
The raw eval in the mail handler is dangerous. It will be fine if the only source of email to that program is the/mail/forminfo URL handler.
However, an attacker could send you an email that results in arbitrary Tcl commands being evaluated by your mail processor. The safe way
to process the email is with a safe interpreter, which is described in Chapter 19. Example 18-15 adds just a few commands to create a safe
interpreter for processing the incoming data. The data command is evaluated in the trusted interpreter by the alias mechanism. All other
commands in the email are evaluated in the safe interpreter, and any malicious commands simply raise Tcl errors but cause no harm:
Example 18-15 Processing mail sent by /mail/forminfo, Safe-Tcl version
# Assume the mail message is on standard input
set X [read stdin]
# Strip off the mail headers, when end with a blank line
if {[regsub {.*?\n\ndata} $X {data} X] != 1} {
error "Malformed mail message"
}
proc data {fields} {
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
foreach {name value} $fields {
# Do something
}
}
# Create the safe interpreter
set i [interp create -safe]
# Link the data command in the safe interpreter to the
# data procedure in this interpreter
interp alias $i data {} data
# Process the message in the safe interpreter
interp eval $i $X
Template Form Handlers
The drawback of using Application Direct URL form handlers is that you must modify their Tcl implementation to change the resulting page.
Another approach is to use templates for the result page that embed a command that handles the form data. The Mail_FormInfo procedure,
for example, mails form data. It takes no arguments. Instead, it looks in the query data for sendto and subject values, and if they are present,
it sends the rest of the data in an email. It returns an HTML comment that flags that mail was sent.
When you use templates to process form data, you need to turn off result caching because the server must process the template each time
the form is submitted. To turn off caching, embed the Doc_Dynamic command into your form handler pages, or set thepage(dynamic)
variable to 1. Alternatively, you can simply post directly to the file.tml page instead of to the file.html page.
Self-Posting Forms
This section illustrates a self-posting form. This is a form on a page that posts the form data to back to the same page. The page embeds a
Tcl command to check its own form data. Once the data is correct, the page triggers a redirect to the next page in the flow. This is a powerful
technique that I use to create complex page flows using templates. Of course, you need to save the form data at each step. You can put the
data in Tcl variables, use the data to control your application, or store it into a database. TclHttpd comes with a Session module, which is one
way to manage this information. For details, you should scan the session.tcl file in the distribution.
Example 18-16 shows the Form_Simple procedure that generates a simple self-checking form. Its arguments are a unique ID for the form, a
description of the form fields, and the URL of the next page in the flow. The field description is a list with three elements for each field: a
required flag, a form element name, and a label to display with the form element:
Example 18-16 A self-checking form procedure
proc Form_Simple {id fields nextpage} {
global page
if {![html::varEmpty formid]} {
# Incoming form values, check them
set check 1
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
} else {
# First time through the page
set check 0
}
set html "<!-- Self-posting. Next page is $nextpage -->\n"
append html "<form action=\"$page(url)\" method=post>\n"
append html "<input type=hidden name=formid value=$id>\n"
append html "<table border=1>\n"
foreach {required key label} $fields {
append html "<tr><td>"
if {$check && $required && [html::varEmpty $key]} {
lappend missing $label
append html "<font color=red>*</font>"
}
append html "</td><td>$label</td>\n"
append html "<td><input [html::formValue $key]></td>\n"
append html "</tr>\n"
}
append html "</table>\n"
if {$check} {
if {![info exist missing]} {
# No missing fields, so advance to the next page.
# In practice, you must save the existing fields
# at this point before redirecting to the next page.
Doc_Redirect $nextpage
} else {
set msg "<font color=red>Please fill in "
append msg [join $missing ", "]
append msg "</font>"
set html <p>$msg\n$html
}
}
append html "<input type=submit>\n</form>\n"
return $html
}
The Form_Simple procedure does two things at once: it computes the HTML form, and it also checks if the required fields are present. It uses
some procedures from the html module to generate form elements that retain values from the previous page. If all the required fields are
present, then it triggers a redirect by calling Doc_Redirect. Example 18-17 shows a page template that calls Form_Simple with the required
field description:
Example 18-17 A page with a self-checking form
<html><head>
<title>Name and Address Form</title>
</head>
<body bgcolor=white text=black>
<h1>Name and Address</h1>
Please enter your name and address.
[Form_Simple nameaddr {
1 name "Name"
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
1 addr1 "Address"
0 addr2" "Address"
1 city "City"
0 state "State"
1 zip "Zip Code"
0 country "Country"
} nameok.html]
</body></html>
The html Package
The Standard Tcl Library, tcllib, includes an html package that is designed to support page generation and self-posting forms. Thehtml
package works in conjunction with the ncgi package, which was introduced inChapter 3. The Form_Simple procedure uses html::varEmpty to
test if particular form values are present in the query data. For example, it tests to see whether the formid field is present so that the
procedure knows whether or not to check for the rest of the fields. The html::formValue procedure is useful for constructing form elements on
self-posting form pages. It returns:
name="name" value="value"
The value is the value of form element name based on incoming query data, or just the empty string if the query value forname is undefined.
As a result, the form can post to itself and retain values from the previous version of the page. It is used like this:
<input type=text [html::formValue name]>
The html::checkValue and html::radioValue procedures are similar to html::formValue, but are designed for checkbuttons and radio buttons.
The html::select procedure formats a selection list and highlights the selected values.
The html package includes a versions of foreach and if that are designed for use in templates. These commands perform asubst on their body
instead of evaluating it. This lets you put HTML with variable and command references into the body to build up results. Example 18-18
shows the html::foreach procedure used to generate a table with several rows. Note that you don't have to worry about the
$ in the prices
because they are inside the braces of the html::foreach value list:
Example 18-18 Generating a table with html::foreach
<TABLE BORDER=1>
[html::foreach {product price} {
T-Shirt $10.00
YoYo
$7.50
Footbag $15.00
}{
<TR>
<TD>$product</TD>
<TD ALIGN=RIGHT><FONT FACE=courier>$price</FONT></TD>
</TR>
}
</TABLE>
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Programming Reference
This section summarizes many of the more useful functions defined by the server. These tables are not complete, however. You are
encouraged to read through the code to learn more about the features offered by the server. A simple naming convention is used to
distinguish procedures that are private to a file (e.g., HttpdEvent) and procedures that are meant to be used by other modules or by the main
application (e.g., Httpd_Server). The underscore after the module prefix indicates that the procedure is public.
This section does not detail the ncgi and html packages, which are quite useful to the TclHttpd programmer. There are doc files that come
with tcllib, and you can find man pages for the tcllib packages in the www.tcl.tk manual section.
Table 18-1 shows Httpd functions used when returning pages to the client.
Table 18-1. Httpd support procedures
Httpd_Error sock code
Returns a simple error page to the client. The code is a numeric error code such as 404 or 500.
Httpd_ReturnData sock type data
Returns a page with Content-Typetype and content data.
Httpd_ReturnFile sock type file
Returns a file with Content-Type type.
Httpd_Redirect newurl sock
Generates a 302 error return with a Location ofnewurl.
Httpd_SelfUrl url
Expands url to include the proper http://server:port prefix to reference the current server.
Table 18-2 summarizes a few useful procedures provided by theUrl module (url.tcl). The Url_DecodeQuery is used to decode query data into a
Tcl-friendly list. The Url_Encode procedure is useful when encoding values directly into URLs.URL encoding is discussed in more detail on
page 262
Table 18-2. Url support procedures
Url_DecodeQuery query
Decodes a www-url-encoded query string and returns a name, value list.Depreciated. This is equivalent
to ncgi::nvlist, which takes no arguments.
Url_Encode value
Returns value encoded according to the www-url-encoded standard.
Url_PrefxInstall prefix handler
Registers handler as the handler for all URLs that begin withprefix. The handler is invoked with two
?-thread bool? ?-callback cmd?
?-readpost bool?
additional arguments: sock, the handle to the client, and suffix, the part of the URL after prefix. Use
-thread 1 to have the handler run in a worker thread. Use-callback cmd to register a callback invoked at
the very end of URL processing. Use -readpost 0 to disable pre-reading post data.
The Doc module procedures for configuration are listed inTable 18-3.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 18-3. Doc procedures for configuration
Doc_Root ?directory?
Sets or queries the directory that corresponds to the root of the URL hierarchy.
Doc_AddRoot virtual
Maps the file systemdirectory into the URL subtree starting at virtual.
directory
Doc_ErrorPage file
Specifies a file relative to the document root used as a simple template for error messages. This is
processed by DocSubstSystem file in doc.tcl.
Doc_CheckTemplates how
If how is 1, then .html files are compared against corresponding.tml files and regenerated, if necessary.
Doc_IndexFile pattern
Registers a file name pattern that will be searched for the default index file in directories.
Doc_NotFoundPage file
Specifies a file relative to the document root used as a simple template for page not found messages. This
is processed by DocSubstSystem file in doc.tcl.
Doc_PublicHtml dirname
Defines the directory used for each user's home directory. When a URL such as ~user is specified, the
dirname under their home directory is accessed.
Doc_TemplateLibrary
Adds directory to the auto_path so that the source files in it are available to the server.
directory
Doc_TemplateInterp interp
Specifies an alternate interpreter in which to process document templates (i.e.,.tml files.)
Doc_Webmaster ?email?
Sets or queries the email for the Webmaster.
The Doc module procedures for generating results are listed inTable 18-4
Table 18-4. Doc procedures for generating responses
Doc_Error sock errorInfo
Generates a 500 response on sock based on the template registered with Doc_ErrorPage. errorInfo is a copy
of the Tcl error trace after the error.
Doc_NotFound sock
Generates a 404 response onsock by using the template registered withDoc_NotFoundPage.
Doc_Subst sock file
Performs a subst on the file and return the resulting page onsock. interp specifies an alternate Tcl interpreter.
?interp?
The Doc module also provides procedures for cookies and redirects that are useful in document templates. These are described in
Table 18-5.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 18-5. Doc procedures that support template processing
Doc_Coookie name
Returns the cookie name passed to the server for this request, or the empty string if it is
not present.
Doc_Dynamic
Turns off caching of the HTML result. Meant to be called from inside a page template.
Doc_IsLinkToSelf url
Returns 1 if the url is a link to the current page.
Doc_Redirect newurl
Raises a special error that aborts template processing and triggers a page redirect to
newurl.
Doc_SetCookie -name name -value value -path
Sets cookie name with the given value that will be returned to the client as part of the
path -domain domain -expires date
response. The path and domain restrict the scope of the cooke. Thedate sets an
expiration date.
Table 18-6 shows the initial elements of the page array that are defined during the processing of a template.
Table 18-6. Elements of the page array
query
The decoded query data in a name, value list. Also available throughncgi.
dynamic
If 1, the results of processing the template are not cached in the corresponding.html file.
filename
The file system pathname of the requested file (e.g.,/usr/local/htdocs/tclhttpd/index.html).
template
The file system pathname of the template file (e.g.,/usr/local/htdocs/tclhttpd/index.tml).
url
The part of the URL after the server name (e.g.,/tclhttpd/index.tml).
root
A relative path from the template file back to the root of the URL tree.
Table 18-7 shows the elements of the env array. These are defined during CGI requests, Application Direct URL handlers, and page template
processing:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 18-7. Elements of the env array
AUTH_TYPE
Authentication protocol (e.g., Basic).
CONTENT_LENGTH
The size of the query data.
CONTENT_TYPE
The type of the query data.
DOCUMENT_ROOT
File system pathname of the document root.
GATEWAY_INTERFACE
Protocol version, which is CGI/1.1.
HTTP_ACCEPT
The Accept headers from the request.
HTTP_AUTHORIZATION
The Authorization challenge from the request.
HTTP_COOKIE
The cookie from the request.
HTTP_FROM
The From: header of the request.
HTTP_REFERER
The Referer indicates the previous page.
HTTP_USER_AGENT
An ID string for the Web browser.
PATH_INFO
Extra path information after the template file.
PATH_TRANSLATED
The extra path information appended to the document root.
QUERY_STRING
The form query data.
REMOTE_ADDR
The client's IP address.
REMOTE_USER
The remote user name specified by Basic authentication.
REQUEST_METHOD
GET, POST, or HEAD.
REQUEST_URI
The complete URL that was requested.
SCRIPT_NAME
The name of the current file relative to the document root.
SERVER_NAME
The server name, e.g., www.beedub.com.
SERVER_PORT
The server's port, e.g., 80.
SERVER_PROTOCOL
The protocol (e.g., http or https).
SERVER_SOFTWARE
A software version string for the server.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Standard Application Direct URLs
The server has several modules that provide Application Direct URLs. These Application Direct URLs let you control the server or examine its
state from any Web browser. You can look at the implementation of these modules as examples for your own application.
Status
The /status URL is implemented in the status.tcl file. The status module implements the display of hit counts, document hits, and document
misses (i.e., documents not found). The Status_Url command enables the Application Direct URLs and assigns the top-level URL for the
status module. The default configuration file contains this command:
Status_Url /status
Table 18-8 shows the URLs implemented by the status module:
Table 18-8. Status Application Direct URLs
/status
Main status page showing summary counters and hit count histograms.
/status/doc
Shows hit counts for each page. This page lets you sort by name or hit count, and limit files by patterns.
/status/domain
Shows hit counts for each domain in the server.
/status/hello
A trivial URL that returns "hello".
/status/notfound
Shows miss counts for URLs that users tried to fetch.
/status/size
Displays an estimated size of Tcl code and Tcl data used by the TclHttpd program.
/status/text
This is a version of the main status page that doesn't use the graphical histograms of hit counts.
Debugging
The /debug URL is implemented in the debug.tcl file. The debug module has several useful URLs that let you examine variable values and
other internal state. It is turned on with this command in the default configuration file:
Debug_Url /debug
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 18-9 lists the /debug URLs. These URLs often require parameters that you can specify directly in the URL. For example, the/debug/echo
URL echoes its query parameters:
http://yourserver:port/debug/echo?name=value&name2=val2
Note: The debug URL is active in the default configuration. If it makes you nervous, then delete the call toDebug_Url from the httpdthread.tcl
file.
The sample URL tree that is included in the distribution includes the file htdocs/hacks.html. This file has several small forms that use the
/debug URLs to examine variables and source files. It may seem dangerous to have these facilities, but I reason that because my source
directories are under my control, it cannot hurt to reload any source files. In general, the library scripts contain only procedure definitions and
no global code that might reset state inappropriately. In practice, the ability to tune (i.e., fix bugs) in the running server has proven useful to
me on many occasions. It lets you evolve your application without restarting it!
Table 18-9. Debug Application Direct URLs
/debug/after
Lists the outstanding after events.
/debug/dbg
Connects to TclPro Debugger. This takes a host and port parameter. You need to install prodebug.tcl from TclPro into
the server's script library directory.
/debug/echo
Echoes its query parameters. Accepts a title parameter.
/debug/errorInfo
Displays the errorInfo variable along with the server's version number and Webmaster email. Acceptstitle and errorInfo
arguments.
/debug/parray
Displays a global array variable. The name of the variable is specified with theaname parameter.
/debug/pvalue
A more general value display function. The name of the variable is specified with the aname parameter. This can be a
variable name, an array name, or a pattern that matches several variable names.
/debug/raise
Raises an error (to test error handling). Any parameters become the error string.
/debug/source
Sources a file from either the server's main library directory or the Doc_TemplateLibrary directory. The file is specified
with the source parameter.
Example 18-19 shows the implementation of /debug/source. You can see that it limits the files to the main script library and to the script
library associated with document templates.
Example 18-19 The /debug/source Application Direct URL implementation
proc Debug/source {source} {
global Httpd Config errorInfo
set source [file tail $source]
set dirlist $Httpd(library) ;# TclHttpd implementation
lappend dirlist $Config(lib) ;# Application custom code
foreach dir $dirlist {
set file [file join $dir $source]
if {[file exists $file]} break
}
set error [catch {uplevel #0 [list source $file]} result]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set html "<title>Source $source</title>\n"
if {$error} {
append html "<H1>Error in $source</H1>\n"
append html "<pre>$result<p>$errorInfo</pre>"
} else {
append html "<H1>Reloaded $source</H1>\n"
append html "<pre>$result</pre>"
}
return $html
}
Sending Email
The /mail URL is implemented in the mail.tcl file. The mail module implements various form handlers that email form data. Currently, it is
UNIX-specific because it uses /usr/lib/sendmail to send the mail. It is turned on with this command in the default configuration file:
Mail_Url /mail
The Application Direct URLs shown in Table 18-10 are useful form handlers. You can specify them as theACTION parameter in your<FORM>
tags. The mail module provides two Tcl procedures that are generally useful. The MailInner procedure is the one that sends mail. It is called
like this:
Table 18-10. Application Direct URLS that email form results
/mail/bugreport
Sends email with the errorInfo from a server error. It takes anemail parameter for the destination address and an
errorInfo parameter. Any additional arguments get included into the message.
/mail/forminfo
Sends email containing form results. It requires these parameters: sendto for the destination address, subject for the
mail subject, href and label for a link to display on the results page. Any additional arguments are formatted with the
Tcl list command for easy processing by programs that read the mail.
/mail/formdata
This is an older form of /mail/forminfo that doesn't format the data into Tcl lists. It requires only theemail and subject
parameters. The rest are formatted into the message body.
MailInner sendto subject from type body
The sendto and from arguments are email addresses. The type is the MIME type (e.g., text/plain or text/html) and appears in a Content-Type
header. The body contains the mail message without any headers.
The Mail_FormInfo procedure is designed for use in HTML+Tcl template files. It takes no arguments but instead looks in current query data
for its parameters. It expects to find the same arguments as the /mail/forminfo direct URL. Using a template withMail_FormInfo gives you
more control over the result page than posting directly to /mail/forminfo, and is illustrated in Example 18-12 on page 272.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The TclHttpd Distribution
Get the TclHttpd distribution from the CD-ROM, or find it on the Internet at:
ftp://ftp.tcl.tk/pub/tcl/httpd/
http://www.tcl.tk/software/tclhttpd/
http://www.sourceforge.net/projects/tclhttpd
Quick Start
Unpack the tar file or the zip file, and you can run the server from the httpd.tcl script in the bin directory. On UNIX:
tclsh bin/httpd.tcl -port 80
This command will start the Web server on the standard port (80). On UNIX, you need to be root to run a server on this port. By default
TclHttpd uses port 8015 instead. If you run it with the -help flag, it will tell you what command line options are available. If you usewish instead
of tclsh, then a simple Tk user interface is displayed that shows how many hits the server is getting.
On Windows, you can double-click the httpd.tcl script to start the server. It will usewish and display the user interface. Again it will start on port
8015. You will need to create a shortcut that passes the -port argument, or edit the associated configuration file to change this. Configuring
the server is described later.
Once you have the server running, you can connect to it from your Web browser. Use this URL if you are running on the default
(nonstandard) port:
http://hostname:8015/
If you are running without a network connection, you may need to specify 127.0.0.1 for the hostname. This is the "localhost" address and will
bypass the network subsystem.
http://127.0.0.1:8015/
Inside the Distribution
The TclHttpd distribution is organized into the following directories:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
bin — This has sample start-up scripts and configuration files. Thehttpd.tcl script runs the server. Thetclhttpd.rc file is the standard
configuration file.
bin/mini — This has a few tiny versions of the server that provide a basic server in about 300 lines of code. Use these as a starting
point by modifying the HttpdRespond procedure.
bin/test — This has a number of test scripts, including thetorture.tcl file that can fetch many URLs at once from a server.
certs — This has sample certificates you can use to test a secure server forhttps URLs. If you have your own server certificates,
put the server.pem file here.
config — This contains autoconf support used by C extensions you can build with the server.
custom — This is where you put your own custom code. Files here are automatically loaded by the server on startup. This
contains a few samples.
doc — This has a UNIX-style manual page for how to run the server.
htaccess — This has sample access control files.
htdocs — This is a sample URL tree that demonstrates the features of the Web server. There is also some documentation there.
One directory to note is htdocs/libtml, which is the standard place to put site-specific Tcl scripts used with the Tcl+HTML template
facility.
lib — This has all the Tcl sources. In general, each file provides a package. You will see thepackage require commands partly in
bin/httpd.tcl and partly inbin/httpdthread.tcl.
src — There are a few C source files for a some optional packages. These have been precompiled for some platforms, and you
can find the compiled libraries under src/Solaris and src/Linux.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Server Configuration
TclHttpd configures itself with two main steps: setting configuration parameters and loading packages. The configuration step uses a
configuration file and command line arguments to set basic configuration parameters. The default configuration file is named tclhttpd.rc in the
same directory as the start-up script (i.e., bin/tclhttpd.rc). Specify an alternate configuration file with the-config command line argument. You
can override the configuration file with additional command line arguments, which are described in Table 18-11. The configuration values
from the file and the command line are copied into the Config Tcl array.
Package loading is split into two parts. The main bin/httpd.tcl script loads some core packages. The rest are loaded in thebin/httpdthread.tcl
script. The reason for the split is to try to isolate the core of the server from application-specific functions. In addition, in the threaded version
of the server, every thread loads and runs the bin/httpdthread.tcl script. You can specify an alternate package loading script with the-main
command line argument.
For example, to start the server for the document tree under /usr/local/htdocs and your own email address as Webmaster, you can execute
this command to start the server:
tclsh httpd.tcl -docRoot /usr/local/htdocs -webmaster welch
If you are using the Tclkit version described in Chapter 22:
tclkit tclhttpd.kit -docRoot /usr/local/htdocs -webmaster welch
Alternatively, you can put these settings into a configuration file, and start the server with that configuration file:
tclsh httpd.tcl -config mytclhttpd.rc
Command Line Arguments
There are several parameters you may need to set for a standard Web server. These are shown below in Table 18-11. The command line
values are mapped into the Config array by the httpd.tcl startup script.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 18-11. Basic TclHttpd parameters
Parameter
Command Option
Config Variable
Port number. The default is 8015.
-port number
Config(port)
Server name. The default is [info hostname].
-name name
Config(name)
IP address. The default is 0, for "any address".
-ipaddr address
Config(ipaddr)
Directory of the root of the URL tree. The default is the htdocs directory.
-docRoot directory
Config(docRoot)
User ID of the TclHttpd process. The default is 50. (UNIX only.)
-uid uid
Config(uid)
Group ID of the TclHttpd process. The default is 100. (UNIX only.)
-gid gid
Config(gid)
Webmaster email. The default is webmaster.
-webmaster email
Config(webmaster)
Configuration file. The default is tclhttpd.rc.
-config filename
Config(file)
Directory containing custom code. The server loads all files found in this directory.
-library directory
Config(library)
Server Name and Port
The name and port parameters define how your server is known to Web browsers. The URLs that access your server begin with:
http://name:port/
If the port number is 80, you can leave out the port specification. The call that starts the server using these parameters is found in httpd.tcl as:
Httpd_Server $Config(name) $Config(port) $Config(ipaddr)
Specifying the IP address is necessary only if you have several network interfaces (or several IP addresses assigned to one network
interface) and want the server to listen to requests on a particular network address. Otherwise, by default, the server accepts requests from
any network interface.
User and Group ID
The user and group IDs are used on UNIX systems with the setuid and setgid system calls. This lets you start the server as root, which is
necessary to listen on port 80, and then switch to a less privileged user account. If you use Tcl+HTML templates that cache the results in
HTML files, then you need to pick an account that can write those files. Otherwise, you may want to pick a very unprivileged account.
The setuid function is available through the TclX (Extended Tcl)id command, or through asetuid extension distributed with TclHttpd under the
src directory. If either of these facilities is not available, then the attempt to change user ID gracefully fails. See the README file in the src
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
directory for instructions on compiling and installing the extensions found there.
Webmaster Email
The Webmaster email address is used for automatic error reporting in the case of server errors. This is defined in the configuration file with
the following command:
Doc_Webmaster $Config(webmaster)
If you call Doc_Webmaster with no arguments, it returns the email address you previously defined. This is useful when generating pages that
contain mailto: URLs with the Webmaster address.
Document Root
The document root is the directory that contains the static files, templates, CGI scripts, and so on that make up your Web site. By default, the
httpd.tcl script uses the htdocs directory next to the directory containing httpd.tcl. It is worth noting the trick used to locate this directory:
file join [file dirname [info script]] ../htdocs
The info script command returns the full name of the http.tcl script, file dirname computes its directory, and file join finds the adjacent directory.
The path ../htdocs works with file join on any platform. The default location of the configuration file is found in a similar way:
file join [file dirname [info script]] tclhttpd.rc
The configuration file initializes the document root with this call:
Doc_Root $Config(docRoot)
If you need to find out what the document root is, you can call Doc_Root with no arguments and it returns the directory of the document root.
If you want to add additional document trees into your Web site, you can do that with a call like this in your configuration file:
Doc_AddRoot directory urlprefix
Other Document Settings
The Doc_IndexFile command sets a pattern used to find the index file in a directory. The command used in the default configuration file is:
Doc_IndexFile index.{htm,html,tml,subst}
If you invent other file types with different file suffixes, you can alter this pattern to include them. This pattern will be used by the Tcl glob
command.
The Doc_PublicHtml command is used to define "home directories" on your HTML site. If the URL begins with~username, then the Web
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
server will look under the home directory of the user for a particular directory. The command in the default configuration file is:
Doc_PublicHtml public_html
For example, if my home directory is /home/welch, then the URL ~welch maps to the directory /home/welch/public_html. If there is no
Doc_PublicHtml command, then this mapping does not occur.
You can register two special pages that are used when the server encounters an error and when a user specifies an unknown URL. The
default configuration file has these commands:
Doc_ErrorPage error.html
Doc_NotFoundPage notfound.html
These files are treated like templates in that they are passed through subst in order to include the error information or the URL of the missing
page. These are pretty crude templates compared to the templates described earlier. You can count only on the Doc and Httpd arrays being
defined. Look at the Doc_SubstSystemFile in doc.tcl for the truth about how these files are processed.
Document Templates
The template mechanism has two main configuration options. The first specifies an additional library directory that contains your
application-specific scripts. This lets you keep your application-specific files separate from the TclHttpd implementation. The command in the
default configuration file specifies the libtml directory of the document tree:
Doc_TemplateLibrary [file join $Config(docRoot) libtml]
You can also specify an alternate Tcl interpreter in which to process the templates. The default is to use the main interpreter, which is named
{} according to the conventions described in Chapter 19.
Doc_TemplateInterp {}
Log Files
The server keeps standard format log files. The Log_SetFile command defines the base name of the log file. The default configuration file
uses this command:
Log_SetFile /tmp/log$Config(port)_
By default, the server rotates the log file each night at midnight. Each day's log file is suffixed with the current date (e.g.,
/tmp/logport_990218.) The error log, however, is not rotated, and all errors are accumulated in/tmp/logport_error.
The log records are normally flushed every few minutes to eliminate an extra I/O operation on each HTTP transaction. You can set this period
with Log_FlushMinutes. If minutes is 0, the log is flushed on every HTTP transaction. The default configuration file contains:
Log_FlushMinutes 1
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
CGI Directories
You can register a directory that contains CGI programs with the Cgi_Directory command. This command has the interesting effect of forcing
all files in the directory to be executed as CGI scripts, so you cannot put normal HTML files there. The default configuration file contains:
Cgi_Directory /cgi-bin
This means that the cgi-bin directory under the document root is a CGI directory. If you supply another argument toCgi_Directory, then this is a
file system directory that gets mapped into the URL defined by the first argument. You can also put CGI scripts into other directories and use
the .cgi suffix to indicate that they should be executed as CGI scripts.
The cgi.tcl file has some additional parameters that you can tune only by setting some elements of the
Cgi Tcl array. See the comments in the
beginning of that file for details.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
.
[ Team LiB ]
Chapter 19. Multiple Interpreters and Safe-Tcl
This chapter describes how to create more than one Tcl interpreter in your application. A child interpreter can be made safe so that it can
execute untrusted scripts without compromising your application or your computer. Command aliases, hidden commands, and shared I/O
channels enable communication among interpreters. Tcl command described is: interp.
Safe-Tcl was invented by Nathaniel Borenstein and Marshall Rose so that they could send Tcl scripts via email and have the recipient safely
execute the script without worry of viruses or other attacks. Safe-Tcl works by removing dangerous commands like exec and open that would
let an untrusted script damage the host computer. You can think of this restricted interpreter as a "padded cell" in which it is safe to execute
untrusted scripts. To continue the analogy, if the untrusted code wants to do anything potentially unsafe, it must ask permission. This works by
adding additional commands, or aliases, that are implemented by a different Tcl interpreter. For example, asafeopen command could be
implemented by limiting file space to a temporary directory that is deleted when the untrusted code terminates.
The key concept of Safe-Tcl is that there are two Tcl interpreters in the application, a trusted one and an untrusted (or "safe") one. The
trusted interpreter can do anything, and it is used for the main application (e.g., the Web browser or email user interface). When the main
application receives a message containing an untrusted script, it evaluates that script in the context of the untrusted interpreter. The restricted
nature of the untrusted interpreter means that the application is safe from attack. This model is much like user mode and kernel mode in a
multiuser operating system like UNIX or Windows/NT. In these systems, applications run in user mode and trap into the kernel to access
resources like files and the network. The kernel implements access controls so that users cannot read and write each other's files, or hijack
network services. In Safe-Tcl the application implements access controls for untrusted scripts.
The dual interpreter model of Safe-Tcl has been generalized in Tcl 7.5 and made accessible to Tcl scripts. A Tcl script can create other
interpreters, destroy them, create command aliases among them, share I/O channels among them, and evaluate scripts in them.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The interp Command
The interp command is used to create and manipulate interpreters. The interpreter being created is called aslave, and the interpreter that
creates it is called the master. The master has complete control over the slave. Theinterp command is summarized in Table 19-1.
Table 19-1. The interp command
interp aliases slave
Lists aliases that are defined inslave.
interp alias slave cmd1
Returns target command and arguments for the aliascmd1 in slave.
interp alias slave cmd1 master cmd2 arg...
Defines cmd1 in slave that is an alias to cmd2 in master with additional args.
interp create ?-safe? slave
Creates an interpreter named slave.
interp delete slave
Destroys interpreter slave.
interp eval slave cmd args ...
Evaluates cmd and args in slave.
interp exists slave
Returns 1 if slave is an interpreter, else 0.
interp expose slave cmd
Exposes hidden command cmd in slave.
interp hide slave cmd
Hides cmd from slave.
interp hidden slave
Returns the commands hidden from slave.
interp invokehidden slave cmd arg ...
Invokes hidden command cmd and args in slave.
interp issafe slave
Returns 1 if slave was created with -safe flag.
interp marktrusted slave
Clears the issafe property of slave.
interp recursionlimit slave ?limit?
Set or get the interpreter recursion limit forslave. (Tcl 8.4)
interp share master file slave
Shares the I/O descriptor named file in master with slave.
interp slaves master
Returns the list of slave interpreters of master.
interp target slave cmd
Returns the name of the interpreter that is the target of aliascmd in slave.
interp transfer master file slave
Transfers the I/O descriptor named file from master to slave.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Creating Interpreters
Here is a simple example that creates an interpreter, evaluates a couple of commands in it, and then deletes the interpreter:
Example 19-1 Creating and deleting an interpreter
interp create foo
=> foo
interp eval foo {set a 5}
=> 5
set sum [interp eval foo {expr {$a + $a}}]
=> 10
interp delete foo
In Example 19-1 the interpreter is named foo. Two commands are evaluated in thefoo interpreter:
set a 5
expr {$a + $a}
Note that curly braces are used to protect the commands from any interpretation by the main interpreter. The variable a is defined in the foo
interpreter and does not conflict with variables in the main interpreter. The set of variables and procedures in each interpreter is completely
independent.
The Interpreter Hierarchy
A slave interpreter can itself create interpreters, resulting in a hierarchy. The next examples illustrates this, and it shows how the grandparent
of an interpreter can reference the grandchild by name. The example uses interp slaves to query the existence of child interpreters.
Example 19-2 Creating a hierarchy of interpreters
interp create foo
=> foo
interp eval foo {interp create bar}
=> bar
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
interp create {foo bar2}
=> foo bar2
interp slaves
=> foo
interp slaves foo
=> bar bar2
interp delete bar
=> interpreter named "bar" not found
interp delete {foo bar}
The example creates foo, and then it creates two children offoo. The first one is created by foo with this command:
interp eval foo {interp create bar}
The second child is created by the main interpreter. In this case, the grandchild must be named by a two-element list to indicate that it is a
child of a child. The same naming convention is used when the grandchild is deleted:
interp create {foo bar2}
interp delete {foo bar2}
The interp slaves operation returns the names of child (i.e., slave) interpreters. The names are relative to their parent, so the slaves offoo are
reported simply as bar and bar2. The name for the current interpreter is the empty list, or{}. This is useful in command aliases and file sharing
described later. For security reasons, it is not possible to name the master interpreter from within the slave.
The Interpreter Name as a Command
After interpreter slave is created, a new command is available in the main interpreter, also calledslave, that operates on the child interpreter.
The following two forms are equivalent most operations:
slave operation args ...
interp operation slave args ...
For example, the following are equivalent commands:
foo eval {set a 5}
interp eval foo {set a 5}
And so are these:
foo issafe
interp issafe foo
However, the operations delete, exists, share, slaves, target, and transfer cannot be used with the per interpreter command. In particular, there
is no foo delete operation; you must use interp delete foo.
If you have a deep hierarchy of interpreters, the command corresponding to the slave is defined only in the parent. For example, if a master
creates foo, and foo creates bar, then the master must operate onbar with the interp command. There is no "foo bar" command defined in the
master.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Use list with interp eval
The interp eval command treats its arguments like eval. If there are extra arguments, they are all concatenated together first. This can lose
important structure, as described in Chapter 10. To be safe, use list to construct your commands. For example, to safely define a variable in
the slave, you should do this:
interp eval slave [list set var $value]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Safe Interpreters
A child can be created either safe (i.e., untrusted) or fully functional. In the examples so far, the children have been trusted and fully
functional; they have all the basic Tcl commands available to them. An interpreter is made safe by eliminating certain commands. Table 19-2
lists the commands removed from safe interpreters. As described later, these commands can be used by the master on behalf of the safe
interpreter. To create a safe interpreter, use the -safe flag:
interp create -safe untrusted
Table 19-2. Commands hidden from safe interpreters
cd
Changes directory.
exec
Executes another program.
exit
Terminates the process.
fconfigure
Sets modes of an I/O stream.
file
Queries file attributes.
glob
Matches on file name patterns.
load
Dynamically loads object code.
open
Opens files and process pipelines.
pwd
Determines the current directory.
socket
Opens network sockets.
source
Loads scripts.
A safe interpreter does not have commands to manipulate the file system and other programs (e.g., cd, open, and exec). This ensures that
untrusted scripts cannot harm the host computer. The socket command is removed so that untrusted scripts cannot access the network. The
exit, source, and load commands are removed so that an untrusted script cannot harm the hosting application. Note that commands like
puts
and gets are not removed. A safe interpreter can still do I/O, but it cannot create an I/O channel. We will show how to pass an I/O channel to a
child interpreter on page 299.
The initial state of a safe interpreter is very safe, but it is too limited. The only thing a safe interpreter can do is compute a string and return
that value to the parent. By creating command aliases, a master can give a safe interpreter controlled access to resources. A security policy
implements a set of command aliases that add controlled capabilities to a safe interpreter. We will show, for example, how to provide limited
network and file system access to untrusted slaves. Tcl provides a framework to manage several security policies, which is described in
Chapter 20.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Command Aliases
A command alias is a command in one interpreter that is implemented by a command in another interpreter. The master interpreter installs
command aliases in its slaves. The command to create an alias has the following general form:
interp alias slave cmd1 target cmd2 ?arg arg ...?
This creates cmd1 in slave that is an alias forcmd2 in target. When cmd1 is invoked in slave, cmd2 is invoked in target. The alias mechanism is
transparent to the slave. Whatever cmd2 returns, the slave sees as the return value ofcmd1. If cmd2 raises an error, the error is propagated to
the slave.
Name the current interpreter with{}.
If target is the current interpreter, name it with {}. The empty list is the way to name yourself as the interpreter. This is the most common case,
although target can be a different slave. The slave and target can even be the same interpreter.
The arguments to cmd1 are passed to cmd2, after any additional arguments to cmd2 that were specified when the alias was created. These
hidden arguments provide a safe way to pass extra arguments to an alias. For example, it is quite common to pass the name of the slave to
the alias. In Example 19-3, exit in the interpreter foo is an alias that is implemented in the current interpreter (i.e.,{}). When the slave executes
exit, the master executes:
interp delete foo
Example 19-3 A command alias for exit
interp create foo
interp alias foo exit {} interp delete foo
interp eval foo exit
# Child foo is gone.
Alias Introspection
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
You can query what aliases are defined for a child interpreter. The interp aliases command lists the aliases; the interp alias command can
also return the value of an alias, and the interp target command tells you what interpreter implements an alias. These are illustrated in the
following examples:
Example 19-4 Querying aliases
proc Interp_ListAliases {name out} {
puts $out "Aliases for $name"
foreach alias [interp aliases $name] {
puts $out [format "%-20s => (%s) %s" $alias \
[interp target $name $alias] \
[interp alias $name $alias]]
}
}
Example 19-4 generates output in a human readable format.Example 19-5 generates the aliases as Tcl commands that can be used to
re-create them later:
Example 19-5 Dumping aliases as Tcl commands
proc Interp_DumpAliases {name out} {
puts $out "# Aliases for $name"
foreach alias [interp aliases $name] {
puts $out [format "interp alias %s %s %s %s" \
$name $alias [list [interp target $name $alias]] \
[interp alias $name $alias]]
}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Hidden Commands
The commands listed in Table 19-2 are hidden instead of being completely removed. A hidden command can be invoked in a slave by its
master. For example, a master can load Tcl scripts into a slave by using its hidden source command:
interp create -safe slave
interp invokehidden slave source filename
Without hidden commands, the master has to do a bit more work to achieve the same thing. It must open and read the file and eval the
contents of the file in the slave. File operations are described in Chapter 9.
interp create -safe slave
set in [open filename]
interp eval slave [read $in]
close $in
Hidden commands were added in Tcl 7.7 in order to better support the Tcl/Tk browser plug-in described in Chapter 20. In some cases, hidden
commands are strictly necessary; it is not possible to simulate them any other way. The best examples are in the context of Safe-Tk, where
the master creates widgets or does potentially dangerous things on behalf of the slave. These will be discussed in more detail later.
A master can hide and expose commands using the interp hide and interp expose operations, respectively. You can even hide Tcl
procedures. However, the commands inside the procedure run with the same privilege as that of the slave. For example, if you are really
paranoid, you might not want an untrusted interpreter to read the clock or get timing information. You can hide the clock and time commands:
interp create -safe slave
interp hide slave clock
interp hide slave time
You can remove commands from the slave entirely like this:
interp eval slave [list rename clock {}]
interp eval slave [list rename time {}]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Substitutions
You must be aware of Tcl parsing and substitutions when commands are invoked in other interpreters. There are three cases corresponding
to interp eval, interp invokehidden, and command aliases.
With interp eval the command is subject to a complete round of parsing and substitutions in the target interpreter. This occurs after the
parsing and substitutions for the interp eval command itself. In addition, if you pass several arguments to interp eval, those are concatenated
before evaluation. This is similar to the way the eval command works as described in Chapter 19. The most reliable way to use interp eval is to
construct a list to ensure the command is well structured:
interp eval slave [list cmd arg1 arg2]
With hidden commands, the command and arguments are taken directly from the arguments to interp invokehidden, and there are no
substitutions done in the target interpreter. This means that the master has complete control over the command structure, and nothing funny
can happen in the other interpreter. For this reason you should not create a list. If you do that, the whole list will be interpreted as the
command name! Instead, just pass separate arguments to interp invokehidden and they are passed straight through to the target:
interp invokehidden slave command arg1 arg2
Never eval alias arguments.
With aliases, all the parsing and substitutions occur in the slave before the alias is invoked in the master. The alias implementation should
never eval or subst any values it gets from the slave to avoid executing arbitrary code.
For example, suppose there is an alias to open files. The alias does some checking and then invokes the hidden open command. An
untrusted script might pass [exit] as the name of the file to open in order to create mischief. The untrusted code is hoping that the master will
accidentally eval the filename and cause the application to exit. This attack has nothing to do with opening files; it just hopes for a poor alias
implementation. Example 19-6 shows an alias that is not subject to this attack:
Example 19-6 Substitutions and hidden commands
interp alias slave open {} safeopen slave
proc safeopen {slave filename {mode r}} {
# do some checks, then...
interp invokehidden $slave open $filename $mode
}
interp eval slave {open \[exit\]}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The command in the slave starts out as:
open \[exit\]
The master has to quote the brackets in its interp eval command or else the slave will try to invoke exit because of command substitution.
Presumably exit isn't defined, or it is defined to terminate the slave. Once this quoting is done, the value offilename is [exit] and it is not subject
to substitutions. It is safe to use $filename in the interp invokehidden command because it is only substituted once, in the master. The hidden
open command also gets [exit] as its filename argument, which is never evaluated as a Tcl command.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
I/O from Safe Interpreters
A safe child interpreter cannot open files or network sockets directly. An alias can create an I/O channel (i.e., open a file or socket) and give
the child access to it. The parent can share the I/O channel with the child, or it can transfer the I/O channel to the child. If the channel is
shared, both the parent and the child can use it. If the channel is transferred, the parent no longer has access to the channel. In general,
transferring an I/O channel is simpler, but sharing an I/O channel gives the parent more control over an unsafe child. The differences are
illustrated in Example 19-7 and Example 19-9.
There are three properties of I/O channels that are important to consider when choosing between sharing and transferring: the name, the
seek offset, and the reference count.
The name of the I/O channel (e.g., file4) is the same in all interpreters. If a parent transfers a channel to a child, it can close the
channel by evaluating a close command in the child. Although names are shared, an interpreter cannot attempt I/O on a channel
to which it has not been given access.
The seek offset of the I/O channel is shared by all interpreters that share the I/O channel. An I/O operation on the channel updates
the seek offset for all interpreters that share the channel. This means that if two interpreters share an I/O channel, their output will
be cleanly interleaved in the channel. If they both read from the I/O channel, they will get different data. Seek offsets are explained
in more detail on page 121.
A channel has a reference count of all interpreters that share the I/O channel. The channel remains open until all references are
closed. When a parent transfers an I/O channel, the reference count stays the same. When a parent shares an I/O channel, the
reference count increments by one. When an interpreter closes a channel with close, the reference count is decremented by one.
When an interpreter is deleted, all of its references to I/O channels are removed.
The syntax of commands to share or transfer an I/O channel is:
interp share interp1 chanName interp2
interp transfer interp1 chanName interp2
In these commands, chanName exists in interp1 and is being shared or transferred tointerp2. As with command aliases, ifinterp1 is the current
interpreter, name it with {}.
The following example creates a temporary file for an unsafe interpreter. The file is opened for reading and writing, and the slave can use it to
store data temporarily.
Example 19-7 Opening a file for an unsafe interpreter
proc TempfileAlias {slave} {
set i 0
while {[file exists Temp$slave$i]} {
incr i
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
set out [open Temp$slave$i w+]
interp transfer {} $out $slave
return $out
}
proc TempfileExitAlias {slave} {
foreach file [glob -nocomplain Temp$slave*] {
file delete -force $file
}
interp delete $slave
}
interp create -safe foo
interp alias foo Tempfile {} TempfileAlias foo
interp alias foo exit {} TempfileExitAlias foo
The TempfileAlias procedure is invoked in the parent when the child interpreter invokesTempfile. TempfileAlias returns the name of the open
channel, which becomes the return value from Tempfile. TempfileAlias uses interp transfer to pass the I/O channel to the child so that the
child has permission to access the I/O channel. In this example, it would also work to invoke the hidden open command to create the I/O
channel directly in the slave.
Example 19-7 is not fully safe because the unsafe interpreter can still overflow the disk or create a million files. Because the parent has
transferred the I/O channel to the child, it cannot easily monitor the I/O activity by the child. Example 19-9 addresses these issues.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The Safe Base
An safe interpreter created with interp create -safe has no script library environment and no way to source scripts. Tcl provides asafe base
that extends a raw safe interpreter with the ability to source scripts and packages which are described in Chapter 12. The safe base also
defines an exit alias that terminates the slave like the one inExample 19-7. The safe base is implemented as Tcl scripts that are part of the
standard Tcl script library. Create an interpreter that uses the safe base with safe::interpCreate:
safe::interpCreate foo
The safe base has source and load aliases that only access directories on an access path defined by the master interpreter. The master has
complete control over what files can be loaded into a slave. In general, it would be all right to source any Tcl program into an untrusted
interpreter. However, untrusted scripts might learn things from the error messages they get by sourcing arbitrary files. The safe base also has
versions of the package and unknown commands that support the library facility.Table 19-3 lists the Tcl procedures in the safe base:
Table 19-3. The safe base master interface
safe::interpCreate ?slave? ?options?
Creates a safe interpreter and initialize the security policy mechanism.
safe::interpInit slave ?options?
Initializes a safe interpreter so it can use security policies.
safe::interpConfigure slave ?options?
Options are -accessPath pathlist, -nostatics, -deleteHook script, -nestedLoadOk.
safe::interpDelete slave
Deletes a safe interpreter.
safe::interpAddToAccessPath slave directory
Adds a directory to the slave's access path.
safe::interpFindInAccessPath
Maps from a directory to the token visible in the slave for that directory.
safe::setLogCmd ?cmd arg ... ?
Sets or queries the logging command used by the safe base.
Table 19-4 lists the aliases defined in a safe interpreter by the safe base.
Table 19-4. The safe base slave aliases
source
Loads scripts from directories in the access path.
load
Loads binary extensions from the slaves access path.
file
Only the dirname, join, extension, root, tail, pathname, and split operations are allowed.
exit
Destroys the slave interpreter.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Security Policies
A security policy defines what a safe interpreter can do. Designing security policies that are secure is difficult. If you design your own, make
sure to have your colleagues review the code. Give out prizes to folks who can break your policy. Good policy implementations are proven
with lots of review and trial attacks. The good news is that Safe-Tcl security policies can be implemented in relatively small amounts of Tcl
code. This makes them easier to analyze and get correct. Here are a number of rules of thumb:
Small policies are better than big, complex policies. If you do a lot of complex processing to allow or disallow access to resources,
chances are there are holes in your policy. Keep it simple.
Never eval arguments to aliases. If an alias accepts arguments that are passed by the slave, you must avoid being tricked into
executing arbitrary Tcl code. The primary way to avoid this is never to eval arguments that are passed into an alias. Watch your
expressions, too. The expr command does an extra round of substitutions, so brace all your expressions so that an attacker
cannot pass [exit] where you expect a number!
Security policies do not compose. Each time you add a new alias to a security policy, it changes the nature of the policy. Even if
alias1 and alias2 are safe in isolation, there is no guarantee that they cannot be used together to mount an attack. Each addition
to a security policy requires careful review.
Limited Socket Access
The Safesock security policy provides limited socket access. The policy is designed around a simple table of allowed hosts and ports. An
untrusted interpreter can connect only to addresses listed in the table. For example, I would never let untrusted code connect to the sendmail,
ftp, or telnet ports on my hosts. There are just too many attacks possible on these ports. On the other hand, I might want to let untrusted code
fetch a URL from certain hosts, or connect to a database server for an intranet application. The goal of this policy is to have a simple way to
specify exactly what hosts and ports a slave can access. Example 19-8 shows a simplified version of the Safesock security policy that is
distributed with Tcl 8.0.
Example 19-8 The Safesock security policy
# The index is a host name, and the
# value is a list of port specifications, which can be
# an exact port number
# a lower bound on port number: N# a range of port numbers, inclusive: N-M
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
array set safesock {
sage.eng
3000-4000
www.sun.com 80
webcache.eng {80 8080}
bisque.eng {80 1025-}
}
proc Safesock_PolicyInit {slave} {
interp alias $slave socket {} SafesockAlias $slave
}
proc SafesockAlias {slave host port} {
global safesock
if ![info exists safesock($host)] {
error "unknown host: $host"
}
foreach portspec $safesock($host) {
set low [set high ""]
if {[regexp {^([0-9]+)-([0-9]*)$} $portspec x low high]} {
if {($low <= $port && $high == "") ||
($low <= $port && $high >= $port)} {
set good $port
break
}
} elseif {$port == $portspec} {
set good $port
}
}
if [info exists good] {
set sock [interp invokehidden $slave socket $host $good]
interp invokehidden $slave fconfigure $sock \
-blocking 0
return $sock
}
error "bad port: $port"
}
The policy is initialized with Safesock_PolicyInit. The name of this procedure follows a naming convention used by the safe base. In this case,
a single alias is installed. The alias gives the slave a socket command that is implemented by SafesockAlias in the master.
The alias checks for a port that matches one of the port specifications for the host. If a match is found, then the invokehidden operation is
used to invoke two commands in the slave. The socket command creates the network connection, and thefconfigure command puts the
socket into nonblocking mode so that read and gets by the slave do not block the application:
set sock [interp invokehidden $slave socket $host $good]
interp invokehidden $slave fconfigure $sock -blocking 0
The socket alias in the slave does not conflict with the hiddensocket command. There are two distinct sets of commands, hidden and
exposed. It is quite common for the alias implementation to invoke the hidden command after various permission checks are made.
The Tcl Web browser plug-in ships with a slightly improved version of the Safesock policy. It adds an alias forfconfigure so that the http
package can set end of line translations and buffering modes. The fconfigure alias does not let you change the blocking behavior of the
socket. The policy has also been extended to classify hosts into trusted and untrusted hosts based on their address. A different table of
allowed ports is used for the two classes of hosts. The classification is done with two tables: One table lists patterns that match trusted hosts,
and the other table lists hosts that should not be trusted even though they match the first table. The improved version also lets a downloaded
script connect to the Web server that it came from. The Web browser plug-in is described in Chapter 20.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Limited Temporary Files
Example 19-9 improves on Example 19-7 by limiting the number of temporary files and the size of the files. It is written to work with the safe
base, so it has a Tempfile_PolicyInit that takes the name of the slave as an argument.TempfileOpenAlias lets the child specify a file by name,
yet it limits the files to a single directory.
The example demonstrates a shared I/O channel that gives the master control over output. TempfilePutsAlias restricts the amount of data
that can be written to a file. By sharing the I/O channel for the temporary file, the slave can use commands like gets, eof, and close, while the
master does the puts. The need for shared I/O channels is somewhat reduced by hidden commands, which were added to Safe-Tcl more
recently than shared I/O channels. For example, the puts alias can either write to a shared channel after checking the file size, or it can invoke
the hidden puts in the slave. This alternative is shown inExample 19-10.
Example 19-9 The Tempfile security policy
# Policy parameters:
# directory is the location for the files
# maxfile is the number of files allowed in the directory
# maxsize is the max size for any single file.
array set tempfile {
maxfile
4
maxsize
65536
}
# tempfile(directory) is computed dynamically based on
# the source of the script
proc Tempfile_PolicyInit {slave} {
global tempfile
interp alias $slave open {} \
TempfileOpenAlias $slave $tempfile(directory) \
$tempfile(maxfile)
interp alias $slave puts {} TempfilePutsAlias $slave \
$tempfile(maxsize)
interp alias $slave exit {} TempfileExitAlias $slave
}
proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} {
global tempfile
# remove sneaky characters
regsub -all {|/:} [file tail $name] {} real
set real [file join $dir $real]
# Limit the number of files
set files [glob -nocomplain [file join $dir *]]
set N [llength $files]
if {($N >= $maxfile) && (\
[lsearch -exact $files $real] < 0)} {
error "permission denied"
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
}
if [catch {open $real $m $p} out] {
return -code error "$name: permission denied"
}
lappend tempfile(channels,$slave) $out
interp share {} $out $slave
return $out
}
proc TempfileExitAlias {slave} {
global tempfile
interp delete $slave
if [info exists tempfile(channels,$slave)] {
foreach out $tempfile(channels,$slave) {
catch {close $out}
}
unset tempfile(channels,$slave)
}
}
# See also the puts alias in Example 24–4 on page 389
proc TempfilePutsAlias {slave max chan args} {
# max is the file size limit, in bytes
# chan is the I/O channel
# args is either a single string argument,
# or the -nonewline flag plus the string.
if {[llength $args] > 2} {
error "invalid arguments"
}
if {[llength $args] == 2} {
if {![string match -n* [lindex $argv 0]]} {
error "invalid arguments"
}
set string [lindex $args 1]
} else {
set string [lindex $args 0]\n
}
set size [expr [tell $chan] + [string length $string]]
if {$size > $max} {
error "File size exceeded"
} else {
puts -nonewline $chan $string
}
}
The TempfileAlias procedure is generalized in Example 19-9 to have parameters that specify the directory, name, and a limit to the number of
files allowed. The directory and maxfile limit are part of the alias definition. Their existence is transparent to the slave. The slave specifies only
the name and access mode (i.e., for reading or writing.) The Tempfile policy can be used by different slave interpreters with different
parameters.
The master is careful to restrict the files to the specified directory. It uses file tail to strip off any leading pathname components that the slave
might specify. The tempfile(directory) definition is not shown in the example. The application must choose a directory when it creates the safe
interpreter. The Browser security policy described on page 317 chooses a directory based on the name of the URL containing the untrusted
script.
The TempfilePutsAlias procedure implements a limited form of puts. It checks the size of the file withtell and measures the output string to see
if the total exceeds the limit. The limit comes from a parameter defined when the alias is created. The file cannot grow past the limit, at least
not by any action of the child interpreter. The args parameter is used to allow an optional-nonewline flag to puts. The value of args is checked
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
explicitly instead of using the eval trick described in Example 10-3 on page 136. Never eval arguments to aliases or else a slave can attack
you with arguments that contain embedded Tcl commands.
The master and slave share the I/O channel. The name of the I/O channel is recorded in tempfile, and TempfileExitAlias uses this information
to close the channel when the child interpreter is deleted. This is necessary because both parent and child have a reference to the channel
when it is shared. The child's reference is automatically removed when the interpreter is deleted, but the parent must close its own reference.
The shared I/O channel lets the master use puts and tell. It is also possible to implement this policy by using hiddenputs and tell commands.
The reason tell must be hidden is to prevent the slave from implementing its own version oftell that lies about the seek offset value. One
advantage of using hidden commands is that there is no need to clean up the tempfile state about open channels. You can also layer the puts
alias on top of any existing puts implementation. For example, a script may define puts to be a procedure that inserts data into a text widget.
Example 19-10 shows the difference when using hidden commands.
Example 19-10 Restricted puts using hidden commands
proc Tempfile_PolicyInit {slave} {
global tempfile
interp alias $slave open {} \
TempfileOpenAlias $slave $tempfile(directory) \
$tempfile(maxfile)
interp hide $slave tell
interp alias $slave tell {} TempfileTellAlias $slave
interp hide $slave puts
interp alias $slave puts {} TempfilePutsAlias $slave \
$tempfile(maxsize)
# no special exit alias required
}
proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} {
# remove sneaky characters
regsub -all {|/:} [file tail $name] {} real
set real [file join $dir $real]
# Limit the number of files
set files [glob -nocomplain [file join $dir *]]
set N [llength $files]
if {($N >= $maxfile) && (\
[lsearch -exact $files $real] < 0)} {
error "permission denied"
}
if [catch {interp invokehidden $slave \
open $real $m $p} out] {
return -code error "$name: permission denied"
}
return $out
}
proc TempfileTellAlias {slave chan} {
interp invokehidden $slave tell $chan
}
proc TempfilePutsAlias {slave max chan args} {
if {[llength $args] > 2} {
error "invalid arguments"
}
if {[llength $args] == 2} {
if {![string match -n* [lindex $args 0]]} {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
error "invalid arguments"
}
set string [lindex $args 1]
} else {
set string [lindex $args 0]\n
}
set size [interp invokehidden $slave tell $chan]
incr size [string length $string]
if {$size > $max} {
error "File size exceeded"
} else {
interp invokehidden $slave \
puts -nonewline $chan $string
}
}
Safe after Command
The after command is unsafe because it can block the application for an arbitrary amount of time. This happens if you only specify a time but
do not specify a command. In this case, Tcl just waits for the time period and processes no events. This will stop all interpreters, not just the
one doing the after command. This is a kind of resource attack. It doesn't leak information or damage anything, but it disrupts the main
application.
Example 19-11 defines an alias that implements after on behalf of safe interpreters. The basic idea is to carefully check the arguments, and
then do the after in the parent interpreter. As an additional feature, the number of outstandingafter events is limited. The master keeps a
record of each after event scheduled. Two IDs are associated with each event: one chosen by the master (i.e.,
myid), and the other chosen by
the after command (i.e., id). The master keeps a map frommyid to id. The map serves two purposes: The number of map entries counts the
number of outstanding events. The map also hides the real after ID from the slave, which prevents a slave from attempting mischief by
specifying invalid after IDs to after cancel. The SafeAfterCallback is the procedure scheduled. It maintains state and then invokes the original
callback in the slave.
Example 19-11 A safe after command
# SafeAfter_PolicyInit creates a child with
# a safe after command
proc SafeAfter_PolicyInit {slave max} {
# max limits the number of outstanding after events
global after
interp alias $slave after {} SafeAfterAlias $slave $max
interp alias $slave exit {} SafeAfterExitAlias $slave
# This is used to generate after IDs for the slave.
set after(id,$slave) 0
}
# SafeAfterAlias is an alias for after. It disallows after
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# with only a time argument and no command.
proc SafeAfterAlias {slave max args} {
global after
set argc [llength $args]
if {$argc == 0} {
error "Usage: after option args"
}
switch -- [lindex $args 0] {
cancel {
# A naive implementation would just
# eval after cancel $args
# but something dangerous could be hiding in args.
set myid [lindex $args 1]
if {[info exists after(id,$slave,$myid)]} {
set id $after(id,$slave,$myid)
unset after(id,$slave,$myid)
after cancel $id
}
return ""
}
default {
if {$argc == 1} {
error "Usage: after time command args..."
}
if {[llength [array names after id,$slave,*]]\
>= $max} {
error "Too many after events"
}
# Maintain concat semantics
set command [concat [lrange $args 1 end]]
# Compute our own id to pass the callback.
set myid after#[incr after(id,$slave)]
set id [after [lindex $args 0] \
[list SafeAfterCallback $slave $myid $command]]
set after(id,$slave,$myid) $id
return $myid
}
}
}
# SafeAfterCallback is the after callback in the master.
# It evaluates its command in the safe interpreter.
proc SafeAfterCallback {slave myid cmd} {
global after
unset after(id,$slave,$myid)
if [catch {
interp eval $slave $cmd
} err] {
catch {interp eval $slave bgerror $error}
}
}
# SafeAfterExitAlias is an alias for exit that does cleanup.
proc SafeAfterExitAlias {slave} {
global after
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
foreach id [array names after id,$slave,*] {
after cancel $after($id)
unset after($id)
}
interp delete $slave
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 20. Safe-Tk and the Browser Plugin
This chapter describes Safe-Tk that lets untrusted scripts display and manipulate graphical user interfaces. The main application of Safe-Tk
is the Tcl/Tk plugin for Web browsers like Netscape Navigator and Internet Explorer.
Safe-Tk supports network applets that display user interfaces. The main vehicle for Safe-Tk is a plugin for Netscape Navigator, Mozilla and
Internet Explorer. The plugin supports Tcl applets, or Tclets, that are downloaded from the Web server and execute inside a window in a Web
browser. For the most part, Tcl/Tk applications can run unchanged in the plugin. However, security policies place some restrictions on Tclets.
The plugin supports multiple security policies, so Tclets can do a variety of interesting things in a safe manner.
You can configure the plugin to use an existing wish application to host the Tcl applets if you require a newer version of Tk, or the plugin can
load the Tcl/Tk shared libraries and everything runs in the browser process. You can use a custom wish that has extensions built in or
dynamically loaded. This gives intranet applications of the plugin the ability to access databases and other services that are not provided by
the Tcl/Tk core. With the security policy mechanism you can still provide mediated access to these resources. This chapter describes how to
set up the plugin.
Jeff Hobbs recently updated the plugin to use Tcl/Tk 8.4. Compiled versions of the plugin are available as part of the Tcl Dev Kit from
ActiveState. The source code of the plugin is freely available. You can recompile the plugin against newer versions of Tcl/Tk, or build custom
plugins that have your own Tcl extensions built in. You can find its sources at:
http://tclplugin.sourceforge.net/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Tk in Child Interpreters
A child interpreter starts out with just the core Tcl commands. It does not include Tk or any other extensions that might be available to the
parent interpreter. This is true whether or not the child interpreter is declared safe. You add extensions to child interpreters by using a form of
the load command that specifies an interpreter:
load {} Tk child
Normally, load takes the name of the library file that contains the extension. In this case, the Tk package is astatic package that is already
linked into the program (e.g., wish or the plugin), so the file name is the empty string. Theload command calls the Tk initialization procedure to
register all the Tcl commands provided by Tk.
Embedding Tk Windows
By default, a slave interpreter that loads Tk gets a new top-level window. Wish supports a -use command line option that directs Tk to use an
existing window as dot. You can use this to embed an application within another. For example, the following commands run a copy of Wish
that uses the .embed toplevel as its main window:
toplevel .embed
exec wish -use [winfo id .embed] somescript.tcl &
More often, embedding is used with child interpreters. If the interpreter is not safe, you can set the argv and argc variables in the slave before
loading Tk:
interp create trustedTk
interp eval trustedTk \
[list set argv [list -use [winfo id .embed]]]
interp eval trustedTk [list set argc 2]
load {} Tk trustedTk
If the child interpreter is safe, then you cannot set argv and argc directly. The easiest way to pass-use to a safe interpreter is with the
safe::loadTk command:
safe::interpCreate safeTk
safe::loadTk safeTk -use [winfo id .embed]
When Tk is loaded into a safe interpreter, it calls back into the master interpreter and evaluates the safe::TkInit procedure. The job of this
procedure is to return the appropriate argv value for the slave. The safe::loadTk procedure stores its additional arguments in the safe::tkInit
variable, and this value is retrieved by the safe::TkInit procedure and returned to the slave. This protocol is used so that a safe interpreter
cannot attempt to hijack the windows of its master by constructing its own argv variable!
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Safe-Tk Restrictions
When Tk is loaded into a safe interpreter, it hides several Tk commands. Primarily these are hidden to prevent denial of service attacks
against the main process. For example, if a child interpreter did a global grab and never released it, all input would be forever directed to the
child. Table 20-1 lists the Tk commands hidden by default from a safe interpreter. The Tcl commands that are hidden insafe interpreters are
listed on page 295.
Table 20-1. Tk commands omitted from safe interpreters
bell
Rings the terminal bell.
clipboard
Accesses the CLIPBOARD selection.
grab
Directs input to a specified widget.
menu
Creates and manipulates menus, because menus needgrab.
selection
Manipulates the selection.
send
Executes a command in another Tk application.
tk appname
Sets the application name.
tk_chooseColor
Color choice dialog.
tk_chooseDirectory
Directory chooser dialog.
tk_getOpenFile
File open dialog.
tk_getSaveFile
File save dialog.
tk_messageBox
Simple dialog boxes.
toplevel
Creates a detached window.
wm
Controls the window manager.
If you find these restrictions limiting, you can restore commands to safe interpreters with the interp expose command. For example, to get
menus and toplevels working, you could do:
interp create -safe safeTk
foreach cmd {grab menu menubutton toplevel wm} {
interp expose safeTk $cmd
}
Instead of exposing the command directly, you can also construct aliases that provide a subset of the features. For example, you could
disable the -global option to grab. Aliases are described in detail inChapter 19.
The Browser plugin defines a more elaborate configuration system to control what commands are available to slave interpreters. You can
have lots of control, but you need to distribute the security policies that define what Tclets can do in the plugin. Configuring security policies
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
for the plugin is described later.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The Browser Plugin
The HTML EMBED tag is used to put various objects into a Web page, including a Tcl program.Example 20-1 shows the EMBED tag used to
insert a Tclet:
Example 20-1 Using EMBED to insert a Tclet
<EMBED
TYPE="application/x-tcl"
PLUGINSPAGE="http://www.tcl.tk/plugin/"
WIDTH="400"
HEIGHT="300"
SRC="eval.tcl"
</EMBED>
The width and height are interpreted by the plugin as the size of the embedded window. Thesrc specifies the URL of the program. These
parameter names (e.g., width) are case sensitive and should be lowercase. In the above example,eval.tcl is a relative URL, so it should be in
the same directory as the HTML file that has the EMBED tag. The window size is fixed in the browser, which is different from normal toplevels
in Tk. The plugin turns off geometry propagation on your main window so that your Tclet stays the size allocated.
There are also "full window" Tclets that do not use an EMBED tag at all. Instead, you just specify the .tcl file directly in the URL. In this case,
the plugin occupies the whole browser window and will resize as you resize the browser window.
The embed_args and plugin Variables
The parameters in the EMBED tag are available to the Tcl program in theembed_args variable, which is an array with the parameter names as
the index values. For example, the string for a ticker-tape Tclet can be passed in the EMBED tag as the string parameter, and the Tclet will
use $embed_args(string) as the value to display:
<EMBED src=ticker.tcl width=400 height=50 string="Hello World">
Note that HTML tag parameters are case sensitive. Your Tclet may want to map all the parameter names to lowercase for convenience:
foreach {name value} [array get embed_args] {
set embed_args([string tolower $name]) $value
}
The plugin array has version, patchLevel, and release elements that identify the version and release date of the plugin implementation.
Example Plugins
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The plugin home page is a great place to find Tclet examples. There are several plugins done by the Tcl/Tk team at Sunlabs, plus links to a
wide variety of Tclets done on the Net.
http://www.tcl.tk/plugin/
My first plugin was calculator for the effective wheel diameter of multigear bicycles. Brian Lewis, who built the Tcl 8.0 byte-code compiler,
explained to me the concept and how important this information is to bicycle enthusiasts. The Tclet that displays the gear combinations on a
Tk canvas and lets you change the number of gears and their size. You can find the result at:
http://www.beedub.com/plugin/bike.html
Setting Up the plugin
There are plugin versions for UNIX, Windows, and Macintosh. The installation details vary somewhat between platforms and between
releases of the plugin. The following components make up the plugin installation:
The plugin shared libraries (i.e., DLLs). The Web browser dynamically loads the plugin implementation when it needs to execute a
Tclet embedded in a Web page. There is a standard directory that the browser scans for the libraries that implement plugins.
The Tcl/Tk script libraries. The plugin needs the standard script libraries that come with Tcl and Tk, plus it has its own scripts that
complete its implementation. Each platform has a plugin script directory with these subdirectories: tcl, tk, plugin, config, safetcl, and
utils. The plugin implementation is in theplugin directory.
The security policies. These are kept in asafetcl directory that is a peer of the Tcl script library.
The trust configuration. This defines what Tclets can use which security policies. This is in aconfig directory that is a peer of the Tcl
script library.
Local hooks. Local customization is supported by two hooks,siteInit and siteSafeInit. The siteInit procedure is called from the plugin
when it first loads, and siteSafeInit is called when each applet is initialized. It is called with the name of the slave interpreter and
the list of arguments from the <EMBED> tag. You can provide these as scripts that get loaded from theauto_path of the master
interpreter. Chapter 12 describes how to manage script libraries found in theauto_path. The plugin also sources a personal start
up script in which you can define siteInit and siteSafeInit. This script is ~/.pluginrc on UNIX and plugin/tclplugin.rc on Windows and
Macintosh.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Security Policies and Browser Plugin
Tclets run in a safe interpreter that is set up with the safe base facilities described on page 300. This limits a Tclet to a display-only
application. To do something more interesting, you must grant the Tclet more privilege. The extra functions are bundled together into a
security policy, which is implemented as a set of command aliases. Unlike a Java applet, a Tclet can choose from different security policies. A
few standard security policies are distributed with the plugin, and these are described below. You can also create custom security policies to
support intranet applications. You can even choose to grant certain Tclets the full power of Tcl/Tk. The policy command is used to request a
security policy:
policy name
The policies that are part of the standard plugin distribution are described below. The home, inside, and outside policies all provide limited
network access. They differ in what set of hosts are accessible. The default trust configuration lets any Tclet request the home, inside, or
outside policy.
home. This provides a socket and fconfigure commands that are limited to connecting to the host from which the Tclet was
downloaded. You can specify an empty string for the host argument to socket to connect back to the home host. This policy also
supports open and file delete that are similar to theTempfile policy shown in Example 19-9 on page 304. This provides limited local
storage that is inside a directory that is, by default, private to the Tclet. Files in the private directory persist after the Tclet exits, so
it can maintain long term state. Tclets from the same server can share the directory by putting the same prefix=partialurl argument
in their EMBED tag. The partialurl must be a prefix of the Tclet's URL. Finally, thehome policy automatically provides a browser
package that is described later.
inside. This is just like thehome policy, except that the site administrator controls a table of hosts and ports to which untrusted
slaves can connect with socket. A similar set of tables control what URLs can be accessed with thebrowser package. This is
similar to the Safesock policy shown in Example 19-8 on page 302. The set of hosts is supposed to be inside the firewall. The local
file storage used by this policy is distinct from that used by the home and outside policies. This is true even if Tclets try to share by
using the prefix=partialurl parameter.
outside. This is just like thehome and inside policies, except that the set of hosts is configured to be outside the firewall. The local
file storage used by this policy is distinct from that used by the home and inside policies.
trusted. This policy restores all features of Tcl and Tk. This policy lets you launch all your Tcl and Tk applications from the Web
browser. The default trust map settings do not allow this for any Tclet. The trust map configuration is described later.
javascript. This policy provides a superset of thebrowser package that lets you invoke arbitrary Javascript and to write HTML
directly to frames. This does not have the limited socket or temporary file access that the home, inside, and outside policies have.
However, the javascript policy places no restrictions on the URLs you can fetch, plus it lets Tclets execute Java-script, which may
have its own security risks. The default trust map settings do not allow this for any Tclet.
The Browser Package
The browser package is bundled with several of the security policies. It makes many features of the Web browser accessible to Tclets. They
can fetch URLs and display HTML in frames. However, the browser package has some risks associated with it. HTTP requests can be used
to transmit information, so a Tclet using the policy could leak sensitive information if it can fetch a URL outside the firewall. To avoid
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
information leakage, the inside, outside, and home policies restrict the URL that can be fetched withbrowser::getURL. Table 20-2 lists the
aliases defined by the browser package.
Table 20-2. Aliases defined by the browser package
browser::status string
Displays string in the browser status window.
browser::getURL url ?timeout? ?newcallback?
Fetches url, if allowed by the security policy. Thecallbacks occur before, during, and
?writecallback? ?endcallback?
after the url data is returned.
browser::displayURL url frame
Causes the browser to display url in frame.
browser::getForm url data ?raw? ?timeout?
Posts data to url. The callbacks are the same as for browser::getURL. If raw is 0, then
?newcallback? ?writecallback? ?endcallback?
data is a name value list that gets encoded automatically. Otherwise, it is assumed to
be encoded already.
browser::displayForm url frame data ?raw?
Posts data to url and displays the result inframe. The raw argument is the same as in
browser::getForm.
The browser::getURL function uses the browser's built-in functions, so it understands proxies and supportsftp:, http:, and file: urls.
Unfortunately, the browser::getURL interface is different from the http::geturl interface. It uses a more complex callback scheme that is due to
the nature of the browser's built-in functions. If you do not specify any callbacks, then the call blocks until all the data is received, and then
that data is returned. The callback functions are described in Table 20-3.
Table 20-3. The browser::getURL callbacks
newcallback name stream url
This is called when data starts to arrive from url. The name identifies the requesting Tclet, and the
mimetype datemodified size
stream identifies the connection. The mimetype, datemodified, and size parameters are attributes of the
returned data.
writecallback name stream size
data
This is called when size bytes of data arrive for Tcllet name over stream.
endcallback name stream
reason data
This is called when the request has completed, although there may be some final bytes in data. The
[ Team LiB ]
reason is one of: EOF, NETWOR_ERROR, USER_BREAK, or TIMEOUT.
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Configuring Security Policies
There are three aspects to the plugin security policy mechanism: policies, features, and trust maps. A policy is an umbrella for a set of
features that are allowed for certain Tclets based on the trust map. A feature is a set of commands and aliases that are defined for a safe
interpreter that requests a policy. The trust map is a filter based on the URL of the Tclet. In the future, trust may bet determined by digital
signatures instead of URLs. The trust map determines whether a Tclet can request a given policy.
Security Policies are configured for each client.
Remember that the configuration files affect the client machine, which is the workstation that runs the Web browser. If you create Tclets that
require custom security policies, you have the burden of distributing the configuration files to clients that will use your Tclets. You also have
the burden of convincing them that your security policy is safe!
The config/plugin.cfg File
The main configuration file is the config/plugin.cfg file in the plugin distribution. This file lists what features are supported by the plugin, and it
defines the URL filters for the trust map.
The configuration file is defined into sections with a section command. The policies section defines which Tclets can use which security
policies. For example, the default configuration file contains these lines in the policies section:
section policies
allow home
disallow intercom
disallow inside
disallow outside
disallow trusted
allow javascript ifallowed trustedJavaScriptURLS \
$originURL
This configuration grants all Tclets the right to use the home policy, disallows all Tclets from using theintercom, inside, outside, and trusted
policies, and grants limited access to the javascript policy. If you are curious, the configuration files are almost Tcl, but not quite. I lost an
argument about that one, so these are stylized configuration files that follow their own rules. For example, the originURL variable is not
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
defined in the configuration file but is a value that is tested later when the Tclet is loaded. I'll just give examples here and you can peer under
the covers if you want to learn how they are parsed.
The ifallowed clause depends on another section to describe the trust mapping for that policy. For thejavascript policy, the config/plugin.cfg file
contains:
section trustedJavascriptURLs
allow http://sunscript.sun.com:80/plugin/javascript/*
Unfortunately, this server isn't running anymore, so you may want to add the Scriptics Web server to your own configuration:
allow http://www.tcl.tk:80/plugin/javascript/*
You can use a combination of allow and disallow rules in a section. The arguments toallow and disallow are URL string match patterns, and
they are processed in order. For example, you could put a liberal allow rule followed bydisallow rules that restrict access, or vice versa. It is
probably safest to explicitly list each server that you trust.
Policy Configuration Files
Each security policy has a configuration file associated with it. For example, the outside policy uses the file outside.cfg file in the config
directory. This file specifies what hosts and ports are accessible to Tclets using the outside policy. For the inside and outside policies, the
configuration files are similar in spirit to the safesock array used to configure the Safesock security policy shown on page 302. There are a set
of allowed hosts and ports, and a set of excluded hosts. The excluded hosts are an exception list. If a host matches the included set but also
matches the excluded set, it is not accessible. There is an included and excluded set for URLs that affect browser::geturl. The settings from
the Tempfile policy shown on page 304 are also part of thehome, inside, and outside configuration files. The configuration files are well
commented, and you should read through them to learn about the configuration options for each security policy.
Security Policy Features
The aliases that make up a security policy are organized into sets called features. The features are listed in the mainconfig/plugin.cfg
configuration file:
variable featuresList {url stream network persist unsafe}
In turn, each security policy configuration file lists what features are part of the policy. For example, the config/home.cfg file lists these
features:
section features
allow url
allow network
allow persist unless {[string match {UNKNOWN *} \
[getattr originURL]]}
Each feature is implemented in a file in the safetcl directory of the distribution. For example, the url feature is implemented insafetcl/url.tcl.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The code in these files follows some conventions in order to work with the configuration mechanism. Each one is implemented inside a
namespace that is a child of the safefeature namespace (e.g., safefeature::url). It must implement an install procedure that is called to initialize
the feature for a new Tclet. It is inside this procedure that the various allow/disallow rules are checked. The cfg::allowed command supports
the rule language used in the .cfg files.
Creating New Security Policies
This book does not describe the details of the configuration language or the steps necessary to create a new security policy. There are
several manual pages distributed with the plugin that explain these details. They can be found on the Web at:
http://www.tcl.tk/plugin/man/
If you are serious about tuning the existing security policies or creating new ones, you should read the existing feature implementations in
detail. As usual, modifying a working example is the best way to proceed! I think it is a very nice property of the plugin that its security policies
are implemented in Tcl source code that is clearly factored out from the rest of the Tcl/Tk and plugin implementation. With a relatively small
amount of code, you can create custom security policies that grant interesting abilities to Tclets.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 21. Multi-Threaded Tcl Scripts
This chapter describes the Thread extension for creating multi-threaded Tcl scripts.
Thread support, a key feature of many languages, is a recent addition to Tcl. That's because the Tcl event loop supports features
implemented by threads in most other languages, such as graphical user interface management, multi-client servers, asynchronous
communication, and scheduling and timing operations. However, although Tcl's event loop can replace the need for threads in many
circumstances, there are still some instances where threads can be a better solution:
Long-running calculations or other processing, which can "starve" the event loop
Interaction with external libraries or processes that don't support asynchronous communication
Parallel processing that doesn't adapt well to an event-driven model
Embedding Tcl into an existing multi-threaded application
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
What are Threads?
Traditionally, processes have been limited in that they can do only one thing at a time. If your application needed to perform multiple tasks in
parallel, you designed the application to create multiple processes. However, this approach has its drawbacks. One is that processes are
relatively "heavy" in terms of the resources they consume and the time it takes to create them. For applications that frequently create new
processes — for example, servers that create a new process to handle each client connection — this can lead to decreased response time.
And widely parallel applications that create many processes can consume so many system resources as to slow down the entire system.
Another drawback is that passing information between processes can be slow because most interprocess communication mechanisms —
such as files, pipes, and sockets — involve intermediaries such as the file system or operating system, as well as requiring a context switch
from one running process to another.
Threads were designed as a light-weight alternative. Threads are multiple flows of execution within the same process. All threads within a
process share the same memory and other resources. As a result, creating a thread requires far fewer resources than creating a separate
process. Furthermore, sharing information between threads is much faster and easier than sharing information between processes.
The operating system handles the details of thread creation and coordination. On a single-processor system, the operating system allocates
processor time to each of an application's threads, so a single thread doesn't block the rest of the application. On multi-processor systems,
the operating system can even run threads on separate processors, so that threads truly can run simultaneously.
The drawback to traditional multi-threaded programming is that it can be difficult to design a thread-safe application — that is, an application
in which one thread doesn't corrupt the resources being used by another thread. Because all resources are shared in a multi-threaded
application, you need to use various locking and scheduling mechanisms to guard against multiple threads modifying resources concurrently.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Thread Support in Tcl
Tcl added support for multi-threaded programming in version 8.1. The Tcl core was made thread-safe. Furthermore, new C functions exposed
"platform-neutral" thread functionality. However, no official support was provided for multi-threaded scripting. Since then, the Thread extension
— originally written by Brent Welch and currently maintained by Zoran Vasiljevic — has become the accepted mechanism for creating
multi-threaded Tcl scripts. The most recent version of the Thread extension as this was being written was 2.5. In general, this version
requires Tcl 8.3 or later, and several of the commands provided require Tcl 8.4 or later.
At the C programming level, Tcl's threading model requires that a Tcl interpreter be managed by only one thread. However, each thread can
create as many Tcl interpreters as needed running under its control. As is the case in even a single-threaded application, each Tcl interpreter
has its own set of variables and procedures. A thread can execute commands in another thread's Tcl interpreter only by sending special
messages to that interpreter's event queue. Those messages are handled in the order received along with all other types of events.
Obtaining a Thread-Enabled Tcl Interpreter
Most binary distributions of Tcl are not thread-enabled, because the default options for building the Tcl interpreters and libraries do not enable
thread support. Thread safety adds overhead, slowing down single-threaded Tcl applications, which constitute the vast majority of Tcl
applications. Also, many Tcl extensions aren't thread safe, and naively trying to use them in a multi-threaded application can cause errors or
crashes.
Unless you can obtain a thread-enabled binary distribution of Tcl, you must compile your own from the Tcl source distribution. This requires
running the configure command with the --enable-threads option during the build process. (See Chapter 48, "Compiling Tcl and Extensions" for
more information.)
You can test whether a particular Tcl interpreter is thread-enabled by checking for the existence of the tcl_platform(threaded) element. This
element exists and contains a Boolean true value in thread-enabled interpreters, whereas it doesn't exist in interpreters without thread
support.
Using Extensions in Multi-Threaded Scripts
Because each interpreter has its own set of variables and procedures, you must explicitly load an extension into each thread that wants to
use it. Only the Thread extension itself is automatically loaded into each interpreter.
You must be careful when using extensions in multi-threaded scripts. Many Tcl extensions aren't thread-safe. Attempting to use them in
multi-threaded scripts often results in crashes or corrupted data.
Tcl-only extensions are generally thread-safe. Of course, they must make no use of other commands or extensions that aren't thread-safe.
But otherwise, multi-threaded operation doesn't add any new issues that don't already affect single-threaded scripts.
You should always assume that a binary extension is not thread-safe unless its documentation explicitly says that it is. And even thread-safe
binary extensions must be compiled with thread support enabled for you to use them in multi-threaded applications. (The default compilation
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
options for most binary extensions don't include thread support.)
Tk isn't truly thread-safe.
Most underlying display libraries (such as X Windows) aren't thread safe — or at least aren't typically compiled with thread-safety enabled.
However, significant work has gone into making the Tk core thread-safe. The result is that you can safely use Tk in a multi-threaded Tcl
application as long as only one thread uses Tk commands to manage the interface. Any other thread that needs to update the interface
should send messages to the thread controlling the interface.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Getting Started with the Thread Extension
You start a thread-enabled tclsh or wish the same as you would a non-threadedtclsh or wish. When started, there is only one thread executing,
often referred to as the main thread, which contains a single Tcl interpreter. If you don't create any more threads, your application runs like
any other single-threaded application.
Make sure that the main thread is the last one to terminate.
The main thread has a unique position in a multi-threaded Tcl script. If it exits, then the entire application terminates. Also, if the main thread
terminates while other threads still exist, Tcl can sometimes crash rather than exiting cleanly. Therefore, you should always design your
multi-threaded applications so that your main thread waits for all other threads to terminate before it exits.
Before accessing any threading features from your application, you must load the Thread extension:
package require Thread
The Thread extension automatically loads itself into any new threads your application creates withthread::create. All other extensions must be
loaded explicitly into each thread that needs to use them. The Thread extension creates commands in three separate namespaces:
The thread namespace contains all of the commands for creating and managing threads, including inter-thread messaging,
mutexes, and condition variables.
The tsv namespace contains all of the commands for creating and managing thread shared variables.
The tpool namespace contains all of the commands for creating and managing thread pools.
Creating Threads
The thread::create command creates a new thread containing a new Tcl interpreter. Any thread can create another thread at will; you aren't
limited to starting threads from only the main thread. The thread::create command returns immediately, and its return value is the ID of the
thread created. The ID is a unique token that you use to interact with and manipulate the thread, in much the same way as you use a channel
identifier returned by open to interact with and manipulate that channel. There are several commands available for introspection on thread IDs:
thread::id returns the ID of the current thread; thread::names returns a list of threads currently in existence; and thread::exists tests for the
existence of a given thread.
The thread::create command accepts a Tcl script as an argument. If you provide a script, the interpreter in the newly created thread executes
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
it and then terminates the thread. Example 21-1 demonstrates this by creating a thread to perform a recursive search for files in a directory.
For a large directory structure, this could take considerable time. By performing the search in a separate thread, the main thread is free to
perform other operations in parallel. Also note how the "worker" thread loads an extension and opens a file, completely independent of any
extensions loaded or files opened in other threads.
Example 21-1 Creating a separate thread to perform a lengthy operation
package require Thread
# Create a separate thread to search the current directory
# and all its subdirectories, recursively, for all files
# ending in the extension ".tcl". Store the results in the
# file "files.txt".
thread::create {
# Load the Tcllib fileutil package to use its
# findByPattern procedure.
package require fileutil
set files [fileutil::findByPattern [pwd] *.tcl]
set fid [open files.txt w]
puts $fid [join $files \n]
close $fid
}
# The main thread can perform other tasks in parallel...
If you don't provide a script argument to thread::create, the thread's interpreter enters its event loop. You then can use thethread::send
command, described on page 328, to send it scripts to evaluate. Often though, you'd like to perform some initialization of the thread before
having it enter its event loop. To do so, use the thread::wait command to explicitly enter the event loop after performing any desired
initialization, as shown in Example 21-2. You should always usethread::wait to cause a thread to enter its event loop, rather thanvwait or
tkwait, for reasons discussed in "Preserving and Releasing Threads" on page 330.
Example 21-2 Initializing a thread before entering its event loop
set httpThread [thread::create {
package require http
thread::wait
}]
After creating a thread, never assume that it has started executing.
There is a distinction between creating a thread and starting execution of a thread. When you create a thread, the operating system allocates
resources for the thread and prepares it to run. But after creation, the thread might not start execution immediately. It all depends on when the
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
operating system allocates execution time to the thread. Be aware that the thread::create command returns when the thread is created, not
necessarily when it has started. If your application has any inter-thread timing dependencies, always use one of the thread synchronization
techniques discussed in this chapter.
Creating Joinable Threads
Remember that the main thread must be the last to terminate. Therefore you often need some mechanism for determining when it's safe for
the main thread to exit. Example 21-3 shows one possible approach: periodically checkingthread::names to see if the main thread is the only
remaining thread.
Example 21-3 Creating several threads in an application
package require Thread
puts "*** I'm thread [thread::id]"
# Create 3 threads
for {set thread 1} {$thread <= 3} {incr thread} {
set id [thread::create {
# Print a hello message 3 times, waiting
# a random amount of time between messages
for {set i 1} {$i <= 3} {incr i} {
after [expr { int(500*rand()) }]
puts "Thread [thread::id] says hello"
}
}] ;# thread::create
puts "*** Started thread $id"
} ;# for
puts "*** Existing threads: [thread::names]"
# Wait until all other threads are finished
while {[llength [thread::names]] > 1} {
after 500
}
puts "*** That's all, folks!"
A better approach in this situation is to use joinable threads, which are supported in Tcl 8.4 or later. A joinable thread allows another thread to
wait upon its termination with the thread::join command. You can use thread::join only with joinable threads, which are created by including
the thread::create -joinable option. Attempting to join a thread not created with-joinable results in an error. Failing to join a joinable thread
causes memory and other resource leaks in your application. Example 21-4 revises the program from Example 21-3 to use joinable threads.
Example 21-4 Using joinable threads to detect thread termination
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
package require Thread
puts "*** I'm thread [thread::id]"
# Create 3 threads
for {set thread 1} {$thread <= 3} {incr thread} {
set id [thread::create -joinable {
# Print a hello message 3 times, waiting
# a random amount of time between messages
for {set i 1} {$i <= 3} {incr i} {
after [expr { int(500*rand()) }]
puts "Thread [thread::id] says hello"
}
}] ;# thread::create
puts "*** Started thread $id"
lappend threadIds $id
} ;# for
puts "*** Existing threads: [thread::names]"
# Wait until all other threads are finished
foreach id $threadIds {
thread::join $id
}
puts "*** That's all, folks!"
The thread::join command blocks.
Be aware that thread::join blocks. While the thread is waiting for thread::join to return, it can't perform any other operations, including servicing
its event loop. Therefore, make sure that you don't use thread::join in situations where a thread must be responsive to incoming events.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Sending Messages to Threads
The thread::send command sends a script to another thread to execute. The target thread's main interpreter receives the script as a special
type of event added to the end of its event queue. A thread evaluates its messages in the order received along with all other types of events.
Obviously, a thread must be in its event loop for it to detect and respond to messages. As discussed on page 324, a thread enters its event
loop if you don't provide a script argument to thread::create, or if you include thethread::wait command in the thread's initialization script.
Synchronous Message Sending
By default, thread::send blocks until the target thread finishes executing the script. The return value ofthread::send is the return value of the
last command executed in the script. If an error occurs while evaluating the script, the error condition is "reflected" into the sending thread;
thread::send generates the same error code, and the target thread's stack trace is included in the value of the
errorInfo variable of the sending
thread:
Example 21-5 Examples of synchronous message sending
set t [thread::create] ;# Create a thread
=> 1572
set myX 42 ;# Create a variable in the main thread
=> 42
# Copy the value to a variable in the worker thread
thread::send $t [list set yourX $myX]
=> 42
# Perform a calculation in the worker thread
thread::send $t {expr { $yourX / 2 } }
=> 21
thread::send $t {expr { $yourX / 0 } }
=> divide by zero
catch {thread::send $t {expr { $yourX / 0 } } } ret
=> 1
puts $ret
=> divide by zero
puts $errorInfo
=> divide by zero
while executing
"expr { $yourX / 0 } "
invoked from within
"thread::send $t {expr { $yourX / 0 } } "
If you also provide the name of a variable to a synchronous thread::send, then it behaves analogously to acatch command; thread::send
returns the return code of the script, and the return value of the last command executed in the script — or the error message — is stored in
the variable. Tcl stores the target thread's stack trace in the sending thread's errorInfo variable.
Example 21-6 Using a return variable with synchronous message sending
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
thread::send $t {incr yourX 2} myY
=> 0
puts $myY
=> 44
thread::send $t {expr { acos($yourX) } } ret
=> 1
puts $ret
=> domain error: argument not in valid range
puts $errorInfo
=> domain error: argument not in valid range
while executing
"expr { acos($yourX) } "
While the sending thread is waiting for a synchronous thread::send to return, it can't perform any other operations, including servicing its event
loop. Therefore, synchronous sending is appropriate only in cases where:
you want a simple way of getting a value back from another thread;
you don't mind blocking your thread if the other thread takes a while to respond; or
you need a response from the other thread before proceeding.
Watch out for deadlock conditions with synchronous message sending.
If Thread A performs a synchronous thread::send to Thread B, and while evaluating the script Thread B performs a synchronous
thread::send
to Thread A, then your application is deadlocked. Because Thread A is blocked in its thread::send, it is not servicing its event loop, and so
can't detect Thread B's message.
This situation arises most often when the script you send calls procedures in the target thread, and those procedures contain thread::send
commands. Under these circumstances, it might not be obvious that the script sent will trigger a deadlock condition. For this reason, you
should be cautious about using synchronous thread::send commands for complex actions. Sending in asynchronous mode, described in the
next section, avoids potential deadlock situations like this.
Asynchronous Message Sending
With the -async option, thread::send sends the script to the target thread in asynchronous mode. In this case,thread::send returns
immediately.
By default, an asynchronous thread::send discards any return value of the script. However, if you provide the name of a variable as an
additional argument to thread::send, the return value of the last command executed in the script is stored as the value of the variable. You can
then either vwait on the variable or create a write trace on the variable to detect when the target thread responds. For example:
thread::send -async $t [list ProcessValues $vals] result
vwait result
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
In this example, the thread::send command returns immediately; the sending thread could then continue with any other operations it needed
to perform. In this case, it executes a vwait on the return variable to wait until the target thread finishes executing the script. However, while
waiting for the response, it can detect and process incoming events. In contrast, the following synchronous thread::send blocks, preventing
the sending thread from processing events until it receives a response from the target thread:
thread::send $t [list ProcessValues $vals] result
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Preserving and Releasing Threads
A thread created with a script not containing a thread::wait command terminates as soon as the script finishes executing. But if a thread
enters its event loop, it continues to run until its event loop terminates. So how do you terminate a thread's event loop?
Each thread maintains an internal reference count. The reference count is set initially to 0, or to 1 if you create the thread with the
thread::create -preserved option. Any thread can increment the reference count afterwards by executingthread::preserve, and decrement the
reference count by executing thread::release. These commands affect the reference count of the current thread unless you specify the ID of
another thread. If a call to thread::release results in a reference count of 0 or less, the thread is marked for termination.
The use of thread reference counts allows multiple threads to preserve the existence of a worker thread until all of the threads release the
worker thread. But the majority of multi-threaded Tcl applications don't require that degree of thread management. In most cases, you can
simply create a thread and then later use thread::release to terminate it:
set worker [thread::create]
thread::send -async $worker $script
# Later in the program, terminate the worker thread
thread::release $worker
A thread marked for termination accepts no further messages and discards any pending events. It finishes processing any message it might
be executing currently, then exits its event loop. If the thread entered its event loop through a call to thread::wait, any other commands
following thread::wait are executed before thread termination, as shown inExample 21-7. This can be useful for performing "clean up" tasks
before terminating a thread.
Example 21-7 Executing commands after thread::wait returns
set t [thread::create {
puts "Starting worker thread"
thread::wait
# This is executed after the thread is released
puts "Exiting worker thread"
}]
Note that if a thread is executing a message script when thread::release is called (either by itself or another thread), the thread finishes
executing its message script before terminating. So, if a thread is stuck in an endless loop, calling thread::release has no effect on the thread.
In fact, there is no way to kill such a "runaway thread."
Always use thread::wait to enter a thread's event loop.
This system for preserving and releasing threads works only if you use the thread::wait command to enter the thread's event loop (or if you
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
did not provide a creation script when creating the thread). If you use vwait or tkwait to enter the event loop,thread::release cannot terminate
the thread.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Error Handling
If an error occurs while a thread is executing its creation script (provided by thread::create), the thread dies. In contrast, if an error occurs
while processing a message script (provided by thread::send), the default behavior is for the thread to stop execution of the message script,
but to return to its event loop and continue running. To cause a thread to die when it encounters an uncaught error, use the thread::configure
command to set the thread's -unwindonerror option to true:
thread::configure $t -unwindonerror 1
Error handling is determined by the thread creating the thread or sending the message. If an error occurs in a script sent by a synchronous
thread::send, then the error condition is "reflected" to the sending thread, as described in Synchronous
"
Message Sending" on page 328. If an
error occurs during thread creation or an asynchronous thread::send, the default behavior is for Tcl to send a stack trace to the standard error
channel. Alternatively, you can specify the name of your own custom error handling procedure with thread::errorproc. Tcl automatically calls
your procedure whenever an "asynchronous" error occurs, passing it two arguments: the ID of the thread generating the error, and the stack
trace. (This is similar to defining your own bgerror procedure, as described in "The bgerror Command" on page 202.) For example, the
following code logs all uncaught errors to the file errors.txt:
Example 21-8 Creating a custom thread error handler
set errorFile [open errors.txt a]
proc logError {id error} {
global errorFile
puts $errorFile "Error in thread $id"
puts $errorFile $error
puts $errorFile ""
}
thread::errorproc logError
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Shared Resources
The present working directory is a resource shared by all interpreters in all threads. If one thread changes the present working directory, then
that change affects all interpreters and all threads. This can pose a significant problem, as some library routines temporarily change the
present working directory during execution, and then restore it before returning. But in a multi-threaded application, another thread could
attempt to access the present working directory during this period and get incorrect results. Therefore, the safest approach if your application
needs to access the present working directory is to store this value in a global or thread-shared variable before creating any other threads.
The following example uses tsv::set to store the current directory in the pwd element of the application shared variable:
package require Thread
# Save the pwd in a thread-shared variable
tsv::set application pwd [pwd]
set t [thread::create {#...}]
Environment variables are another shared resource. If one thread makes a change to an environment variable, then that change affects all
threads in your application. This might make it tempting to use the global env array as a method for sharing information between threads.
However, you should not do so, because it is far less efficient than thread-shared variables, and there are subtle differences in the way
environment variables are handled on different platforms. If you need to share information between threads, you should instead use
thread-shared variables, as discussed in "Shared Variables" on page 337.
The exit command kills the entire application.
Although technically not a shared resource, it's important to recognize that the exit command kills the entire application, no matter which
thread executes it. Therefore, you should never call exit from a thread when your intention is to terminate only that thread.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Managing I/O Channels
Channels are shared resources in most programming languages. But in Tcl, channels are implemented as a per-interpreter resource. Only the
standard I/O channels (stdin, stdout, and stderr) are shared.
Be careful with standard I/O channel on Windows and Macintosh.
When running wish on Windows and Macintosh prior to OS X, you don't have real standard I/O channels, but simulated
stdout and stderr
channels direct output to the special console window. As of Thread 2.5, these simulated channels appear in the main thread's channel list, but
not in any other thread's channel list. Therefore, you'll cause an error if you attempt to access these channels from any thread other than the
main thread.
Accessing Files from Multiple Threads
In a multi-threaded application, avoid having the same file open in multiple threads. Having the same file open for read access in multiple
threads is safe, but it is more efficient to have only one thread read the file and then share the information with other threads as needed.
Opening the same file in multiple threads for write or append access is likely to fail. Operating systems typically buffer information written to a
disk on a per-channel basis. With multiple channels open to the same file, it's likely that one thread will end up overwriting data written by
another thread. If you need multiple threads to have write access to a single file, it's far safer to have one thread responsible for all file access,
and let other threads send messages to the thread to write the data. Example 21-9 shows the skeleton implementation of a logging thread.
Once the log file is open, other threads can call the logger's AddLog procedure to write to the log file.
Example 21-9 A basic implementation of a logging thread
set logger [thread::create {
proc OpenLog {file} {
global fid
set fid [open $file a]
}
proc CloseLog {} {
global fid
close $fid
}
proc AddLog {msg} {
global fid
puts $fid $msg
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
thread::wait
}]
Transferring Channels between Threads
As long as you're working with Tcl 8.4 or later, the Thread extension gives you the ability to transfer a channel from one thread to another with
the thread::transfer command. After the transfer, the initial thread has no further access to the channel. The symbolic channel ID remains the
same in the target thread, but you need some method of informing the target thread of the ID, such as a thread-shared variable. The
thread::transfer command blocks until the target thread has incorporated the channel. The following shows an example of transferring a
channel, and simply duplicating the value of the channel ID in the target thread rather than using a thread-shared variable:
set fid [open myfile.txt r]
# ...
set t [thread::create]
thread::transfer $t $fid
# Duplicate the channel ID in the target thread
thread::send $t [list set fid $fid]
Another option for transferring channels introduced in Thread 2.5 is thread::detach, which detaches a channel from a thread, and
thread::attach, which attaches a previously detached channel to a thread. The advantage to this approach is that the thread relinquishing the
channel doesn't need to know which thread will be acquiring it. This is useful when your application uses thread pools, which are described
on page 342.
The ability to transfer channels between threads is a key feature in implementing a multi-thread server, in which a separate thread is created
to service each client connected. One thread services the listening socket. When it receives a client connection, it creates a new thread to
service the client, then transfers the client's communication socket to that thread.
Transferring socket channels requires special handling.
A complication arises in that you can't perform the transfer of the communication socket directly from the connection handler, like this:
socket -server ClientConnect 9001
proc ClientConnect {sock host port} {
set t [thread::create { ... }]
# The following command fails
thread::transfer $t $sock
}
The reason is that Tcl maintains an internal reference to the communication socket during the connection callback. The thread::transfer
command (and the thread::detach command) cannot transfer the channel while this additional reference is in place. Therefore, we must use
the after command to defer the transfer until after the connection callback returns, as shown inExample 21-10.
Example 21-10 Deferring socket transfer until after the connection callback
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
proc _ClientConnect {sock host port} {
after 0 [list ClientConnect $sock $host $port]
}
proc ClientConnect {sock host port} {
# Create the client thread and transfer the channel
}
One issue in early versions of Tcl 8.4 was a bug that failed to initialize Tcl's socket support when a socket channel was transferred into a
thread. The work-around for this bug is to explicitly create a socket in the thread (which can then be immediately closed) to initialize the
socket support, and then transfer the desired socket. This bug has been fixed, but Example 21-11 illustrates how you can perform extra
initialization in a newly created thread before it enters its event loop:
Example 21-11 Working around Tcl's socket transfer bug by initializing socket support
set t [thread::create {
# Initialize socket support by opening and closing
# a server socket.
close [socket -server {} 0]
# Now sockets can be transferred safely into this thread.
thread::wait
}]
Example 21-12 integrates all of these techniques to create a simple multi-threaded echo server. Note that the server still uses event-driven
interaction in each client thread. Technically, this isn't necessary for such a simple server, because once a client thread starts it doesn't expect
to receive messages from any other thread. If a thread needs to respond to messages from other threads, it must be in its event loop to
detect and service such messages. Because this requirement is common, this application demonstrates the event-driven approach.
Example 21-12 A multi-threaded echo server
package require Tcl 8.4
package require Thread 2.5
if {$argc > 0} {
set port [lindex $argv 0]
} else {
set port 9001
}
socket -server _ClientConnect $port
proc _ClientConnect {sock host port} {
# Tcl holds a reference to the client socket during
# this callback, so we can't transfer the channel to our
# worker thread immediately. Instead, we'll schedule an
# after event to create the worker thread and transfer
# the channel once we've re-entered the event loop.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
after 0 [list ClientConnect $sock $host $port]
}
proc ClientConnect {sock host port} {
# Create a separate thread to manage this client. The
# thread initialization script defines all of the client
# communication procedures and puts the thread in its
# event loop.
set thread [thread::create {
proc ReadLine {sock} {
if {[catch {gets $sock line} len] || [eof $sock]} {
catch {close $sock}
thread::release
} elseif {$len >= 0} {
EchoLine $sock $line
}
}
proc EchoLine {sock line} {
if {[string equal -nocase $line quit]} {
SendMessage $sock \
"Closing connection to Echo server"
catch {close $sock}
thread::release
} else {
SendMessage $sock $line
}
}
proc SendMessage {sock msg} {
if {[catch {puts $sock $msg} error]} {
puts stderr "Error writing to socket: $error"
catch {close $sock}
thread::release
}
}
# Enter the event loop
thread::wait
}]
# Release the channel from the main thread. We use
# thread::detach/thread::attach in this case to prevent
# blocking thread::transfer and synchronous thread::send
# commands from blocking our listening socket thread.
thread::detach $sock
# Copy the value of the socket ID into the
# client's thread
thread::send -async $thread [list set sock $sock]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Attach the communication socket to the client-servicing
# thread, and finish the socket setup.
thread::send -async $thread {
thread::attach $sock
fconfigure $sock -buffering line -blocking 0
fileevent $sock readable [list ReadLine $sock]
SendMessage $sock "Connected to Echo server"
}
}
vwait forever
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
Shared Variables
Standard Tcl variables are a per-interpreter resource; an interpreter has no access to variables in another interpreter. For the simple
exchange of information between threads, you can substitute the values of variables into a script that you send to another thread, and obtain
the return value of a script evaluated by another thread. But this technique is inadequate for sharing information among multiple threads, and
inefficient when transferring large amounts of information.
The Thread extension supports the creation of thread-shared variables, which are accessible by all threads in an application. Thread-shared
variables are stored independent of any interpreter, so if the thread that originally created a shared variable terminates, the shared variable
continues to exist. Shared variables are stored in collections called arrays. The term is somewhat unfortunate, because while shared variable
arrays are similar to standard Tcl arrays, they do not use the same syntax. Your application can contain as many shared variable arrays as
you like.
Because of the special nature of shared variables, you cannot use the standard Tcl commands to create or manipulate shared variables, or
use standard variable substitution syntax to retrieve their values. (This also means that you cannot use shared variables as a widget's
-textvariable or -listvariable, with vwait or tkwait, or with variable traces.) All commands for interacting with shared variables are provided by the
Thread extension in the tsv namespace. Most of the tsv commands are analogous to Tcl commands for creating and manipulating standard Tcl
variables. Table 21-3 on page 346 describes all of the tsv commands.
You create a shared variable with tsv::set, specifying the array name, the variable name (sometimes also referred to as the shared array
element), and the value to assign to it. For example:
tsv::set application timeout 10
To retrieve the value of a shared variable, either usetsv::set without a value or calltsv::get. The two commands shown below are equivalent:
tsv::set application timeout
tsv::get application timeout
All shared variable commands are guaranteed to be atomic. A thread locks the variable during the entire command. No other thread can
access the variable until the command is complete; if a thread attempts to do so, it blocks until the variable is unlocked. This simplifies the
use of shared variables in comparison to most other languages, which require explicit locking and unlocking of variables to prevent possible
corruption from concurrent access by multiple threads.
This locking feature is particularly useful in the class of tsv commands that manipulate lists. Standard Tcl commands likelinsert and lreplace
take a list value as input, and then return a new list as output. Modifying the value of a list stored in a standard Tcl variable requires a
sequence like this:
set states [linsert $states 1 California Nevada]
Doing the same with shared variables is problematic:
tsv::set common cities \
[linsert [tsv::get common cities] 1 Yreka Winnemucca]
After reading the shared variable with tsv::get, another thread could modify the value of the variable before thetsv::set command executes,
resulting in data corruption. For this reason, the tsv commands that manipulate list values actually modify the value of the shared variable.
Data corruption by another thread won't occur because the shared variable is locked during the entire execution of the command:
tsv::linsert common cities 1 Yreka Winnemucca
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Mutexes and Condition Variables
Mutexes and condition variables are thread synchronization mechanisms. Although they are used frequently in other languages, they aren't
needed as often in Tcl because of Tcl's threading model and the atomic nature of all shared variable commands. All mutex and condition
variable commands are provided by the Thread extension in the thread namespace.
Mutexes
A mutex, which is short for mutual exclusion, is a locking mechanism. You use a mutex to protect shared resources — such as shared
variables, serial ports, databases, etc. — from concurrent access by multiple threads. Before accessing the shared resource, the thread
attempts to lock the mutex. If no other thread currently holds the mutex, the thread successfully locks the mutex and can access the resource.
If another thread already holds the mutex, then the attempt to lock the mutex blocks until the other thread releases the mutex.
This sequence is illustrated in Example 21-13. The first step is creating a mutex with thethread::mutex create operation, which returns a
unique token representing the mutex. The same token is used in all threads, and so you must make this token available (for example, through
a shared variable) to all threads that access the shared resource.
Example 21-13 Using a mutex to protect a shared resource
# Create the mutex, storing the mutex token in a shared
# variable for other threads to access.
tsv::set db mutex [thread::mutex create]
# ...
# Lock the mutex before accessing the shared resource.
thread::mutex lock [tsv::get db mutex]
# Use the shared resource, and then unlock the mutex.
thread::mutex unlock [tsv::get db mutex]
# Lather, rinse, repeat as needed...
thread::mutex destroy [tsv::get db mutex]
Mutexes rely on threads being "good citizens."
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Mutexes work only if all threads in an application use them properly. A "rogue" thread can ignore using a mutex and access the shared
resource directly. Therefore, you should be very careful to use your mutexes consistently when designing and implementing your
application.
Condition Variables
A condition variable is a synchronization mechanism that allows one or more threads to sleep until they receive notification from another
thread. A condition variable is associated with a mutex and a boolean condition known as a predicate. A thread uses the condition variable to
wait until the boolean predicate is true. A different thread changes the state of the predicate to true, and then notifies the condition variable.
The mutex synchronizes thread access to the data used to compute the predicate value. The general usage pattern for the signalling thread
is:
Lock the mutex
Change the state so the predicate is true
Notify the condition variable
Unlock the mutex
The pattern for a waiting thread is:
Lock the mutex
Check the predicate
If the predicate is false, wait on the condition variable until notified
Do the work
Unlock the mutex
In practice, a waiting thread should always check the predicate inside a while loop, because multiple threads might be waiting on the same
condition variable. A waiting thread automatically releases the mutex when it waits on the condition variable. When the signalling thread
notifies the condition variable, all threads waiting on that condition variable compete for a lock on the mutex. Then when the signalling thread
releases the mutex, one of the waiting threads gets the lock. It is quite possible for that thread then to change the state so that the predicate
is no longer true when it releases the lock. For example, several worker threads forming a thread pool might wait until there is some type of
job to process. Upon notification, the first worker thread takes the job, leaving nothing for the other worker threads to process.
This sequence for using a condition variable sounds complex, but is relatively easy to code. Example 21-14 shows the sequence for the
signalling thread. The first step is creating a condition variable with the thread::cond create operation, which returns a unique token
representing the condition variable. As with mutexes, the same token is used in all threads, and so you must make this token available (for
example, through a shared variable) to all threads that access the condition variable. When the thread is ready to update the predicate, it first
locks the associated mutex. Then it notifies the condition variable with thread::cond notify and finally unlocks the mutex.
Example 21-14 Standard condition variable use for a signalling thread
# Create the condition variable and accompanying mutex.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Use shared variables to share these tokens with all other
# threads that need to access them.
set cond [tsv::set tasks cond [thread::cond create]]
set mutex [tsv::set tasks mutex [thread::mutex create]]
# When we're ready to update the state of the predicate, we
# must first obtain the mutex protecting it.
thread::mutex lock $mutex
# Now update the predicate. In this example, we'll just set a
# shared variable to true. In practice, the predicate can be
# more complex, such as the length of a list stored in a
# shared variable being greater than 0.
tsv::set tasks predicate 1
# Notify the condition variable, waking all waiting threads.
# Each thread will block until it can lock the mutex.
thread::cond notify $cond
# Unlock the mutex.
thread::mutex unlock $mutex
Example 21-15 shows the sequence for a waiting thread. When a thread is ready to test the predicate, it must first lock the mutex protecting it.
If the predicate is true, the thread can continue processing, unlocking the mutex when appropriate. If the predicate is false, the thread
executes thread::cond wait to wait for notification. The thread::cond wait command atomically unlocks the mutex and puts the thread into a
wait state. Upon notification, the thread atomically locks the mutex (blocking until it can obtain it) and returns from the thread::cond wait
command. It then tests the predicate, and repeats the process until the predicate is true.
Example 21-15 Standard condition variable use for a waiting thread
set mutex [tsv::get tasks mutex]
set cond [tsv::get tasks cond]
# Lock the mutex before testing the predicate.
thread::mutex lock $mutex
# Test the predicate, if necessary waiting until it is true.
while {![tsv::get tasks predicate]} {
# Wait for notification on the condition variable.
# thread::cond wait internally unlocks the mutex,
# blocks until it receives notification, then locks
# the mutex again before returning.
thread::cond wait $cond $mutex
}
# We now hold the mutex and know the predicate is true. Do
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# whatever processing is desired, and unlock the mutex when
# it is no longer needed.
thread::mutex unlock $mutex
Tcl's threading model greatly reduces the need for condition variables. It's usually much simpler to place a thread in its event loop with
thread::wait, and then send it messages withthread::send. And for applications where you want a thread pool to handle jobs on demand, the
Thread extension's built-in thread pool implementation is far easier than creating your own with condition variables.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Thread Pools
A thread pool is a common multi-threaded design pattern. A thread pool consists of several worker threads that wait for jobs to perform. When
a job is sent to the thread pool, one of the available worker threads processes it. If all worker threads are busy, either additional worker
threads are created to handle the incoming jobs, or the jobs are queued until worker threads are available.
The tpool namespace of the Thread extension provides several commands for creating and managing thread pools. Using these commands is
much easier than trying to build your own thread pools from scratch using mutexes, condition variables, etc. Thread pool support was added
to the Thread extension in version 2.5.
The tpool::create command creates a thread pool, returning the ID of the new thread pool. There are several options totpool::create that allow
you to configure the behavior of the thread pool. The -minthreads option specifies the minimum number of threads in the pool. This number of
threads is created when the thread pool is created, and as worker threads in the pool terminate, new worker threads are created to bring the
number up to this minimum. The -maxthreads option specifies the maximum number of worker threads allowed. If a job is posted to the
thread pool and there are no idle worker threads available, a new worker thread is created to handle the job only if the number of worker
threads won't exceed the maximum number. If the maximum has been reached, the job is queued until a worker thread is available. The
-idletime option specifies the number of seconds that a worker thread waits for a new job before terminating itself to preserve system
resources. And the -initcmd and -exitcmd options provide scripts to respectively initialize newly created worker threads and clean up exiting
worker threads.
Once you have created a thread pool, you send jobs to it with the tpool::post command. A job consists of an arbitrary Tcl script to execute.
The job is executed by the first available worker thread in the pool. If there are no idle worker threads, a new worker thread is created, as
long as the number of worker threads doesn't exceed the thread pool maximum. If a new worker thread can't be created, the tpool::post
command blocks until a worker thread can handle the job, but while blocked the posting thread still services its event loop.
The return value of tpool::post is a job ID. To receive notification that a job is complete, your thread must calltpool::wait. The tpool::wait
command blocks, but continues to service the thread's event loop while blocked. Additionally, the tpool::wait command can wait for several
jobs simultaneously, returning when any of the jobs are complete. The return value of tpool::wait is a list of completed job IDs.
After tpool::wait reports that a job is complete, you can call tpool::get to retrieve the result of the job, which is the return value of the last
command executed in the job script. If the job execution resulted in an error, the error is "reflected" to the posting thread: tpool::get raises an
error and the values of errorInfo and errorCode are updated accordingly.
Finally, a thread pool can be preserved and released in much the same way as an individual thread. Each thread pool maintains an internal
reference count, which is initially set to 0 upon creation. Any thread can increment the reference count afterwards by executing
tpool::preserve, and decrement the reference count by executingtpool::release. If a call to tpool::release results in a reference count of 0 or
less, the thread pool is marked for termination. Any further reference to a thread pool once it is marked for termination results in an error.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The Thread Package Commands
The commands of the Thread extension are grouped into three separate namespaces, based on their functionality. This section summarizes
the commands found in each namespace.
The thread Namespace
The thread namespace contains all of the commands for creating and managing threads, including inter-thread messaging, mutexes, and
condition variables. Table 21-1 describes all of the commands contained in thethread namespace.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 21-1. The commands of the thread namespace
thread::attach channel
Attaches the previously detached channel into current interpreter of the current thread.
thread::cond create
Returns a token for a newly created condition variable.
thread::cond destroy cond
Destroys the specified condition variable.
thread::cond notify cond
Wakes up all threads waiting on the specified condition variable.
thread::cond wait cond
Blocks until the specified condition variable is signaled by another thread with thread::cond notify, or until the
mutex ?ms?
optional timeout in milliseconds specified by ms expires. The mutex must be locked by the calling thread
before calling thread::cond wait. While waiting on the cond, the command releases mutex. Before returning to
the calling thread, the command re-acquires mutex again.
thread::configure id
?option?value? ?option
Queries or sets thread configuration options, as described in Table 21-2.
value...?
thread::create ?-joinable?
Creates a thread, returning the thread's ID. The -joinable flag allows another thread to wait for termination of
?-preserved? ?script?
this thread with thread::join. The -preserved flag sets the thread's initial reference count to 1, rather than the
default of 0. (See thread::preserve and thread::release.) If provided, the thread executes the script, then exits;
otherwise, it enters an events loop to wait for messages.
thread::detach channel
Detaches the specified channel from the current thread so that it no longer has access to it. Any single thread
can then thread::attach the channel to gain access to it.
thread::errorproc ?proc?
Registers a procedure to handle errors that occur when performing asynchronous thread::send commands.
When called, proc receives two argument: the ID of the thread that generated the error, and the value of that
thread's errorInfo variable.
thread::eval ?-lock mutex?
Concatenates the arguments and evaluates the resulting script under the mutex protection. If no mutex is
arg ?arg...?
specified, an internal static one is used for the duration of the evaluation.
thread::exists id
Returns boolean indicating whether or not the specified thread exists.
thread::id
Returns the current thread's ID.
thread::join id
Blocks until the target thread terminates. (Available only with Tcl 8.4 or later.)
thread::mutex create
Returns a token for a newly created mutex.
thread::mutex destroy
mutex
Destroys the mutex.
thread::mutex lock mutex
Locks the mutex, blocking until it can gain exclusive access.
thread::mutex unlock
mutex
Unlocks the mutex.
thread::names
Returns a list of the IDs of all running threads.
thread::preserve ?id?
Increments the reference count of the indicated thread, or the current thread if noid is given.
thread::release ?-wait?
Decrements the reference count of the indicated thread, or the current thread if no id is given. If the reference
?id?
count is 0 or less, mark the thread for termination. If -wait is specified, the command blocks until the target
thread terminates.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
thread::send ?-async? id
Sends the script, to thread id. If -async is specified, do not wait for script to complete. Stores the result of script
script ?varname?
in varname, if provided.
thread::transfer id
Transfers the open channel from the current thread to the main interpreter of the target thread. This command
channel
blocks until the target thread incorporates the channel. (Available only with Tcl 8.4 or later.)
thread::unwind
Terminates a prior thread::wait to cause a thread to exit. Deprecated in favor ofthread::release.
thread::wait
Enters the event loop.
The thread::configure command allows an application to query and set thread configuration options, in much the same way as thefconfigure
command configures channels. Table 21-2 lists the available thread configuration options.
Table 21-2. Thread configuration options
-eventmark int
Specifies the maximum number of pending scripts sent with thread::send that the thread accepts. Once the maximum
is reached, subsequent thread::send messages to this script block until the number of pending scripts drops below
the maximum. A value of 0 (default) allows an unlimited number of pending scripts.
-unwindonerror
boolean
If true, the thread "unwinds" (terminates its event loop) on uncaught errors. Default is false.
The tsv Namespace
The tsv namespace contains all of the commands for creating and managing thread shared variables.Table 21-3 describes all of the
commands contained in the tsv namespace.
Table 21-3. The commands of the tsv namespace
tsv::append array element value
Appends to the shared variable likeappend.
?value ...?
tsv::exists array ?element?
Returns boolean indicating whether the given element exists, or if no element is given, whether the
shared array exists.
tsv::get array element
Returns the value of the shared variable. If varname is provided, the value is stored in the variable, and
?varname?
the command returns 1 if the element existed, 0 otherwise.
tsv::incr array element
Increments the shared variable likeincr.
?increment?
tsv::lappend array element
Appends elements to the shared variable likelappend.
value ?value ...?
tsv::lindex array element index
Returns the indicated element from the shared variable, similar tolindex.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
tsv::linsert array element index
Atomically inserts elements into the shared variable, similar tolinsert, but actually modifying the variable.
value ?value ...?
tsv::llength array element
Returns the number of elements in the shared variable, similar tollength.
tsv::lock array arg ?arg ...?
Concatenates the args and evaluates the resulting script. During script execution, the command locks
the specified shared array with an internal mutex.
tsv::lpop array element ?index?
Atomically deletes the value at the index list position from the shared variable and returns the value
deleted. The default index is 0.
tsv::lpush array element value
Atomically inserts the value at the index list position in the shared variable. The defaultindex is 0.
?index?
tsv::lrange array element first
Returns the indicated range of elements from the shared variable, similar tolrange.
last
tsv::lreplace array element
Atomically replaces elements in the shared variable, similar to lreplace, but actually modifying the
value ?value ...?
variable.
tsv::lsearch array element
Returns the index of the first element in the shared variable matching the pattern, similar to lsearch.
?mode? pattern
Supported modes are: -exact, -glob (default), and -regexp.
tsv::move array old new
Atomically renames the shared variable fromold to new.
tsv::names ?pattern?
Returns a list of all shared variable arrays, or those whose names match the optional glob pattern.
tsv::object array element
Creates and returns the name of an accessor command for the shared variable. Other tsv commands
are available as subcommands of the accessor to manipulate the shared variable.
tsv::pop array element
Atomically returns the value of the shared variable and deletes theelement.
tsv::set array element ?value?
Sets the value of the shared variable, creating it if necessary. Ifvalue is omitted, the current value is
returned.
tsv::unset array ?element?
Deletes the shared variable, or the entirearray if no element is specified.
The tpool Namespace
The tpool namespace contains all of the commands for creating and managing thread pools.Table 21-4 describes all of the commands
contained in the tpool namespace.
Table 21-4. The commands of the tpool namespace
tpool::create
Creates a thread pool, returning the thread pool's ID.Table 21-5 describes supported configuration options.
?options?
tpool::post tpoolId
Sends a Tcl script to the specified thread pool for execution, returning the ID of the posted job. This command
script
blocks (entering the event loop to service events) until a worker thread can service the job
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
tpool::wait tpoolId
Blocks (entering the event loop to service events) until one or more of the jobs whose IDs are given by the jobList
jobList ?varName?
argument are completed. Returns a list of completed jobs from jobList. If provided, varName is set to a list of jobs
from jobList that are still pending.
tpool::get tpoolId
Returns the result of the specified jobId. tpool::wait must have reported previously that the job is complete. If no
jobId
error occurred in the job, the result is the return value of the last command executed in the job script. Any error
encountered in job execution is in turn thrown by tpool::get, with the errorCode and errorInfo variables set
appropriately.
tpool::names
Returns a list of existing thread pool IDs.
tpool::preserve
Increments the reference count of the indicated thread pool.
tpoolId
tpool::release tpoolId
Decrements the reference count of the indicated thread pool. If the reference count is 0 or less, mark the thread
pool for termination.
The tpool::create command supports several options for configuring thread pools. Table 21-5 lists the available thread pool configuration
options.
Table 21-5. Thread pool configuration options
-minthreads
number
The minimum number of threads. If the number of live threads in the thread pool is less than this number (including
when the thread pool is created initially), new threads are created to bring the number up to the minimum. Default is 0.
-maxthreads
number
The maximum number of threads.When a job is posted to the thread pool, if there are no idle threads and the number of
existing worker threads is at the maximum, the thread posting the job blocks (in its event loop) until a worker thread is
free to handle the job. Default is 4.
-idletime
seconds
The maximum idle time, in seconds, before a worker thread exits (as long as the number of threads doesn't drop below
the -minthreads limit). Default value is 0, meaning idle threads wait forever.
-initcmd script
A script that newly created worker threads execute.
-exitcmd script
A script that worker threads execute before exiting.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 22. Tclkit and Starkits
Tclkit is a version of the Tcl/Tk interpreter that is designed to make packaging and deployment of Tcl applications easy. Tclkit includes Tcl/Tk,
[incr Tcl], the Metakit database, and TclVFS. A Starkit is a special file that contains all the scripts and supporting files you need for your Tcl
application. This chapter describes how to package and deploy your application as a Starkit.
Tclkit was created by Jean-Claude Wippler as a way to make deploying Tcl applications easier. Tclkit is an extended Tcl interpreter that
includes the Metakit database, the [incr Tcl] object-oriented system, and a Virtual File System (VFS). The database is cleverly stored as part
of the Tclkit application itself, and the VFS interface is used to make the database look like a private filesystem. Tclkit puts all the scripts
normally associated with Tcl and its extensions into this database. The result is a self-contained, single file distribution of Tcl that includes
extensions for your GUI, object-oriented programming, a database, and a few other goodies.
Metakit is a fast, transactional database with a simple programming API. Like Tcl, Metakit is a compact, efficient library designed to be
embedded into applications. The Tcl interface to Metakit gives you a simple, easy way to manipulate persistent data. Although you do not
have to program Metakit directly when using Starkits, this Chapter does provide a short introduction to using Metakit to store data for your
application.
A Starkit is a Metakit database file that stores your application. The VFS interface makes this transparent. Tclkit processes the Starkit just like
tclsh or wish, and your application doesn't even have to know it is packaged inside a Starkit.
The original Tclkit used an early version of VFS created by Matt Newman. TclVFS was ported to the Tcl core in version 8.4.1 by Vince Darley.
Today you can build Tclkit using unmodified Tcl sources. The ActiveTcl distribution includes Metakit, TclVFS and tools to create Starkits, too.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Getting Started with Tclkit
Using Tclkit is easy. Just copy the version for your platform (e.g., Linux, Windows or Solaris) into a convenient location under the name tclkit
(or tclkit.exe on Windows.) The CD-ROM has builds for lots of platforms, and you can find more at the Tclkit home page:
http://www.equi4.com/tclkit
You can use the tclkit application just like tclsh. Run with no arguments, it prints a prompt and you can type Tcl commands interactively. If you
pass a file argument, then it sources that file just as tclsh would. To use tclkit like wish, you must add this to your scripts:
package require Tk
Although you can use tclkit to source .tcl files, tclkit is normally used to interpret Starkits, which have a.kit suffix. On UNIX, Starkits use the #!
header to associate themselves with tclkit. Make sure thattclkit is in a directory named in yourPATH environment variable. On Windows, you
can associate tclkit.exe with the .kit extension. Mac OS X behaves like UNIX (yay!). On Mac Classic systems you can use theFile Source menu
to source .kit files. Creating Starkits is described on page 352.
Inside a Starkit
Tclkit uses the Virtual Filesystem extension to make records in a Metakit database look like files and directories to your application. Through a
simple packaging step described shortly, you can easily put all of the Tcl scripts and other supporting files that make up your application into a
single database file. The Virtual Filesystem (VFS) extension lets you transparently access these files through the regular file system interface
(e.g., open, gets, source, even cd.)
A Starkit is a Metakit database that stores an application. The great thing about a Starkit is that it is a single file so it is easy to manage. There
is no need to unpack files or run an installer to set things up. Instead, you can distribute your application as two files: the Tclkit interpreter and
the Starkit file. Both of these embed a virtual file system that include all the bits and pieces needed for Tcl/Tk and your application. The Tclkit
file is platform-specific because it contains Tcl and all the other extensions in a compiled form. There are pre-compiled Tclkits for Windows,
Macintosh, and many flavors of Unix. The Starkit file is platform-independent. You can use it with the appropriate Tclkit interpreter on different
platforms.
Deploying Applications as Starkits
The key benefit of Tclkit and Starkits is easy deployment. Users just copy tclkit and your Starkits onto their system; there is no special
installation step. You can even have different versions of tclkit and they don't interfere with each other. If users get tired of your application,
they just remove the files.
Creating Starkits is made easy with the sdx application, which was created by Steve Landers and Jean-Claude Wippler. You organize your
collection of application scripts, data files, binary graphics, and online documentation into a file system directory structure. Then you use sdx
to wrap that into a Starkit. Creating your own Starkits is described on page 352.
You can include binary extensions in a Starkit and dynamically load them. The load command automatically copies the shared library out of
the VFS to a temporary location, and loads the library from that location. The temporary file is necessary because the host OS cannot find the
library inside the Starkit. Binary extensions make the Starkit platform-specific, but it is possible to put libraries for different platforms into the
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Starkit. For example, the kitten.kit Starkit includes extensions for Windows, Linux, and Solaris.
You can combine Tclkit and a Starkit into a Starpack. The advantage of this is that it reduces deployment to a single file. The main drawback
is that the Starpack file is relatively large, and it is platform-specific. Use sdx to create Starpacks as described later.
The Starkit archive contains a growing collection of Starkits that include applications, games, development tools, a Wiki, tutorials and
documentation bundles. There is a copy of the archive on the CD-ROM, and its home page is:
http://mini.net/sdarchive/
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Virtual File Systems
The key concept in Tclkit and Starkits is the virtual file system (VFS). You may be familiar with the file system interface inside a Unix operating
system that makes everything look the same (files, tape drives, network sockets, pipes). The nice thing about Unix is that a system
programmer can use the same APIs to access all of these things. The goal of the Tcl VFS interface is similar in spirit: use the regular Tcl file
system interface to make things like embedded databases, FTP servers, and zip files available to the Tcl programmer. The VFS layer in Tcl
8.4 is implemented below the Tcl C APIs for file system access (e.g., Tcl_CreateChannel, Tcl_FSDeleteFile). The result is that scripting
commands (e.g., open, file, glob) and any C extensions that use these APIs automatically access any Virtual File Systems that are part of the
Starkit.
The virtual file system is mounted on a regular file; by default it is mounted on the Starkit. For example, if the Starkit is namedfoo.kit, and its
virtual file system contains a file named main.tcl, then it is visible to the Tcl application asfoo.kit/main.tcl. The VFS can contain a whole
directory structure (e.g., foo.kit/lib/httpd.tcl or foo.kit/htdocs/help/index.html.)
The next section explores some simple Starkits and their file system structure. The main idea is that the Starkit file itself is the root of the
virtual file system hierarchy, and everything in the virtual file system is visible to Tcl via the regular scripting commands. If the VFS supports it,
you can create and write files as well as read them.
Tclkit includes the TclVFS extension that exposes the ability to implement new file systems in Tcl. Ordinarily you do not need to use the vfs
API directly when using a Starkit. However, the TclVFS project has created a number of VFS implementations that let you access web sites,
FTP sites, zip files, tar files, and more through the filesystem interface. Tclkit does not include all of these, but you can get them as part of the
TclVFS extension. Its home page is
http://sourceforge.net/projects/tclvfs
Accessing a Zip File Through a VFS
Tclkit includes a zipvfs package that lets you mount a compressed ZIP file archive and read its contents. This is currently limited to read-only
access. Example 22-1 uses the vfs::zip::Mount command to set up the VFS access. If you use other VFS types supplied by the TclVFS
extension, you will find that each supplies its own vfs::vfs_type::Mount API:
Example 22-1 Accessing a Zip file through a VFS
package require vfs::zip
=> 1.0
# Mount the zip file on "xyz"
vfs::zip::Mount c:/downloads/tclhttpd343.zip xyz
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
=> filecb15a8
# Examine the contents
glob xyz/*
=> xyz/tclhttpd3.4.3
# Open and read file inside the zip archive
set in [open xyz/tclhttpd3.4.3/README]
=> rechan16
gets $in
This HTTPD is written in Tcl and Tk.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Using sdx to Bundle Applications
Sdx, which stands for Starkit Developer eXtension, is an application that you run from the Unix, Windows, or MacOS command line to create
and manipulate Starkits. It is itself a Starkit, of course. The sdx application is on the CD-ROM, and you can find a link to it from the Starkit
home page:
http://www.equi4.com/starkit/
Creating a Simple Starkit
Creating a Starkit amounts to creating a directory structure that contains the files you need, and then wrapping them up with sdx. Create files
under kitname.vfs, and wrap them into thekitname.kit Starkit with:
sdx wrap kitname.kit
In simple cases, sdx will create the directory structure for you. For example, if you have a self-contained Tcl script calledhello.tcl, then you can
turn it into a Starkit like this:
sdx qwrap hello.tcl
The qwrap operation (i.e., "quick wrap") creates a new Starkit,hello.kit, that includes the original hello.tcl script organized into a virtual file
system hierarchy with some additional support files. You run the Starkit like this:
tclkit hello.kit
On Unix systems you can also execute the Starkit directly. The file uses the #! syntax to specify that tclkit should run the file. On Windows,
you can achieve the same effect by associating tclkit.exe with files that end in.kit.
Examining a Starkit
There are two ways to look at a Starkit. You can get a listing of the files with the sdx lsk operation, or you can use sdx unwrap to extract the
files from the Starkit into a kitname.vfs directory. Example 22-2 shows the lsk output for hello.kit. The dates are inYY/MM/DD format:
Example 22-2 The output of sdx lsk hello.kit
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
hello.kit:
dir lib/
67 02/11/08 12:07 main.tcl
hello.kit/lib:
dir app-hello/
hello.kit/lib/app-hello:
43 02/11/08 12:10 hello.tcl
72 02/11/08 12:07 pkgIndex.tcl
Standard Package Organization
The qwrap operation turns the hello.tcl script into the app-hello package. If necessary, sdx adds a package provide app-hello 1.0 command to
the hello.tcl script. It also creates a shortmain.tcl script that initializes the Starkit system and invokeshello.tcl by doing apackage require.
Example 22-3 shows main.tcl:
Example 22-3 The main program of a Starkit
package require starkit
starkit::startup
package require app-hello
When you run the Starkit, its Metakit database is mounted into a Virtual File System that is visible to the Tcl application. Tclkit sources the
main.tcl script it finds in the VFS. The starkit::startup procedure updates the auto_path to contain the Starkit's lib directory, so any packages
stored there are available to the package mechanism. By convention, the application is put into a package with the name app-kitname.
Example 22-4 shows the pkgIndex.tcl, which causes thepackage require app-hello command to source hello.tcl.
Example 22-4 The pkgIndex.tcl in a Starkit
package ifneeded app-hello 1.0 \
[list source [file join $dir hello.tcl]]
The dir variable is set by the package mechanism to be the directory containing thepkgIndex.tcl file. That the lib directory happens to be inside
the virtual file system is completely transparent to the package mechanism. The package mechanism is described in more detail in Chapter
12.
Creating a Starpack
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A Starpack contains a copy of Tclkit and your Starkit. Use sdx to create Starpacks. The -runtime flag specifies which Tclkit application you
want to merge with your Starkit. For example, to build a Windows Starpack out of our hello.tcl application:
sdx wrap hello.kit -runtime tclkit-win32.exe
To build a Starkit for Linux, use the appropriate runtime:
sdx wrap hello.kit -runtime tclkit-linux-x86
There are 4 variations of the Windows Tclkit. One option uses zlib to automatically compress Tclkit and the Metakit database. These have
.upx in their name. The other creates a console-mode application that does not include Tk. These have
-sh in their name. The smallest Tclkit,
tclkit-win32-sh.upx.exe, is only 450 K. Eventclkit-win32.upx.exe is only 907 K, so you really can create complete applications that fit easily
onto a floppy disk!
The auto-compress variation is also available on the Linux x86 builds as the tclkit-linux-x86.upx.bin runtime file. Check the Tclkit home page
for the latest set of Tclkit builds:
http://www.equi4.com/tclkit
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Exploring the Virtual File System in a Starkit
Example 22-2 introduces the standard, recommended VFS structure for a Starkit that makes everything into a package, even the main
application. However, in this section we are going to show a Starkit without packages in order to get a feel for how the VFS works. For
example, instead of doing the package require hello, the main.tcl script of Example 22-3 could source the hello.tcl file directly:
source hello.kit/lib/app-hello/hello.tcl
However, this only works if you are in the directory containing the hello.kit file.
Use starkit::topdir to find things in the Starkit Virtual File System.
The starkit::topdir variable is set by starkit::startup to be the file name of the Starkit, which is also the root of the Virtual File System inside the
Starkit. The value of starkit::topdir is an absolute pathname, so it is always valid.Example 22-5 shows a Starkit that manipulates its virtual file
system.
Example 22-5 A Starkit that examines its Virtual File System
package require starkit
starkit::startup
puts "Contents of VFS before"
foreach f [glob [file join $starkit::topdir *]] {
puts "[file size $f] $f"
}
puts "Reading data file"
set in [open [file join starkit::topdir data]]
set X [read $in]
puts $X
close $in
set out [open [file join $starkit::topdir data.new w]]
puts $out $X
close $out
puts "Contents of VFS after"
foreach f [glob [file join $starkit::topdir *]] {
puts "[file size $f] $f"
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Create the Starkit by putting the code in Example 22-5 into a file namedmain.tcl in the write.vfs directory. Then use sdx as shown in Example
22-6:
Example 22-6 Creating a simple Starkit
# These are UNIX shell commands
mkdir write.vfs
cp 22_5.tcl write.vfs/main.tcl
sdx wrap write.kit
tclkit write.kit
If you run the write.kit file more than once you will notice that thewrite.kit/data.new file does not persist between runs. This is because, by
default, the Metakit database is modified in main memory and it is not written out to the Starkit file. If you want to store files long term, use the
-writable flag to sdx:
sdx wrap write.kit -writable
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Creating
tclhttpd.kit
The Tcl Web Server, TclHttpd, has its source tree organized so you can run the server without any installation steps. This makes it very easy
to put into a Starkit. For our first version, which we will refine later, all we need is a copy of the TclHttpd source code and a copy of the
Standard Tcl Library, tcllib. I used the tcllib1.3 directory that was installed in the main lib directory of my desktop Tcl environment, and the
tclhttpd3.4.3 source distribution. Example 22-7 shows the contents of the tclhttpd.vfs directory:
Example 22-7 The contents of the tclhttpd.vfs directory, version 1
main.tcl
tclhttpd3.4.3/bin/httpd.tcl
tclhttpd3.4.3/bin/httpdthread.tcl
tclhttpd3.4.3/bin/tclhttpd.rc
tclhttpd3.4.3/lib/ (lots of files)
tclhttpd3.4.3/htdocs/ (lots of files)
tcllib1.3 (copy of /usr/local/lib/tclib1.3)
Example 22-8 shows the short main.tcl script used to start up the Starkit. The first two lines are common to all Starkits. Thestarkit::autoextend
command is used to add the tcllib1.3 directory to the auto_path so the Standard Tcl Library packages are available. The last line uses
starkit::topdir to find the TclHttpd startup script, bin/httpd.tcl.
Example 22-8 The main program for the TclHttpd Starkit, version 1
package require starkit
starkit::startup
starkit::autoextend [file join $starkit::topdir tcllib1.3]
source [file join $starkit::topdir tclhttpd3.4.3/bin/httpd.tcl]
The Starkit is created and used as shown below, assuming tclhttpd.vfs is in the current directory. Note that command line options are passed
through, so you can also use this Starkit to host an htdocs directory outside the Starkit. If you don't specify one, thehtdocs tree inside the
Starkit is used:
sdx wrap tclhttpd.kit
tclkit tclhttpd.kit -port 8080 -docRoot /my/htdocs
The standard structure introduced in Example 22-2 organizes packages under a lib directory. By convention, the version numbers are dropped
from the package directory names. Because everything is self contained, there really isn't any need to have explicit version numbers in the
directory names. The file system for the second version of tclhttpd.kit is shown in Example 22-9.
Example 22-9 Contents of the tclhttpd.vfs directory, version 2
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
main.tcl
bin/httpd.tcl
bin/httpdthread.tcl
bin/tclhttpd.rc
lib/tclhttpd/pkgIndex.tcl
lib/tclhttpd/*.tcl (lots of files)
lib/tcllib/pkgIndex.tcl
lib/tcllib/* (lots of subdirectories)
The main.tcl file is shown in Example 22-10. There is no need to adjust the auto_path because starkit::startup ensures that the lib directory is
on it.
Example 22-10 The main program for the TclHttpd Starkit, version 2
package require starkit
starkit::startup
source [file join $starkit::topdir bin/httpd.tcl]
One of the first things I noticed about the tclhttpd.vfs was that tcllib took up far more space than the rest of TclHttpd. TclHttpd only uses a few
of the many modules in tcllib. I ended up only adding the modules I needed in order to keep the Starkit smaller. Another way to solve this
problem is to use the tcllib.kit Starkit that can be shared among applications. Creating shared Starkits is the topic of the next section.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Creating a Shared Starkit
Starkits can be used to create modules that are shared by other applications. For example, the kitten.kit Starkit contains about 50 popular
extensions, and several of them are binary extensions. It is over 4 MB in size, and so it is a great candidate for sharing. You can find kitten.kit
on the CD-ROM or in the Starkit archive. By organizing each shared module into a Starkit with the appropriate structure, it is a simple matter
to share them.
Whenever a Starkit is sourced, Tclkit mounts its VFS and looks for itsmain.tcl file. This is true for shared Starkits as well as the main Starkit of
an application. If main.tcl calls starkit::startup, then the lib directory in the VFS is automatically added to theauto_path. Any libraries organized
under lib will be automatically accessible to the application that sourced the Starkit.
You can add a little logic to make your package behave differently if it is run as the main Starkit or sourced into another application. For
example, this is done in the tcllib Starkit, which starts a stand-alone Wiki that describes the Standard Tcl Library APIs if run as its own Starkit.
Otherwise it just sets up tcllib to be shared by the main application.Example 22-11 shows the main.tcl of tcllib.kit. It has to explicitly add the
tcllib directory to the auto_path because it has both alib and tcllib directory in its VFS:
Example 22-11 The Standard Tcl Library Starkit main.tcl file
package require starkit
if {[starkit::startup] eq "starkit"} {
# Do application startup
package require app-tcllib
} else {
# Set up to be used as a library
set vfsroot [file dirname [file normalize [info script]]]
lappend auto_path [file join $vfsroot tcllib]
}
Another side effect of starkit::startup is to set starkit::topdir. However, this variable is only set once. If you source other Starkits that call
starkit::startup, then the starkit::topdir value is not disturbed.
This behavior changed in Tclkit 8.4.2. In earlier versions, starkit::topdir was set by each Starkit, so you had to worry about saving its value if
you loaded other Starkits. If you source tcllib.kit and cannot package require its packages, check its main.tcl. If it uses starkit::topdir in the
non-Starkit case, then it is an older version. Simply unwrap it, make its main.tcl look like Example 22-11, and wrap it back up to fix the
problem.
The starkit::startup procedure determines the environment of the application by making a series of tests against the script environment. Its
return value helps your main.tcl script distinguish between starting out as the main Starkit, or being loaded into another Starkit as a library.
Table 22-1 lists the return values of the starkit::startup procedure in the order they are checked:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 22-1. Return values of the starkit::startup procedure
starpack
The Starkit was bundled with tclkit to make a Starpack.
starkit
The Starkit was run by itself.
unwrapped
The Starkit was run out of its unpacked vfs directory.
tclhttpd
The Starkit was sourced into TclHttpd.
plugin
The Starkit was sourced in the browser plugin.
service
The Starkit was run in an NT service.
sourced
The Starkit was sourced by another Starkit.
The easiest way to organize your shared Starkits is to put them into the same directory. Example 22-12 shows how the TclHttpd Starkit is
modified to load the tcllib Starkit from the same directory.
Example 22-12 The main program for TclHttpd Starkit, version 3
package require starkit
starkit::startup
set dir [file dirname $starkit::topdir]
if {![file exists [file join $dir tcllib.kit]]} {
puts stderr "Please install tcllib.kit in $dir"
exit 1
}
source [file join $dir tcllib.kit]
source [file join $starkit::topdir tclhttpd/bin/httpd.tcl]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Metakit
This section provides a short overview of the Metakit database that is used by Starkits to store their data. You do not need to program Metakit
directly to use Starkits because of the transparent VFS interface. However, Metakit is an easy-to-use database that provides more power than
storing data in flat files, but not as much power (or overhead) as a full SQL database engine. Metakit has a simple, flexible programming API
and an efficient implementation. By storing your application data in a Metakit table, you can have persistent data that lives with your
application. You can store the data in a file separate from your application, or right inside the application Starkit itself.
This Chapter gives a few introductory examples and explains some of the other features that are available. This Chapter does not provide a
complete reference. The following URLs are excellent guides to the Tcl interface for Metakit. The first URL is also on the CD as
sdarchive/doc/mk4dok.kit.
http://www.equi4.com/metakit/tcl.html
http://www.equi4.com/metakit/wiki.cgi/mk4tcl
http://www.markroseman.com/tcl/mktcl.html
Metakit Data Model
The Metakit data model is table-oriented. A view is like a table with rows of values. Each row in a view has anindex, which is an integer that
counts from 0. The elements (i.e., columns or fields) of a row are called properties. A property might itself be a view, which leads to nested
views (i.e., nested tables). All the rows in a view have the same properties, and the properties of a view can be changed dynamically. You
can directly relate (view, row, property) to (table, row, field) when thinking about Metakit views.
A Metakit data file has one or more views within it. When you open a Metakit file, you specify a tag. Views are specified astag.view. Row N of
a view is specified as tag.view!N. Such a position within a view is called acursor, and there are operations to create cursor variables and move
them through a view. If a property is a nested view, then you can specify a row in the nested view with tag.view!N.subview!M.
Examining a Metakit Database
Our first exercise is to open up a Starkit and look at the Metakit database views inside. The mk::file command implements several operations.
The open operation opens a database and associates it with a tag. Theviews operation lists the views in the database identified by the tag.
The close operation commits any outstanding modifications to the database. The othermk::file operations are used to control the commit
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
behavior and to save or restore the database to an external file. Example 22-13 illustrates how to open a Metakit database and examine the
views it contains:
Example 22-13 Examining the views in a Metakit database
package require Mk4tcl
=> 2.4.8
mk::file open tclhttpd tclhttpd.kit
=> tclhttpd
mk::file views tclhttpd
=> dirs
The mk::view command has several operations to inspect and manipulate views. Thelayout operation queries or sets the properties of a view.
Given only a view, the layout operation returns the properties defined for the view. Each property has a type, and nested views are
represented as a nested list of the property name and its list of properties. Given a set of properties, the layout operation defines new
properties for a view. This may involve adding or deleting properties from any existing rows in the table. Example 22-14 shows the layout of
the dirs view in a Starkit. The files property is a nested view, which provides a natural way to represent a hierarchical filesystem. The example
gets the name property of tclhttpd.dirs!0.files!0, which is the first file in the first directory in the view:
Example 22-14 Examining data in a Metakit view
mk::view layout tclhttpd.dirs
=> name parent:I {files {name size:I date:I contents:B}}
mk::view size tclhttpd.dirs
=> 48
mk::get tclhttpd.dirs!0
=> name <root> parent -1
mk::get tclhttpd.dirs!1
=> name tcllib1.3 parent 0
mk::get tclhttpd.dirs!1 name
=> tcllib1.3
mk::get tclhttpd.dirs!0.files!0 name
=> main.tcl
Of course, real applications will want to query views for values that have certain properties. The mk::select command returns the row
numbers for rows that match given criteria, or all the row numbers if no matching criteria are given. You can match on multiple properties, and
there are flags that control how the match is done. For example, you can do numeric comparisons, regular expression or glob matches, and
min/max comparisons.
Example 22-15 shows two forms of mk::select. The KitWalk procedure enumerates the files in a given directory, which is the view
$tag.dirs!$dir.files. Then it queries the row indices for the$tag.dirs view whose parent property equals $dir, and calls itself recursively to
process the child directories. KitWalk provides a similar function to sdx lsk:
Example 22-15 Selecting data with mk::select
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
proc KitWalk {tag dir {indent 0}} {
set prefix [string repeat " " $indent]
puts "$prefix[mk::get $tag.dirs!$dir name]/"
incr indent 2
# List the plain files in the directory, if any
foreach j [mk::select $tag.dirs!$dir.files] {
puts "$prefix [mk::get $tag.dirs!$dir.files!$j name]"
}
# Recursively process directories where $dir is the parent
foreach i [mk::select $tag.dirs parent $dir] {
KitWalk $tag $i $indent
}
}
proc KitInit {starkit} {
mk::file open starkit $starkit
if {[mk::file views starkit] != "dirs"} {
mk::file close $starkit
error "This database is not a starkit"
}
return starkit
;# db tag
}
proc KitTest {} {
set tag [KitInit tclhttpd.kit]
KitWalk $tag 0
}
Creating a Metakit View
Creating a new view is simple. Example 22-16 opens a database file mydb.tkd and creates a viewtest with three properties: name, blob, and i.
If the file does not exist, then it gets created automatically. If the test view doesn't exist, it gets created. If it already exists, it is reformatted to
have the new properties. The name property has the default type, which is a null-terminated string. Theblob property is a binary value (B)
which can store anything, including null characters. The i property is a 32-bit integer (I). Other types include 64-bit integer (L), 32-bit floating
point (F), 64-bit double-precision floating point (D), and null-terminated string (S), which is the default and needn't be specified.
Example 22-16 Creating a new view
mk::file open mydb mydb.tkd
=> mydb
mk::view layout mydb.test {name blob:B i:I}
=> mydb.test
mk::file close mydb
The mk::set command sets property values, and themk::row command modifies rows. Example 22-17 adds a few values to thetest view. Note
that you can insert into rows beyond the end of the view and it is automatically extended. If you only define some properties for a row, the
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
other properties get default values. Other mk::row operations include insert, replace, and delete.
Example 22-17 Adding data to a view
mk::set mydb.test!0 name hello
=> mydb.test!0
mk::get mydb.test!0
=> hello {} 0
mk::row append mydb.test "line two" 0x0 65
=> mydb.test!1
mk::view size mydb.test
=> 2
mk::set mydb.test!100 i 1234
=> mydb.test!100
mk::view size mydb.test
=> 101
Storing Application Data in a Starkit
Your application can create new views in a Starkit to store persistent data. Remember to wrap your application with the -writable flag. You can
determine the name of the Starkit from $starkit::topdir, and then define a new view within it. Of course, remember that Starkits usedirs view to
store files, but you can create any number of other views within your Starkits. This is illustrated in Example 22-18, which records each time
the application was run in a simple audit view.
Example 22-18 is careful to find the existing Metakit handle that is already opened by Tclkit. Thevfs::filesystem info command returns an
alternating list of VFS names and their Metakit database handle. The example extracts the handle and saves it in the $db variable. This is
important because opening the same Metakit file twice (for writing) can cause corruption:
Example 22-18 Storing data in a Starkit
package require starkit
starkit::startup
set db [lindex [vfs::filesystem info [$starkit::topdir]] 1]
mk::view layout $db.audit {action timestamp:I}
mk::row append $db.audit "Run as pid [pid]" [clock seconds]
puts "$argv0 has been run [mk::view size $db.audit] times"
To test this, put this example into the main.tcl of a trivial Starkit. When you create the Starkit, remember the-writable option with sdx:
mkdir bundle.vfs
cp 22_18.tcl bundle.vfs/main.tcl
sdx wrap bundle.kit -writable-
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Wikit and the Tcler's Wiki
The alternative to storing data in the Starkit file is to have a separate Metakit data file. This is the approach taken by Wikit. The wikit.kit file is
the Wikit application, and the wikit.tkd file is a Metakit database file that stores all the pages in the Wiki. (Creating a new Wiki is simple, just
specify a different .tkd file name.) The advantage of having a separate Metakit file is that you can easily maintain your application by
unwrapping and wrapping your application Starkit. Otherwise, if you put the application data directly into the Starkit you have to extract it and
restore it as an additional maintenance step. In that case, you must use the mk::file save and load operations to save and restore your Metakit
views to a file.
A Wiki is a web site that users can easily edit using a simplified markup syntax. Wikit is a Wiki implementation in Tcl using Metakit to store
pages. It can run as a stand-alone Tk application, a GGI script, as its own little web server, or embedded into another application as a
documentation bundle. There is a copy of wikit.tkd on the CD-ROM. For example, you run a stand alone copy of the Tcler's Wiki as:
tclkit wikit.kit wikit.tkd
[*]
The live Wiki is at wiki.tcl.tk , and you can find out more about Wikit at:
[*]
http://wiki.tcl.tk is an alias for http://mini.net/tcl.
http://wiki.tcl.tk/wikit
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
More Ideas
This Chapter has provided a brief introduction to Tclkit, Starkits, and Metakit. This should be enough to help you get started creating your own
Starkits and using Metakit for persistent storage. You should consult the documentation on the Web for more detailed reference material.
Document Bundles
The Starkit archive includes a number of documentation bundles. For example, mk4dok.kit is a Starkit that contains all the MetaKit
documentation. These document bundles are all based on Wikit. It is very easy to create Wiki-style documentation for your application and
then bundle it up as a Metakit file. You can load wikit.kit and your .tkd document bundle into your application and use the "local" Wikit interface
to display your documentation. For example, the critcl Starkit displays its help with this simple command:
Wikit::init [file join $::starkit::topdir doc critcl.tkd]
Self-Updating Applications
The client in a client-server application is an ideal candidate for a self-updating application. The front-end client is a Starkit with some simple
startup logic that connects to a server via HTTP and displays a pretty splash screen. The server, which is often based on TclHttpd, delivers
code updates to the client. The client caches the code in the VFS inside the Starkit. The application is maintained on the server, and clients
automatically get updated as they are used.
This scenario has the same deployment advantage as browser-based applications: you deploy a "thin-client" to desktops that rarely, if ever,
changes and you update the application code on the server. In addition, this application structure lets you create a nice client front-end that
uses Tcl/Tk instead of HTML, yet still have the benefit of an easy to manage server-side installation of the application code. This design
pattern is being used for a number of large-scale commercial application deployments with considerable success.
A similar system is used with the Starkit archive. If you do:
sdx update tclhttpd.kit
The sdx application contacts the web server running the archive and checks for any updates available for the Starkit. Only the differences are
transmitted, so updates are quick, and they are automatically applied to your copy of the Starkit. This should work for all the Starkits in the
snapshot of the archive on the CD-ROM.
Simple Installers
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
In some cases you simply must install a collection of files as part of your application. It is very easy to include those files in the VFS, and then
extract them into the local file system the first time your application runs. Or, you can create a traditional "installer" that unpacks the entire
application from the Starkit (or Starpack).
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Part III: Tk Basics
Part III introduces Tk, the toolkit for building graphical user interfaces. The Tcl command interface to Tk makes it quick
and easy to build powerful user interfaces. Tk is portable and your user interface code can work unchanged on UNIX,
Windows, and the Macintosh.
Chapter 23 describes the basic concepts of Tk and provides an overview of its facilities.
Chapter 24 illustrates Tk with three example programs including a browser for the examples from this book. These
examples use facilities that are described in more detail in later chapters.
Geometry managers implement the layout of a user interface. Chapters Chapter 25, Chapter 26, and Chapter 27
describe the pack, grid, and place geometry managers. The packer and gridder are general-purpose managers that use
constraints to create flexible layouts with a small amount of code. The placer is a special purpose geometry manager
that can be used for special effects. Chapter 28 describes the panedwindow widget, which is also a geometry manager.
Chapter 29 describes event bindings that associate Tcl commands with events like keystrokes and mouse motion.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Chapter 23. Tk Fundamentals
This chapter introduces the basic concepts used in the Tk graphical user interface toolkit. Tk adds about 45 Tcl commands that let you create
and manipulate widgets in a graphical user interface. Tk works with the X window system, Windows, and Macintosh. The same script can run
unchanged on all of these major platforms.
Tk is a toolkit for programming graphical user interfaces. It was designed for the X window system used on UNIX systems, and it was ported
later to the Macintosh and Windows environments. Tk shares many concepts with other windowing toolkits, but you do not need to know
much about graphical user interfaces to get started with Tk.
Tk provides a set of Tcl commands that create and manipulate widgets. A widget is a window in a graphical user interface that has a
particular appearance and behavior. The terms widget and window are often used interchangeably. Widget types include buttons, scrollbars,
menus, and text windows. Tk also has a general-purpose drawing widget called a canvas that lets you create lighter-weight items such as
lines, boxes, and bitmaps. The canvas is extremely powerful, yet very easy to use. The Tcl commands added by Tk are summarized at the
end of this chapter.
Tk widgets are organized in a hierarchy. To an application, the window hierarchy means that there is a primary window, and inside that
window there can be a number of children windows. The children windows can contain more windows, and so on. Just as a hierarchical file
system has directories (i.e., folders) that are containers for files and directories, a hierarchical window system uses windows as containers for
other windows. The hierarchy affects the naming scheme used for Tk widgets as described later, and it is used to help arrange widgets on the
screen.
Widgets are under the control of a geometry manager that controls their size and location on the screen. Until a geometry manager learns
about a widget, it will not be mapped onto the screen and you will not see it. Tk has powerful geometry managers that make it very easy to
create nice screen layouts. The main trick with any geometry manager is that you use frame widgets as containers for other widgets. One or
more widgets are created and then arranged in a frame by a geometry manager. By putting frames within frames you can create complex
layouts. There are three different geometry managers you can use in Tk: grid, pack, and place, and one widget, the panedwindow, that also
acts as a geometry manager. The Tk geometry managers are discussed in detail in Chapters 25, 26, and 27; the panedwindow is discussed in
Chapter 28.
A Tk-based application has an event-driven control flow, like most window system toolkits. The Tk widgets handle most events automatically,
so programming your application remains simple. For specialized behaviors, you use the bind command to register a Tcl command that runs
when an event occurs. There are lots of events, including mouse motion, keystrokes, window resize, and window destruction. You can also
define virtual events, like Cut and Paste, that are caused by different events on different platforms. Bindings are discussed in detail in
Chapter
29. Chapter 16 describes I/O events and the Tcl event loop, whileChapter 50 describes C programming and the event loop.
Event bindings are grouped into classes, which are called bindtags. The bindtags command associates a widget with an ordered set of
bindtags. The level of indirection between the event bindings and the widgets creates a flexible and powerful system for managing events.
You can create your own bindtags and dynamically change the bindtags for a widget to support mode changes in your application.
A concept related to binding is focus. At any given time, one of the widgets has the input focus, and keyboard events are directed to it. There
are two general approaches to focusing: give focus to the widget under the mouse, or explicitly set the focus to a particular widget. Tk
provides commands to change focus so you can implement either style of focus management. To support modal dialog boxes, you can
forcibly grab the focus away from other widgets.Chapter 39 describes focus, grabs, and dialogs.
The basic structure of a Tk script begins by creating widgets and arranging them with a geometry manager, and then binding actions to the
widgets. After the interpreter processes the commands that initialize the user interface, the event loop is entered and your application begins
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
running.
If you use wish interactively, it creates and displays an empty main window and gives you a command-line prompt. With this interface, your
keyboard commands are handled by the event loop, so you can build your Tk interface gradually. As we will see, you will be able to change
virtually all aspects of your application interactively.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Hello, World! in Tk
Our first Tk script is very simple. It creates a button that prints "Hello, World!" to standard output when you press it. Above the button widget is
a title bar that is provided by the window manager, which in this case is twm under X windows:
Example 23-1 "Hello, World!" Tk program
#!/usr/local/bin/wish
button .hello -text Hello \
-command {puts stdout "Hello, World!"}
pack .hello -padx 20 -pady 10
The first line identifies the interpreter for the script:
#!/usr/local/bin/wish
This special line is necessary if the script is in a file that will be used like other UNIX command files. Chapter 2 describes how to set up scripts
on different platforms.
There are two Tcl commands in the script: one to create the button, and one to make it visible on the display. The button command creates an
instance of a button:
button .hello -text Hello \
-command {puts stdout "Hello, World!"}
=> .hello
The name of the button is .hello. The label on the button is Hello, and the command associated with the button is:
puts stdout "Hello, World!"
The pack command maps the button onto the screen. Some padding parameters are supplied, so there is space around the button:
pack .hello -padx 20 -pady 10
If you type these two commands into wish, you will not see anything happen when thebutton command is given. After the pack command,
though, you will see the empty main window shrink to be just big enough to contain the button and its padding. The behavior of the packer will
be discussed further in Chapters 24 and 25.
Tk uses an object-based system for creating and naming widgets. Associated with each class of widget (e.g., Button) is a command that
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
creates instances of that class of widget. As the widget is created, a new Tcl command is defined that operates on that instance of the widget.
Example 23-1 creates a button named.hello, and we can operate on the button using its name as a Tcl command. For example, we can cause
the button to highlight a few times:
.hello flash
Or we can run the command associated with the button:
.hello invoke
=> Hello, World!
Tk has widget classes and instances, but it is not fully object oriented. It is not possible to subclass a widget class and use inheritance.
Instead, Tk provides very flexible widgets that can be configured in many different ways to tune their appearance. The resource database can
store configuration information that is shared by many widgets, and new classes can be introduced to group resources. Widget behavior is
shared by using binding tags that group bindings. Instead of building class hierarchies, Tk uses composition to assemble widgets with shared
behavior and attributes.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Naming Tk Widgets
The period in the name of the button instance, .hello, is required. Tk uses a naming system for the widgets that reflects their position in a
hierarchy of widgets. The root of the hierarchy is the main window of the application, and its name is simply a dot (i.e., .). This is similar to the
naming convention for directories in UNIX where the root directory is named /, and then / is used to separate components of a file name. Tk
uses a dot in the same way. Each widget that is a child of the main window is named something like .foo. A child widget of .foo would be
.foo.bar, and so on. Just as file systems have directories that are containers for files and other directories, the Tk window hierarchy uses
frame widgets that are containers for widgets and other frames.
Each component of a Tk pathname must start with a lowercase letter or a number. Obviously, a component cannot include a period, either.
The lower case restriction avoids a conflict with resource class names that begin with an upper case letter. A resource name can include Tk
pathname components and Tk widget classes, and case is used to distinguish them. Chapter 31 describes resources in detail.
Store widget names in variables.
There is one drawback to the Tk widget naming system. If your interface changes enough it can result in some widgets changing their
position in the widget hierarchy. In that case they may need to change their name. You can insulate yourself from this programming nuisance
by using variables to hold the names of important widgets. Use a variable reference instead of widget pathnames in case you need to change
things, or if you want to reuse your code in a different interface. The widget creating commands return the name of the widget:
set b [button .hello -text "Hello" -command {puts "Hello!"}]
You use $b as a command to operate on the button:
$b configure -background green
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Configuring Tk Widgets
Example 23-1 illustrates a style of named parameter passing that is prevalent in the Tk commands. Pairs of arguments specify the attributes
of a widget. The attribute names begin with -, such as -text, and the next argument is the value of that attribute. Even the simplest Tk widget
can have a dozen or more attributes that can be specified this way, and complex widgets can have 30 or more attributes. However, the
beauty of Tk is that you need to specify only the attributes for which the default value is not good enough. This is illustrated by the simplicity of
the Hello, World example.
Finally, each widget instance supports a configure operation, which can be abbreviated to config, that can query and change these attributes.
The syntax for config uses the same named argument pairs used when you create the widget. For example, we can change the background
color of the button to red even after it has been created and mapped onto the screen:
.hello config -background red
Widget attributes can be redefined any time, even the text and command that were set when the button was created. The following command
changes .hello into a goodbye button:
.hello config -text Goodbye! -command exit
Widgets have a cget operation to query the current value of an attribute:
.hello cget -background
=> red
You can find out more details about a widget attribute by using configure without a value:
.hello config -background
=> -background background Background #ffe4c4 red
The returned information includes the command-line switch, the resource name, the class name, the default value, and the current value,
which is last. The class and resource name have to do with the resource mechanism described in Chapter 31. If you only specify configure
and no attribute, then a list of the configuration information for all widget attributes is returned. Example 23-2 uses this to print out all the
information about a widget:
Example 23-2 Looking at all widget attributes
proc Widget_Attributes {w {out stdout}} {
puts $out [format "%-20s %-10s %s" Attribute Default Value]
foreach item [$w configure] {
puts $out [format "%-20s %-10s %s" \
[lindex $item 0] [lindex $item 3] \
[lindex $item 4]]
}
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Tk Widget Attributes and the Resource Database
A widget attribute can be named three different ways: by its command-line option, by its resource name, and by its resource class. The
command-line option is the format you use in Tcl scripts. This form is always all lowercase and prefixed with a hyphen (e.g., -offvalue). The
resource name for the attribute has no leading hyphen, and it has uppercase letters at internal word boundaries (e.g., offValue). The resource
class begins with an uppercase letter and has uppercase letters at internal word boundaries. (e.g., OffValue).
The tables in this book list widget attributes by their resource name.
You need to know these naming conventions if you specify widget attributes via the resource mechanism. The command-line option can be
derived from the resource name by mapping it to all lowercase. The primary advantage of using resources to specify attributes is that you do
not have to litter your code with attribute specifications. With just a few resource database entries you can specify attributes for all your
widgets. In addition, if attributes are specified with resources, users can provide alternate resource specifications in order to override the
values supplied by the application. For attributes like colors and fonts, this feature can be important to users. Resource specifications are
described in detail in Chapter 31.
The Tk Manual Pages
This book provides summaries for all the Tk commands, the widget attributes, and the default bindings. However, for the absolute truth, you
may need to read the on-line manual pages that come with Tk. They provide a complete reference source for the Tk commands. You should
be able to use the UNIX man program to read them:
% man button
The tkman program provides a very nice graphical user interface to the UNIX manual pages. On the Macintosh platform, the manual pages
are formatted into HTML documents that you can find in the HTML Docs folder of the Tcl/Tk distribution. On Windows, the manual pages are
formatted into Help documents. You can find the manual pages on the web at:
http://www.tcl.tk/man/
There are a large number of attributes that are common across most of the Tk widgets. These are described in a separate man page under
the name options. Each man page begins with aSTANDARD OPTIONS section that lists which of these standard attributes apply, but you
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
have to look at the options man page for the description. In contrast, the tables in this book always list all widget attributes.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Summary of the Tk Commands
The following tables list the Tcl commands added by Tk. The page number in the table is the primary reference for the command, and there
are other references in the index.
Widget Commands
Table 23-1 lists commands that create widgets. There are 18 different widgets in Tk, although 4 of them are variations on a button, and 5 are
devoted to different flavors of text display.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 23-1. Tk widget-creation commands
Command
Pg.
Description
button
454
Create a command button.
canvas
557
Create a canvas, which supports lines, boxes, bitmaps, images, arcs, text, polygons, and embedded widgets.
checkbutton
458
Create a toggle button that is linked to a Tcl variable.
entry
507
Create a one-line text entry widget.
frame
485
Create a container widget used with geometry managers.
label
490
Create a read-only, multiline text label.
labelframe
485
Create a container widget used with geometry managers that has extra label attributes. (Tk 8.4)
listbox
519
Create a line-oriented, scrolling text widget.
menu
462
Create a menu.
menubutton
462
Create a button that posts a menu.
message
493
Create a read-only, multiline text message.
panedwindow
429
Create a container widget that controls other widgets in a paned fashion. (Tk 8.4)
radiobutton
458
Create one of a set of radio buttons linked to one variable.
scale
495
Create a scale widget that adjusts the value of a variable.
scrollbar
499
Create a scrollbar that can be linked to another widget.
spinbox
511
Create a spinbox widget that is a composite entry widget with button controls for adjusting the value. (Tk 8.4)
text
531
Create a general-purpose, editable text widget.
toplevel
485
Create a frame that is a new top level window.
Widget Manipulation Commands
Table 23-2 lists commands that manipulate widgets and provide associated functions like input focus, event binding, and geometry
management.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 23-2. Tk widget-manipulation commands
Command
Pg.
Description
bell
497
Ring the terminal bell device.
bind
435
Bind a Tcl command to an event.
bindtags
437
Create binding classes and control binding inheritance.
clipboard
594
Manipulate the clipboard.
destroy
605
Delete a widget.
event
446
Define and generate virtual events.
focus
603
Control the input focus.
font
641
Set and query font attributes and measurements.
grab
604
Steal the input focus from other widgets.
grid
419
Arrange widgets into a grid with constraints.
image
626
Create and manipulate images.
lower
409
Lower a window in the stacking order.
option
477
Set and query the resources database.
pack
409
Pack a widget in the display with constraints.
place
427
Place a widget in the display with positions.
raise
409
Raise a window in the stacking order.
selection
593
Manipulate the selection.
send
648
Send a Tcl command to another Tk application.
tk
669
Query or set the application name or global caret.
tkerror
202
Handler for background errors.
tkwait
605
Wait for an event.
update
608
Update the display by going through the event loop.
winfo
663
Query window state.
wm
657
Interact with the window manager.
Support Procedures
Table 23-3 lists several support procedures that implement standard dialogs, option menus, and other facilities.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 23-3. Tk support procedures
Command
Pg.
Description
tk_bisque
621
Install bisque family of colors.
tk_chooseColor
602
Dialog to select a color. (Tk 4.2)
tk_chooseDirectory
600
Dialog to select a directory. (Tk 8.2)
tk_dialog
599
Create simple dialogs.
tk_focusFollowsMouse
603
Install mouse-tracking focus model.
tk_focusNext
604
Focus on next widget in tab order.
tk_focusPrev
604
Focus on previous widget in tab order.
tk_getOpenFile
600
Dialog to open an existing file. (Tk 4.2)
tk_getSaveFile
600
Dialog to open a new file. (Tk 4.2)
tk_messageBox
600
Message dialog. (Tk 4.2)
tk_optionMenu
465
Create an option menu.
tk_popup
465
Create a pop-up menu.
tk_setPalette
621
Set the standard color palette. (Tk 4.2)
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Other Widget Sets
This book describes the set of widgets provided by core Tk distribution. There are number of other widget sets for Tk. Some are implemented
as Tcl procedures that compose the basic widgets into useful combinations (e.g., BWidgets). Others are C-based toolkits (e.g., Tix and BLT).
A few of the more popular widget sets are listed here:
BLT
George Howlett created BLT. It includes a great graph widget that efficiently supports large datasets. It also includes a tabbed notebook and
tree view widget. Its busy widget covers your application with a transparent widget that just displays a watch cursor, which is handy when the
application is busy doing something and you don't want to accept mouse clicks. This is a C-based toolkit.
http://www.sourceforge.net/projects/blt/
Tix
Tix was created by Ioi Lam, and is now supported by a team of volunteers. It includes several widgets and an infrastructure for creating new
widgets in Tcl. Notable features include balloon help, tabbed windows, paned window, and a hierarchy browser. This is a C-based toolkit,
although it includes a number of compound widgets created in Tcl.
http://tix.sourceforge.net/
[incr Tk]
and [incr Widgets]
[incr Tk] is a C-based framework for creating compound widgets using the [incr Tcl] object system. [incr Widgets] is the widget set created
using that framework. It includes loads of widgets, from simple labeled-entry widgets up through HTML display widgets. These tools are
described in Chad Smith's book, [incr Tcl] from the Ground Up (Osborne-McGraw Hill, 1999).
http://incrtcl.sourceforge.net
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
BWidgets
BWidgets is a set of Tcl-based widgets. It includes a variety of compound widgets, including a tabbed notebook, combobox, and hierarchy
browser. It is hosted at the Standard Tcl Lib (tcllib) web site:
http://www.sourceforge.net/projects/tcllib
TkTable
TkTable is combination of a gridding geometry manager and several text-oriented widgets. It makes it easy to lay out tabular data like
spreadsheets, and it also provides a large amount of control over the formatting of cells and their data.
http://www.sourceforge.net/projects/tktable
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 24. Tk by Example
This chapter introduces Tk through a series of short examples. The ExecLog runs a program in the background and displays its output. The
Example Browser displays the Tcl examples from the book. The Tcl Shell lets you type Tcl commands and execute them in a slave
interpreter.
Tk provides a quick and fun way to generate user interfaces. In this chapter we will go through a series of short example programs to give you
a feel for what you can do. Some details are glossed over in this chapter and considered in more detail later. In particular, the pack geometry
manager is covered in Chapter 25 and event bindings are discussed inChapter 29. The Tk widgets are discussed in more detail in later
chapters.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
ExecLog
Our first example provides a simple user interface to running another program with the exec command. The interface consists of two buttons,
Run it and Quit, an entry widget in which to enter a command, and a text widget in which to log the results of running the program. The script
runs the program in a pipeline and uses the fileevent command to wait for output. This structure lets the user interface remain responsive
while the program executes. You could use this to run make, for example, and it would save the results in the log. The complete example is
given first, and then its commands are discussed in more detail.
Example 24-1 Logging the output of a program run with exec
#!/usr/local/bin/wish
# execlog - run a program with exec and log the output
# Set window title
wm title . ExecLog
# Create a frame for buttons and entry.
frame .top -borderwidth 10
pack .top -side top -fill x
# Create the command buttons.
button .top.quit -text Quit -command exit
set but [button .top.run -text "Run it" -command Run]
pack .top.quit .top.run -side right
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
# Create a labeled entry for the command
label .top.l -text Command: -padx 0
entry .top.cmd -width 20 -relief sunken \
-textvariable command
pack .top.l -side left
pack .top.cmd -side left -fill x -expand true
# Set up key binding equivalents to the buttons
bind .top.cmd <Return> Run
bind .top.cmd <Control-c> Stop
focus .top.cmd
# Create a text widget to log the output
frame .t
set log [text .t.log -width 80 -height 10 \
-borderwidth 2 -relief raised -setgrid true \
-yscrollcommand {.t.scroll set}]
scrollbar .t.scroll -command {.t.log yview}
pack .t.scroll -side right -fill y
pack .t.log -side left -fill both -expand true
pack .t -side top -fill both -expand true
# Run the program and arrange to read its input
proc Run {} {
global command input log but
if [catch {open "|$command |& cat"} input] {
$log insert end $input\n
} else {
fileevent $input readable Log
$log insert end $command\n
$but config -text Stop -command Stop
}
}
# Read and log output from the program
proc Log {} {
global input log
if [eof $input] {
Stop
} else {
gets $input line
$log insert end $line\n
$log see end
}
}
# Stop the program and fix up the button
proc Stop {} {
global input but
catch {close $input}
$but config -text "Run it" -command Run
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
}
Window Title
The first command sets the title that appears in the title bar implemented by the window manager. Recall that dot (i.e., .) is the name of the
main window:
wm title . ExecLog
The wm command communicates with the window manager. The window manager is the program that lets you open, close, and resize
windows. It implements the title bar for the window and probably some small buttons to close or resize the window. Different window
managers have a distinctive look; the figure shows a title bar from twm, a window manager for X.
A Frame for Buttons
A frame is created to hold the widgets that appear along the top of the interface. The frame has a border to provide some space around the
widgets:
frame .top -borderwidth 10
The frame is positioned in the main window. The default packing side is the top, so -side top is redundant here, but it is used for clarity. The
-fill x packing option makes the frame fill out to the whole width of the main window:
pack .top -side top -fill x
Command Buttons
Two buttons are created: one to run the command, the other to quit the program. Their names, .top.quit and .top.run, imply that they are
children of the .top frame. This affects the pack command, which positions widgets inside their parent by default:
button .top.quit -text Quit -command exit
set but [button .top.run -text "Run it" \
-command Run]
pack .top.quit .top.run -side right
A Label and an Entry
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
The label and entry are also created as children of the .top frame. The label is created with no padding in the X direction so that it can be
positioned right next to the entry. The size of the entry is specified in terms of characters. The relief attribute gives the entry some looks to set
it apart visually on the display. The contents of the entry widget are linked to the Tcl variable command:
label .top.l -text Command: -padx 0
entry .top.cmd -width 20 -relief sunken \
-textvariable command
The label and entry are positioned to the left inside the .top frame. The additional packing parameters to the entry allow it to expand its
packing space and fill up that extra area with its display. The difference between packing space and display space is discussed in Chapter 25
on page 399:
pack .top.l -side left
pack .top.cmd -side left -fill x -expand true
Key Bindings and Focus
Key bindings on the entry widget provide an additional way to invoke the functions of the application. The bind command associates a Tcl
command with an event in a particular widget. The <Return> event is generated when the user presses theReturn key on the keyboard. The
<Control-c> event is generated when the letter c is typed while the Control key is already held down. For the events to go to the entry widget,
.top.cmd, input focus must be given to the widget. By default, an entry widget gets the focus when you click the left mouse button in it. The
explicit focus command is helpful for users with the focus-follows-mouse model. As soon as the mouse is over the main window the user can
type into the entry:
bind .top.cmd <Return> Run
bind .top.cmd <Control-c> Stop
focus .top.cmd
A Resizable Text and Scrollbar
A text widget is created and packed into a frame with a scrollbar. The width and height of the text widget are specified in characters and lines,
respectively. The setgrid attribute of the text widget is turned on. This restricts the resize so that only a whole number of lines and
average-sized characters can be displayed.
The scrollbar is a separate widget in Tk, and it can be connected to different widgets using the same setup as is used here. The text's
yscrollcommand updates the display of the scrollbar when the text widget is modified, and the scrollbar'scommand scrolls the associated
widget when the user manipulates the scrollbar:
frame .t
set log [text .t.log -width 80 -height 10 \
-borderwidth 2 -relief raised -setgrid true\
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
-yscrollcommand {.t.scroll set}]
scrollbar .t.scroll -command {.t.log yview}
pack .t.scroll -side right -fill y
pack .t.log -side left -fill both -expand true
pack .t -side top -fill both -expand true
A side effect of creating a Tk widget is the creation of a new Tcl command that operates on that widget. The name of the Tcl command is the
same as the Tk pathname of the widget. In this script, the text widget command, .t.log, is needed in several places. However, it is a good idea
to put the Tk pathname of an important widget into a variable because that pathname can change if you reorganize your user interface. The
disadvantage of this is that you must declare the variable with global inside procedures. The variable log is used for this purpose in this
example to demonstrate this style.
The Run Procedure
The Run procedure starts the program specified in the command entry. That value is available in the global
command variable because of the
textvariable attribute of the entry. The command is run in a pipeline so that it executes in the background. The leading
| in the argument to open
indicates that a pipeline is being created. The catch command guards against bogus commands. The variableinput is set to an error
message, or to the normal open return that is a file descriptor. The program is started like this:
if [catch {open "|$command |& cat"} input] {
Trapping errors from pipelines.
The pipeline diverts error output from the command through the cat program. If you do not use cat like this, then the error output from the
pipeline, if any, shows up as an error message when the pipeline is closed. In this example it turns out to be awkward to distinguish between
errors generated from the program and errors generated because of the way the Stop procedure is implemented. Furthermore, some
programs interleave output and error output, and you might want to see the error output in order instead of all at the end.
If the pipeline is opened successfully, then a callback is set up using the fileevent command. Whenever the pipeline generates output, then
the script can read data from it. The Log procedure is registered to be called whenever the pipeline is readable:
fileevent $input readable Log
The command (or the error message) is inserted into the log. This is done using the name of the text widget, which is stored in the
log variable,
as a Tcl command. The value of the command is appended to the log, and a newline is added so that its output will appear on the next line.
$log insert end $command\n
The text widget's insert function takes two parameters: amark and a string to insert at that mark. The symbolic markend represents the end of
the contents of the text widget.
The run button is changed into a stop button after the program begins. This avoids a cluttered interface and demonstrates the dynamic nature
of a Tk interface. Again, because this button is used in a few different places in the script, its pathname has been stored in the variable but:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
$but config -text Stop -command Stop
The Log Procedure
The Log procedure is invoked whenever data can be read from the pipeline, and when end of file has been reached. This condition is
checked first, and the Stop procedure is called to clean things up. Otherwise, one line of data is read and inserted into the log. The text
widget's see operation is used to position the view on the text so that the new line is visible to the user:
if [eof $input] {
Stop
} else {
gets $input line
$log insert end $line\n
$log see end
}
The Stop Procedure
The Stop procedure terminates the program by closing the pipeline. Theclose is wrapped up with a catch. This suppresses the errors that can
occur when the pipeline is closed prematurely on the process. Finally, the button is restored to its run state so that the user can run another
command:
catch {close $input}
$but config -text "Run it" -command Run
In most cases, closing the pipeline is adequate to kill the job. On UNIX, this results in a signal, SIGPIPE, being delivered to the program the
next time it does a write to its standard output. There is no built-in way to kill a process, but you can exec the UNIX kill program. The pid
command returns the process IDs from the pipeline:
foreach pid [pid $input] {
catch {exec kill $pid}
}
If you need more sophisticated control over another process, you should check out the expect Tcl extension, which is described in the book
Exploring Expect (Don Libes, O'Reilly & Associates, Inc., 1995).Expect provides powerful control over interactive programs. You can write Tcl
scripts that send input to interactive programs and pattern match on their output. Expect is designed to automate the use of programs that
were designed for interactive use.
Cross-Platform Issues
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
This script will run on UNIX and Windows, but not on Macintosh because there is no exec command. One other problem is the binding for
<Control-c> to cancel the job. This is UNIX-like, while Windows users expect<Escape> to cancel a job, and Macintosh users expect
<Command-period>. Platform_CancelEvent defines a virtual event, <<Cancel>>, and Stop is bound to it:
Example 24-2 A platform-specific cancel event
proc Platform_CancelEvent {} {
global tcl_platform
switch $tcl_platform(platform) {
unix {
event add <<Cancel>> <Control-c>
}
windows {
event add <<Cancel>> <Escape>
}
macintosh {
event add <<Cancel>> <Command-period>
}
}
}
bind .top.entry <<Cancel>> Stop
There are other virtual events already defined by Tk. Theevent command and virtual events are described on page 446.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
The Example Browser
Example 24-3 is a browser for the code examples that appear in this book. The basic idea is to provide a menu that selects the examples, and
a text window to display the examples. Before you can use this sample program, you need to edit it to set the proper location of the exsource
directory that contains all the example sources from the book. Example 24-4 on page 389 extends the browser with a shell that is used to test
the examples.
Example 24-3 A browser for the code examples in the book
#!/usr/local/bin/wish
# Browser for the Tcl and Tk examples in the book.
# browse(dir) is the directory containing all the tcl files
# Please edit to match your system configuration.
switch $tcl_platform(platform) {
"unix" {set browse(dir) /cdrom/tclbook2/exsource}
"windows" {set browse(dir) D:/exsource}
"macintosh" {set browse(dir) /tclbook2/exsource}
}
wm minsize . 30 5
wm title . "Tcl Example Browser"
# Create a row of buttons along the top
set f [frame .menubar]
pack $f -fill x
button $f.quit -text Quit -command exit
button $f.next -text Next -command Next
button $f.prev -text Previous -command Previous
# The Run and Reset buttons use EvalEcho that
# is defined by the Tcl shell in Example 24–4 on page 389
button $f.load -text Run -command Run
button $f.reset -text Reset -command Reset
pack $f.quit $f.reset $f.load $f.next $f.prev -side right
# A label identifies the current example
label $f.label -textvariable browse(current)
pack $f.label -side right -fill x -expand true
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
# Create the menubutton and menu
menubutton $f.ex -text Examples -menu $f.ex.m
pack $f.ex -side left
set m [menu $f.ex.m]
# Create the text to display the example
# Scrolled_Text is defined in Example 33–1 on page 500
set browse(text) [Scrolled_Text .body \
-width 80 -height 10\
-setgrid true]
pack .body -fill both -expand true
# Look through the example files for their ID number.
foreach f [lsort -dictionary [glob [file join $browse(dir) *]]] {
if [catch {open $f} in] {
puts stderr "Cannot open $f: $in"
continue
}
while {[gets $in line] >= 0} {
if [regexp {^# Example ([0-9]+)-([0-9]+)} $line \
x chap ex] {
lappend examples($chap) $ex
lappend browse(list) $f
# Read example title
gets $in line
set title($chap-$ex) [string trim $line "# "]
set file($chap-$ex) $f
close $in
break
}
}
}
# Create two levels of cascaded menus.
# The first level divides up the chapters into chunks.
# The second level has an entry for each example.
option add *Menu.tearOff 0
set limit 8
set c 0; set i 0
foreach chap [lsort -integer [array names examples]] {
if {$i == 0} {
$m add cascade -label "Chapter $chap..." \
-menu $m.$c
set sub1 [menu $m.$c]
incr c
}
set i [expr ($i +1) % $limit]
$sub1 add cascade -label "Chapter $chap" -menu $sub1.sub$i
set sub2 [menu $sub1.sub$i]
foreach ex [lsort -integer $examples($chap)] {
$sub2 add command -label "$chap-$ex $title($chap-$ex)" \
-command [list Browse $file($chap-$ex)]
}
}
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Display a specified file. The label is updated to
# reflect what is displayed, and the text is left
# in a read-only mode after the example is inserted.
proc Browse { file } {
global browse
set browse(current) [file tail $file]
set browse(curix) [lsearch $browse(list) $file]
set t $browse(text)
$t config -state normal
$t delete 1.0 end
if [catch {open $file} in] {
$t insert end $in
} else {
$t insert end [read $in]
close $in
}
$t config -state disabled
}
# Browse the next and previous files in the list
set browse(curix) -1
proc Next {} {
global browse
if {$browse(curix) < [llength $browse(list)] - 1} {
incr browse(curix)
}
Browse [lindex $browse(list) $browse(curix)]
}
proc Previous {} {
global browse
if {$browse(curix) > 0} {
incr browse(curix) -1
}
Browse [lindex $browse(list) $browse(curix)]
}
# Run the example in the shell
proc Run {} {
global browse
EvalEcho [list source \
[file join $browse(dir) $browse(current)]]
}
# Reset the slave in the eval server
proc Reset {} {
EvalEcho reset
}
More about Resizing Windows
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
This example uses the wm minsize command to put a constraint on the minimum size of the window. The arguments specify the minimum
width and height. These values can be interpreted in two ways. By default they are pixel values. However, if an internal widget has enabled
geometry gridding, then the dimensions are in grid units of that widget. In this case the text widget enables gridding with its
setgrid attribute, so
the minimum size of the window is set so that the text window is at least 30 characters wide by five lines high:
wm minsize . 30 5
In older versions of Tk, Tk 3.6, gridding also enabled interactive resizing of the window. Interactive resizing is enabled by default in Tk 4.0 and
later.
Managing Global State
The example uses the browse array to collect its global variables. This makes it simpler to reference the state from inside procedures
because only the array needs to be declared global. As the application grows over time and new features are added, that global command
won't have to be adjusted. This style also serves to emphasize what variables are important. The browse array holds the name of the
example directory (dir), the Tk pathname of the text display (text), and the name of the current file (current). The list and curix elements are
used to implement the Next and Previous procedures.
Searching through Files
The browser searches the file system to determine what it can display. The tcl_platform(platform) variable is used to select a different
example directory on different platforms. You may need to edit the on-line example to match your system. The example uses glob to find all
the files in the exsource directory. The file join command is used to create the file name pattern in a platform-independent way. The result of
glob is sorted explicitly so the menu entries are in the right order. Each file is read one line at a time with
gets, and then regexp is used to scan
for keywords. The loop is repeated here for reference:
foreach f [lsort -dictionary [glob -directory $browse(dir) *]] {
if {[catch {open $f} in]} {
puts stderr "Cannot open $f: $in"
continue
}
while {[gets $in line] >= 0} {
if {[regexp {^# Example ([0-9]+)-([0-9]+)} $line \
x chap ex]} {
lappend examples($chap) $ex
lappend browse(list) $f
# Read example title
gets $in line
set title($chap-$ex) [string trim $line "# "]
set file($chap-$ex) $f
close $in
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
break
}
}
}
The example files contain lines like this:
# Example 1-1
# The Hello, World! program
The regexp picks out the example numbers with the([0-9]+)-([0-9]+) part of the pattern, and these are assigned to thechap and ex variables.
The x variable is assigned the value of the whole match, which is more than we are interested in. Once the example number is found, the next
line is read to get the description of the example. At the end of the foreach loop the examples array has an element defined for each chapter,
and the value of each element is a list of the examples for that chapter.
Cascaded Menus
The values in the examples array are used to build up a cascaded menu structure. First a menubutton is created that will post the main menu.
It is associated with the main menu with its menu attribute. The menu must be a child of the menubutton for its display to work properly:
menubutton $f.ex -text Examples -menu $f.ex.m
set m [menu $f.ex.m]
There are too many chapters to put them all into one menu. The main menu has a cascade entry for each group of eight chapters. Each of
these submenus has a cascade entry for each chapter in the group, and each chapter has a menu of all its examples. Once again, the
submenus are defined as a child of their parent menu. Note the inconsistency between menu entries and buttons. Their text is defined with
the -label option, not -text. Other than this they are much like buttons.Chapter 30 describes menus in more detail. The code is repeated here:
set limit 8 ; set c 0 ; set i 0
foreach key [lsort -integer [array names examples]] {
if {$i == 0} {
$m add cascade -label "Chapter $key..." \
-menu $m.$c
set sub1 [menu $m.$c]
incr c
}
set i [expr {($i +1) % $limit}]
$sub1 add cascade -label "Chapter $key" -menu $sub1.sub$i
set sub2 [menu $sub1.sub$i]
foreach ex [lsort -integer $examples($key)] {
$sub2 add command -label "$key-$ex $title($key-$ex)" \
-command [list Browse $file($key-$ex)]
}
}
A Read-Only Text Widget
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
The Browse procedure is fairly simple. It sets browse(current) to be the name of the file. This changes the main label because of its
textvariable attribute that links it to this variable. Thestate attribute of the text widget is manipulated so that the text is read-only after the text
is inserted. You have to set the state to normal before inserting the text; otherwise, theinsert has no effect. Here are a few commands from the
body of Browse:
global browse
set browse(current) [file tail $file]
$t config -state normal
$t insert end [read $in]
$t config -state disabled
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
A Tcl Shell
This section demonstrates the text widget with a simple Tcl shell application. It uses a text widget to prompt for commands and display their
results. It uses a second Tcl interpreter to evaluate the commands you type. This dual interpreter structure is used by the console built into
the Windows and Macintosh versions of wish. The TkCon application written by Jeff Hobbs is an even more elaborate console that has many
features to support interactive Tcl use:
http://tkcon.sourceforge.net/
Example 24-4 is written to be used with the browser fromExample 24-3 in the same application. The browser'sRun button runs the current
example in the shell. An alternative is to have the shell run as a separate process and use the send command to communicate Tcl commands
between separate applications. That alternative is shown in Example 43-2 on page 651.
Example 24-4 A Tcl shell in a text widget
#!/usr/local/bin/wish
# Simple evaluator. It executes Tcl in a slave interpreter
set t [Scrolled_Text .eval -width 80 -height 10]
pack .eval -fill both -expand true
# Text tags give script output, command errors, command
# results, and the prompt a different appearance
$t tag configure prompt -underline true
$t tag configure result -foreground purple
$t tag configure error -foreground red
$t tag configure output -foreground blue
# Insert the prompt and initialize the limit mark
set eval(prompt) "tcl> "
$t insert insert $eval(prompt) prompt
$t mark set limit insert
$t mark gravity limit left
focus $t
set eval(text) $t
# Key bindings that limit input and eval things. The break in
# the bindings skips the default Text binding for the event.
bind $t <Return> {EvalTypein ; break}
bind $t <BackSpace> {
if {[%W tag nextrange sel 1.0 end] != ""} {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
%W delete sel.first sel.last
} elseif {[%W compare insert > limit]} {
%W delete insert-1c
%W see insert
}
break
}
bind $t <Key> {
if [%W compare insert < limit] {
%W mark set insert end
}
}
# Evaluate everything between limit and end as a Tcl command
proc EvalTypein {} {
global eval
$eval(text) insert insert \n
set command [$eval(text) get limit end]
if [info complete $command] {
$eval(text) mark set limit insert
Eval $command
}
}
# Echo the command and evaluate it
proc EvalEcho {command} {
global eval
$eval(text) mark set insert end
$eval(text) insert insert $command\n
Eval $command
}
# Evaluate a command and display its result
proc Eval {command} {
global eval
$eval(text) mark set insert end
if [catch {$eval(slave) eval $command} result] {
$eval(text) insert insert $result error
} else {
$eval(text) insert insert $result result
}
if {[$eval(text) compare insert != "insert linestart"]} {
$eval(text) insert insert \n
}
$eval(text) insert insert $eval(prompt) prompt
$eval(text) see insert
$eval(text) mark set limit insert
return
}
# Create and initialize the slave interpreter
proc SlaveInit {slave} {
interp create $slave
load {} Tk $slave
interp alias $slave reset {} ResetAlias $slave
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
interp alias $slave puts {} PutsAlias $slave
return $slave
}
# The reset alias deletes the slave and starts a new one
proc ResetAlias {slave} {
interp delete $slave
SlaveInit $slave
}
# The puts alias puts stdout and stderr into the text widget
proc PutsAlias {slave args} {
if {[llength $args] > 3} {
error "invalid arguments"
}
set newline "\n"
if {[string match "-nonewline" [lindex $args 0]]} {
set newline ""
set args [lreplace $args 0 0]
}
if {[llength $args] == 1} {
set chan stdout
set string [lindex $args 0]$newline
} else {
set chan [lindex $args 0]
set string [lindex $args 1]$newline
}
if [regexp (stdout|stderr) $chan] {
global eval
$eval(text) mark gravity limit right
$eval(text) insert limit $string output
$eval(text) see limit
$eval(text) mark gravity limit left
} else {
puts -nonewline $chan $string
}
}
set eval(slave) [SlaveInit shell]
Text Marks, Tags, and Bindings
The shell uses a text mark and some extra bindings to ensure that users only type new text into the end of the text widget. A mark represents
a position in the text that is updated as characters are inserted and deleted. The limit mark keeps track of the boundary between the read-only
area and the editable area. The insert mark is where the cursor is displayed. The end mark is always the end of the text. TheEvalTypein
procedure looks at all the text between limit and end to see if it is a complete Tcl command. If it is, it evaluates the command in the slave
interpreter.
The <Key> binding checks to see where the insert mark is and bounces it to the end if the user tries to input text before thelimit mark. The puts
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
alias sets right gravity on limit, so the mark is pushed along when program output is inserted right atlimit. Otherwise, the left gravity on limit
means that the mark does not move when the user inserts right at limit.
Text tags are used to give different regions of text difference appearances. A tag applies to a range of text. The tags are configured at the
beginning of the script and they are applied when text is inserted.
Chapter 36 describes the text widget in more detail.
Multiple Interpreters
The SlaveInit procedure creates another interpreter to evaluate the commands. This prevents conflicts with the procedures and variables
used to implement the shell. Initially, the slave interpreter only has access to Tcl commands. The load command installs the Tk commands,
and it creates a new top-level window that is "." for the slave interpreter. Chapter 20 describes how you can embed the window of the slave
within other frames.
The shell interpreter is not created with the -safe flag, so it can do anything. For example, if you typeexit, it will exit the whole application. The
SlaveInit procedure installs an alias, reset, that just deletes the slave interpreter and creates a new one. You can use this to clean up after
working in the shell for a while. Chapter 19 describes the interp command in detail.
Native Look and Feel
When you run a Tk script on different platforms, it uses native buttons, menus, and scrollbars. The text and entry widgets are tuned to give
the application the native look and feel. The following screen shots show the combined browser and shell as it looks on Macintosh, Windows,
and UNIX.
Example 24-5. Macintosh look and feel.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Example 24-6. Windows look and feel.
Example 24-7. UNIX look and feel.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 25. The Pack Geometry Manager
This chapter explores the pack geometry manager that positions widgets on the screen.
Geometry managers arrange widgets on the screen. This chapter describes the pack geometry manager, which is a constraint-based system.
The next two chapters describe the grid and place geometry managers. The pack and grid geometry managers are quite general, whileplace is
used for special-purpose applications. This book uses pack a lot because it was the original geometry manager for Tk. Thegrid geometry
manager was added in Tk 4.1.
A geometry manager uses one widget as a parent, and it arranges multiplechildren (also called slaves) inside the parent. The parent is almost
always a frame, but this is not strictly necessary. A widget can only be managed by one geometry manager at a time, but you can use
different managers to control different widgets in your user interface. If a widget is not managed, then it doesn't appear on your display at all.
Don't pack and grid into the same manager widget.
For each individual manager widget — such as a frame, a labelframe, or a toplevel — you have the choice of using either pack or grid to
manage all of its immediate children. Attempting to use both in the same manager results in an endless loop as both geometry managers try
to control the window layout. This restriction applies only to the immediate children of a manager widget; you can use a different geometry
manager for "descendents" that aren't immediate children. For example, you can choose to pack all of the immediate children of the . toplevel.
Then, if one of the children of . is a frame, you can choose to use either pack or grid to manage the children of that frame.
The packer is a powerful constraint-based geometry manager. Instead of specifying in detail the placement of each window, the programmer
defines some constraints about how windows should be positioned, and the packer works out the details. It is important to understand the
algorithm the packer uses; otherwise, the constraint-based results may not be what you expect.
This chapter explores the packer through a series of examples. The background of the main window is set to black, and the other frames are
given different colors so you can identify frames and observe the effect of the different packing parameters. When consecutive examples
differ by a small amount, the added command or option is printed in bold courier to highlight the addition.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Packing toward a Side
The following example creates two frames and packs them toward the top side of the main window. The upper frame, .one, is not as big and
the main window shows through on either side. The children are packed toward the specified side in order, so .one is on top. The four
possible sides are: top, right, bottom, and left. The top side is the default.
Example 25-1 Two frames packed inside the main frame
# Make the main window black
. config -bg black
# Create and pack two frames
frame .one -width 40 -height 40 -bg white
frame .two -width 100 -height 50 -bg grey50
pack .one .two -side top
Shrinking Frames and pack propagate
In the previous example, the main window shrank down to be just large enough to hold its two children. In most cases this is the desired
behavior. If not, you can turn it off with the pack propagate command. Apply this to the parent frame, and it will not adjust its size to fit its
children:
Example 25-2 Turning off geometry propagation
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
frame .one -width 40 -height 40 -bg white
frame .two -width 100 -height 50 -bg grey50
pack propagate . false
pack .one .two -side top
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Horizontal and Vertical Stacking
In general, you use either horizontal or vertical stacking within a frame. If you mix sides such as left and top, the effect might not be what you
expect. Instead, you should introduce more frames to pack a set of widgets into a stack of a different orientation. For example, suppose we
want to put a row of buttons inside the upper frame in the examples we have given so far:
Example 25-3 A horizontal stack inside a vertical stack
frame .one -bg white
frame .two -width 100 -height 50 -bg grey50
# Create a row of buttons
foreach b {alpha beta gamma} {
button .one.$b -text $b
pack .one.$b -side left
}
pack .one .two -side top
Example 25-4 Even more nesting of horizontal and vertical stacks
frame .one -bg white
frame .two -width 100 -height 50 -bg grey50
foreach b {alpha beta} {
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
button .one.$b -text $b
pack .one.$b -side left
}
# Create a frame for two more buttons
frame .one.right
foreach b {delta epsilon} {
button .one.right.$b -text $b
pack .one.right.$b -side bottom
}
pack .one.right -side right
pack .one .two -side top
You can build more complex arrangements by introducing nested frames and switching between horizontal and vertical stacking as you go.
Within each frame pack all the children with either a combination of -side left and -side right, or -side top and -side bottom.
Example 25-4 replaces the .one.gamma button with a vertical stack of two buttons,.one.right.delta and .one.right.epsilon. These are packed
toward the bottom of .one.right, so the first one packed is on the bottom.
The frame .one.right was packed to the right, and in the previous example, the button.one.gamma was packed to the left. Despite the
difference, they ended up in the same position relative to the other two widgets packed inside the .one frame. The next section explains why.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
The Cavity Model
The packing algorithm is based on a cavity model for the available space inside a frame. For example, when
the main wish window is created, the main frame is empty and there is an obvious space, or cavity, in which
to place widgets. The primary rule about the packing cavity is a widget occupies one whole side of the cavity.
To demonstrate this, pack three widgets into the main frame. Put the first two on the bottom, and the third one
on the right:
Example 25-5 Mixing bottom and right packing sides
# pack two frames on the bottom.
frame .one -width 100 -height 50 -bg grey50
frame .two -width 40 -height 40 -bg white
pack .one .two -side bottom
# pack another frame to the right
frame .three -width 20 -height 20 -bg grey75
pack .three -side right
When we pack a third frame into the main window with -side left or -side right, the new frame is positioned inside the cavity, which is above
the two frames already packed toward the bottom side. The frame does not appear to the right of the existing frames as you might have
expected. This is because the .two frame occupies the whole bottom side of the packing cavity, even though its display does not fill up that
side.
Can you tell where the packing cavity is after this example? It is to the left of the frame .three, which is the last frame packed toward the right,
and it is above the frame .two, which is the last frame packed toward the bottom. This explains why there was no difference between the
previous two examples when .one.gamma was packed to the left, but .one.right was packed to the right. At that point, packing to the left or
right of the cavity had the same effect. However, it will affect what happens if another widget is packed into those two configurations. Try out
[*]
the following commands after running Example 25-3 and Example 25-4 and compare the difference.
[*]
Answer: After Example 25-3 the new button is to the right of all buttons. AfterExample 25-4 the new button is
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
between .one.beta and .one.right.
button .one.omega -text omega
pack .one.omega -side right
Each packing parent has its own cavity, which is why introducing nested frames can help. If you use a horizontal or vertical arrangement
inside any given frame, you can more easily simulate the packer's behavior in your head!
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Packing Space and Display Space
The packer distinguishes between packing space and display space when it arranges the widgets. The display space is the area requested by
a widget for the purposes of painting itself. The packing space is the area the packer allows for the placement of the widget. Because of
geometry constraints, a widget may be allocated more (or less) packing space than it needs to display itself. The extra space, if any, is along
the side of the cavity against which the widget was packed.
The -fill Option
The -fill packing option causes a widget to fill up the allocated packing space with its display. A widget can fill in the X or Y direction, or both.
The default is not to fill, which is why the black background of the main window has shown through in the examples so far:
Example 25-6 Filling the display into extra packing space
frame .one -width 100 -height 50 -bg grey50
frame .two -width 40 -height 40 -bg white
# Pack with fill enabled
pack .one .two -side bottom -fill x
frame .three -width 20 -height 20 -bg red
pack .three -side right -fill x
This is just like Example 25-5, except that -fill x has been specified for all the frames. The.two frame fills, but the .three frame does not. This is
because the fill does not expand into the packing cavity. In fact, after this example, the packing cavity is the part that shows through in black.
Another way to look at this is that the .two frame was allocated the whole bottom side of the packing cavity, so its fill can expand the frame to
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
occupy that space. The .three frame has only been allocated the right side, so a fill in the X direction will not have any effect.
Another use of fill is for a menu bar that has buttons at either end and some empty space between them. The frame that holds the buttons is
packed toward the top. The buttons are packed into the left and right sides of the menu bar frame. Without fill, the menu bar shrinks to be just
large enough to hold all the buttons, and the buttons are squeezed together. When fill is enabled in the X direction, the menu bar fills out the
top edge of the display:
Example 25-7 Using horizontal fill in a menu bar
frame .menubar -bg white
frame .body -width 150 -height 50 -bg grey50
# Create buttons at either end of the menubar
foreach b {alpha beta} {
button .menubar.$b -text $b
}
pack .menubar.alpha -side left
pack .menubar.beta -side right
# Let the menu bar fill along the top
pack .menubar -side top -fill x
pack .body
Internal Padding with -ipadx and -ipady
Another way to get more fill space is with the -ipadx and -ipady packing options that request more display space in the X and Y directions,
respectively. Due to other constraints the request might not be offered, but in general you can use this to give a widget more display space.
The next example is just like the previous one except that some internal padding has been added:
Example 25-8 The effects of internal padding (-ipady)
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Create and pack two frames
frame .menubar -bg white
frame .body -width 150 -height 50 -bg grey50
# Create buttons at either end of the menubar
foreach b {alpha beta} {
button .menubar.$b -text $b
}
pack .menubar.alpha -side left -ipady 10
pack .menubar.beta -side right -ipadx 10
# Let the menu bar fill along the top
pack .menubar -side top -fill x -ipady 5
pack .body
The alpha button is taller and thebeta button is wider because of the internal padding. The frame has internal padding, which reduces the
space available for the packing cavity, so the .menubar frame shows through above and below the buttons.
Some widgets have attributes that result in more display space. For example, it would be hard to distinguish a frame with width 50 and no
internal padding from a frame with width 40 and a -ipadx 5 packing option. The packer would give the frame5 more pixels of display space on
either side for a total width of 50.
Buttons have their own -padx and -pady options that give them more display space, too. This padding provided by the button is used to keep
its text away from the edge of the button. The following example illustrates the difference. The -anchor e button option positions the text as far
to the right as possible. Example 40-5 on page 617 provides another comparison of these options:
Example 25-9 Button padding vs. packer padding
# Foo has internal padding from the packer
button .foo -text Foo -anchor e -padx 0 -pady 0
pack .foo -side right -ipadx 10 -ipady 10
# Bar has its own padding
button .bar -text Bar -anchor e -pady 10 -padx 10
pack .bar -side right -ipadx 0 -ipady 0
In all cases, you can specify the amount of padding using any type of screen distance recognized by Tk. A simple numeric value is
interpreted as pixels. You can also follow a number with one of i, m, c, or p, which is interpreted as inches, millimeters, centimeters, or
typographic points, respectively.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
External Padding with -padx and -pady
The packer can provide external padding that allocates packing space that cannot be filled. The space is outside of the border that widgets
use to implement their 3D reliefs. Example 40-2 on page 614 shows the different reliefs. The look of a default button is achieved with an extra
frame and some padding:
Example 25-10 The look of a default button
. config -borderwidth 10
# OK is the default button
frame .ok -borderwidth 2 -relief sunken
button .ok.b -text OK
pack .ok.b -padx 5 -pady 5
# Cancel is not
button .cancel -text Cancel
pack .ok .cancel -side left -padx 5 -pady 5
The .ok.b button looks the same even if it is packed with-fill both. The child widgets do not fill the external padding provided by the packer.
Example 25-10 handcrafts the look of a default button. Tk 8.0 added a-default attribute for buttons that gives them the right appearance for the
default button on the current platform. It looks somewhat like this on UNIX, but the appearance is different on Macintosh and Windows.
Tk 8.4 added the ability to specify asymmetric padding as a list of two screen distances. For example, the following adds 5 pixels of padding
to the left and right of the widgets, 3 pixels above them, and 6 pixels below them:
pack .ok .cancel -side left -padx 5 -pady {3 6}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Resizing and -expand
The -expand true packing option lets a widget expand its packing space into unclaimed space in the packing cavity.
Example 25-6 could use
this on the small frame on top to get it to expand across the top of the display, even though it is packed to the right side. The more common
case occurs when you have a resizable window. When the user makes the window larger, the widgets have to be told to take advantage of
the extra space. Suppose you have a main widget like a text, listbox, or canvas that is in a frame with a scrollbar. That frame has to be told to
expand into the extra space in its parent (e.g., the main window) and then the main widget (e.g., the canvas) has to be told to expand into its
parent frame. Example 24-1 on page 378 does this.
In nearly all cases the -fill both option is used along with-expand true so that the widget actually uses its extra packing space for its own
display. The converse is not true. There are many cases where a widget should fill extra space but not attempt to expand into the packing
cavity. The examples below show the difference.
Now we can investigate what happens when the window is made larger. The next example starts like Example 25-7 on page 400, but the size
of the main window is increased:
Example 25-11 Resizing without the expand option
# Make the main window black
. config -bg black
# Create and pack two frames
frame .menubar -bg white
frame .body -width 150 -height 50 -bg grey50
# Create buttons at either end of the menubar
foreach b {alpha beta} {
button .menubar.$b -text $b
}
pack .menubar.alpha -side left
pack .menubar.beta -side right
# Let the menu bar fill along the top
pack .menubar -side top -fill x
pack .body
# Resize the main window to be bigger
wm geometry . 200x100
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Allow interactive resizing
wm minsize . 100 50
The only widget that claims any of the new space is .menubar because of its -fill x packing option. The .body frame needs to be packed
properly:
Example 25-12 Resizing with expand turned on
# Use all of Example 25–11 then repack .body
pack .body -expand true -fill both
If more than one widget inside the same parent is allowed to expand, then the packer shares the extra space between them proportionally.
This is probably not the effect you want in the examples we have built so far. The .menubar, for example, is not a good candidate for
expansion.
Example 25-13 More than one expanding widget
# Use all of Example 25–11 then repack .menubar and .body
pack .menubar -expand true -fill x
pack .body -expand true -fill both
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Anchoring
If a widget is left with more packing space than display space, you can position it within its packing space using the -anchor packing option.
The default anchor position is center. The other options correspond to points on a compass:n, ne, e, se, s, sw, w, and nw:
Example 25-14 Setup for anchor experiments
# Make the main window black
. config -bg black
# Create two frames to hold open the cavity
frame .prop -bg white -height 80 -width 20
frame .base -width 120 -height 20 -bg grey50
pack .base -side bottom
# Float a label and the prop in the cavity
label .foo -text Foo
pack .prop .foo -side right -expand true
The .base frame is packed on the bottom. Then the.prop frame and the .foo label are packed to the right with expand set but no fill. Instead of
being pressed up against the right side, the expand gives each of these widgets half of the extra space in the X direction. Their default anchor
of center results in the positions shown. The next example shows some different anchor positions:
Example 25-15 The effects of noncenter anchors
. config -bg black
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Create two frames to hold open the cavity
frame .prop -bg white -height 80 -width 20
frame .base -width 120 -height 20 -bg grey50
pack .base -side bottom
# Float the label and prop
# Change their position with anchors
label .foo -text Foo
pack .prop -side right -expand true -anchor sw
pack .foo -side right -expand true -anchor ne
The label has room on all sides, so each of the different anchors will position it differently. The.prop frame only has room in the X direction, so
it can only be moved into three different positions: left, center, and right. Any of the anchors w, nw, and sw result in the left position. The
anchors center, n, and s result in the center position. The anchorse, se, and ne result in the right position.
If you want to see all the variations, type in the following commands to animate the different packing anchors. The update idletasks forces any
pending display operations. The after 500 causes the script to wait for500 milliseconds:
Example 25-16 Animating the packing anchors
foreach anchor {center n ne e se s sw w nw center} {
pack .foo .prop -anchor $anchor
# Update the display
update idletasks
# Wait half a second
after 500
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Packing Order
The packer maintains an order among the children that are packed into a frame. By default, each new child is appended to the end of the
packing order. The most obvious effect of the order is that the children first in the packing order are closest to the side they are packed
against. You can control the packing order with the -before and -after packing options, and you can reorganize widgets after they have
already been packed:
Example 25-17 Controlling the packing order
# Create five labels in order
foreach label {one two three four five} {
label .$label -text $label
pack .$label -side left -padx 5
}
# ShuffleUp moves a widget to the beginning of the order
proc ShuffleUp { parent child } {
set first [lindex [pack slaves $parent] 0]
pack $child -in $parent -before $first
}
# ShuffleDown moves a widget to the end of the order
proc ShuffleDown { parent child } {
pack $child -in $parent
}
ShuffleUp . .five
ShuffleDown . .three
Introspection
The pack slaves command returns the list of children in their packing order. TheShuffleUp procedure uses this to find out the first child so that
it can insert another child before it. The ShuffleDown procedure is simpler because the default is to append the child to the end of the packing
order.
When a widget is repacked, then it retains all its packing parameters that have already been set. If you need to examine the current packing
parameters for a widget, use the pack info command.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
pack info .five
=> -in . -anchor center -expand 0 -fill none -ipadx 0 \
-ipady 0 -padx 0 -pady 0 -side left
Pack the Scrollbar First
The packing order also determines what happens when the window is made too small. If the window is made
small enough the packer will clip children that come later in the packing order. This is why, when you pack a
scrollbar and a text widget into a frame, you should pack the scrollbar first. Otherwise, when the window is
made smaller the text widget takes up all the space and the scrollbar is clipped.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Choosing the Parent for Packing
In nearly all of the examples in this chapter, a widget is packed into its parent frame. In general, it is possible to pack a widget into any
descendent of its parent. For example, the .a.b widget could be packed into .a, .a.c or .a.d.e.f. The -in packing option lets you specify an
alternate packing parent. One motivation for this is that the frames introduced to get the arrangement right can cause cluttered names for
important widgets. In Example 25-4 on page 398, the buttons have names like.one.alpha and .one.right.delta, which is not consistent. Here is
an alternate implementation of the same example that simplifies the button names and gives the same result:
Example 25-18 Packing into other relatives
# Create and pack two frames
frame .one -bg white
frame .two -width 100 -height 50 -bg grey50
# Create a row of buttons
foreach b {alpha beta} {
button .$b -text $b
pack .$b -in .one -side left
}
# Create a frame for two more buttons
frame .one.right
foreach b {delta epsilon} {
button .$b -text $b
pack .$b -in .one.right -side bottom
}
pack .one.right -side right
pack .one .two -side top
When you do this, remember that the order in which you create widgets is important. Create the frames first, then create the widgets. The
stacking order for windows will cause the later windows to obscure the windows created first. The following is a common mistake because the
frame obscures the button:
button .a -text hello
frame .b
pack .a -in .b
If you cannot avoid this problem scenario, then you can use theraise command to fix things up. Stacking order is also discussed on page 409.
raise .a
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Unpacking a Widget
The pack forget command removes a widget from the packing order. The widget gets unmapped, so it is not visible. If you unpack a parent
frame, the packing structure inside it is maintained, but all the widgets inside the frame get unmapped. Unpacking a widget is useful if you
want to suppress extra features of your interface. You can create all the parts of the interface, and just delay packing them in until the user
requests to see them. Then you can pack and unpack them dynamically.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Packer Summary
Keep these rules in mind about the packer:
Pack vertically (-side top and -side bottom) or horizontally (-side left and -side right) within a frame. Only rarely will a different
mixture of packing directions work out the way you want. Add frames to build more complex structures.
By default, the packer puts widgets into their parent frame, and the parent frame must be created before the children that are
packed into it.
If you put widgets into other relatives, remember to create the frames first so the frames stay underneath the widgets packed into
them.
By default, the packer ignores -width and -height attributes of frames that have widgets packed inside them. It shrinks frames to be
just big enough to allow for its border width and to hold the widgets inside them. Use pack propagate to turn off the shrink-wrap
behavior.
The packer distinguishes between packing space and display space. A widget's display might not take up all the packing space
allocated to it.
The -fill option causes the display to fill up the packing space in the X or Y directions, or both.
The -expand true option causes the packing space to expand into any room in the packing cavity that is otherwise unclaimed. If
more than one widget in the same frame wants to expand, then they share the extra space.
The -ipadx and -ipady options allocate more display space inside the border, if possible.
The -padx and -pady options allocate more packing space outside the border, if possible. The widget never fills this space. These
values may be specified as a list of two values to get asymmetric padding (Tk 8.4.)
The pack Command
Table 25-1 summarizes the pack command. Table 25-2 summarizes the packing options for a widget. These are set with thepack configure
command, and the current settings are returned by the pack info command.
Table 25-1. The pack command
pack win ?win ..? ?options?
This is just like pack configure.
pack configure win ?win ...? ?options?
Packs one or more widgets according to the options, which are given inTable 25-2.
pack forget win ?win...?
Unpacks the specified windows.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
pack info win
Returns the packing parameters of win.
pack propagate win ?bool?
Queries or sets the geometry propagation of win, which has other widgets packed inside it.
pack slaves win
Returns the list of widgets managed bywin.
Table 25-2. Packing options
-after win
Packs after win in the packing order.
-anchor anchor
Anchors: center, n, ne, e, se, s, sw, w, or nw.
-before win
Packs before win in the packing order.
-expand boolean
Controls expansion into the unclaimed packing cavity.
-fill style
Controls fill of packing space. Style:x, y, both, or none.
-in win
Packs inside win.
-ipadx amount
Horizontal internal padding, in screen units.
-ipady amount
Vertical internal padding, in screen units.
-padx amount
Horizontal external padding, in screen units. May be a list of two screen units for asymmetric padding (Tk 8.4).
-pady amount
Vertical external padding, in screen units. May be a list of two screen units for asymmetric padding (Tk 8.4).
-side side
Sides: top, right, bottom, or left.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Window Stacking Order
The raise and lower commands control the window stacking order. The stacking order controls the display of windows. Windows higher in the
stacking order obscure windows lower in the stacking order. By default, new windows are created at the top of the stacking order so they
obscure older windows. Consider this sequence of commands:
button .one
frame .two
pack .one -in .two
If you do this, you do not see the button. The problem is that the frame is higher in the stacking order so it obscures the button.
You can change the stacking order with theraise command:
raise .one .two
This puts .one just above .two in the stacking order. If.two was not specified, then .one would be put at the top of the stacking order.
The lower command has a similar form. With one argument, it puts that window at the bottom of the stacking order. Otherwise, it puts it just
below another window in the stacking order.
You can use raise and lower on top-level windows to control their stacking order among all other top-level windows. For example, if a user
requests a dialog that is already displayed, use raise to make it pop to the foreground of their cluttered desktop. To determine the stacking
order of toplevel windows, use the wm stackorder command. (See "Toplevel Size, Placement, and Decoration" on page 658.)
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 26. The Grid Geometry Manager
This chapter explores the grid geometry manager that positions widgets on a grid that automatically adjusts its size. Grid was added in Tk 4.1.
The grid geometry manager arranges widgets on a grid with variable-sized rows and columns. You specify the rows and columns occupied by
each widget, and the grid is adjusted to accommodate all the widgets it contains. This is ideal for creating table-like layouts. The manager
also has sophisticated facilities for controlling row and column sizes and the dynamic resize behavior. By introducing subframes with grids of
their own, you can create arbitrary layouts.
Don't pack and grid into the same manager widget.
As discussed on page 395, you can use a combination of pack and grid to create your display. But for each individual manager widget, you
must use only one of pack or grid to manage all of its immediate children.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
A Basic Grid
Example 26-1 uses grid to lay out a set of labels and frames in two parallel columns. It takes advantage of the relative placement feature of
grid. Instead of specifying rows and columns, the order ofgrid commands and their arguments implies the layout. Eachgrid command starts a
new row, and the order of the widgets in the grid command determines the column. In the example, there are two columns, and each iteration
of the loop adds a new row. grid makes each column just wide enough to hold the biggest widget. Widgets that are smaller are centered in
their cell. That's why the labels appear centered in their column:
Example 26-1 A basic grid
foreach color {red orange yellow green blue purple} {
label .l$color -text $color -bg white
frame .f$color -background $color -width 100 -height 2
grid .l$color .f$color
}
The -sticky Setting
If a grid cell is larger than the widget inside it, you can control the size and position of the widget with the -sticky option. The -sticky option
combines the functions of -fill and -anchor used with the pack geometry manager. You specify to which sides of its cell a widget sticks. You
can specify any combination of n, e, w, and s to stick a widget to the top, right, left, and bottom sides of its cell. You can concatenate these
letters together (e.g., news) or uses spaces or commas to separate them (e.g.,n,e,w,s). Example 26-2 uses -sticky w to left justify the labels,
and -sticky ns to stretch the color frames to the full height of their row:
Example 26-2 A grid with sticky settings
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
foreach color {red orange yellow green blue purple} {
label .l$color -text $color -bg white
frame .f$color -background $color -width 100 -height 2
grid .l$color .f$color
grid .l$color -sticky w
grid .f$color -sticky ns
}
Example 26-2 uses grid in two ways. The firstgrid in the loop fixes the positions of the widgets because it is the first time they are assigned to
the master. The next grid commands modify the existing parameters; they just adjust the-sticky setting because their row and column
positions are already known.
You can specify row and column positions explicitly with the -row and -column attributes. This is generally more work than using the relative
placement, but it is necessary if you need to dynamically move a widget into a different cell. Example 26-3 keeps track of rows and columns
explicitly and achieves the same layout as Example 26-2:
Example 26-3 A grid with row and column specifications
set row 0
foreach color {red orange yellow green blue purple} {
label .l$color -text $color -bg white
frame .f$color -background $color -width 100
grid .l$color -row $row -column 0 -sticky w
grid .f$color -row $row -column 1 -sticky ns
incr row
}
External Padding with -padx and -pady
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
You can keep a widget away from the edge of its cell with the -padx and -pady settings. Example 26-4 uses external padding to shift the
labels away from the left edge, and to keep some blank space between the color bars:
Example 26-4 A grid with external padding
foreach color {red orange yellow green blue purple} {
label .l$color -text $color -bg white
frame .f$color -background $color -width 100 -height 2
grid .l$color .f$color
grid .l$color -sticky w -padx 3
grid .f$color -sticky ns -pady 1
}
Tk 8.4 added the ability to specify asymmetric padding as a list of two screen distances. For example, -padx {0.125i 0.25i} adds 1/8 inch of
padding to the left and 1/4 inch padding to the right of a widget.
Internal Padding with -ipadx and -ipady
You can give a widget more display space than it normally needs with internal padding. The internal padding increases the size of the grid. In
contrast, a -sticky setting might stretch a widget, but it will not change the size of the grid.
Example 26-5 makes the labels taller with-ipady:
Example 26-5 A grid with internal padding
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
foreach color {red orange yellow green blue purple} {
label .l$color -text $color -bg white
frame .f$color -background $color -width 100 -height 2
grid .l$color .f$color
grid .l$color -sticky w -padx 3 -ipady 5
grid .f$color -sticky ns -pady 1
}
Multiple Widgets in a Cell
Example 26-6 shows all possible -sticky settings. It uses the ability to put more than one widget into a grid cell. A large square frame is put in
each cell, and then a label is put into the same cell with a different -sticky setting. It is important to create the frame first so it is below the
label. Window stacking is discussed on page 409. External padding is used to keep the labels away from the edge so that they do not hide the
-ridge relief of the frames.
Example 26-6 All combinations of -sticky settings
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
set index 0
foreach x {news ns ew " " new sew wsn esn nw ne sw se n s w e} {
frame .f$x -borderwidth 2 -relief ridge -width 40 -height 40
grid .f$x -sticky news \
-row [expr {$index/4}] -column [expr {$index%4}]
label .l$x -text $x -background white
grid .l$x -sticky $x -padx 2 -pady 2 \
-row [expr {$index/4}] -column [expr {$index%4}]
incr index
}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Spanning Rows and Columns
A widget can occupy more than one cell. The -rowspan and -columnspan attributes indicate how many rows and columns are occupied by a
widget. Example 26-7 uses explicit row, column, rowspan, and columnspan specifications:
Example 26-7 Explicit row and column span
. config -bg white
foreach color {888 999 aaa bbb ccc fff} {
frame .$color -bg #$color -width 40 -height 40
}
grid .888 -row 0 -column 0 -columnspan 3 -sticky news
grid .999 -row 1 -column 0 -rowspan 2 -sticky news
grid .aaa -row 1 -column 1 -columnspan 2 -sticky news
grid .bbb -row 2 -column 2 -rowspan 2 -sticky news
grid .ccc -row 3 -column 0 -columnspan 2 -sticky news
grid .fff -row 2 -column 1 -sticky news
You can also use special syntax in grid commands that imply row and column placement. Special characters represent a cell that is spanned
or skipped:
- represents a spanned column.
^ represents a spanned row.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
x represents a skipped cell.
A nice feature of the implicit row and column assignments is that it is easy to make minor changes to your layout. Example 26-8 achieves the
same layout:
Example 26-8 Grid syntax row and column span
. config -bg white
foreach color {888 999 aaa bbb ccc ddd fff} {
frame .$color -bg #$color -width 40 -height 40
}
grid .888 - -sticky news
grid .999 .aaa -sticky news
grid ^ .fff .bbb -sticky news
grid .ccc - ^
-sticky news
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Row and Column Constraints
The grid manager supports attributes on whole rows and columns that affect their size and resize behavior. Thegrid command has a
rowconfigure and columnconfigure operation to set and query these attributes:
grid columnconfigure master col ?attributes?
grid rowconfigure master row ?attributes?
With no attributes, the current settings are returned. Therow and col specifications can be lists instead of simple indices, so you can configure
several rows or columns at once.
Row and Column Padding
The -pad attribute increases a row or column size. The initial size of a row or column is determined by the largest widget, and
-pad adds to this
size. This padding can be filled by the widget by using the -sticky attribute. Row and column padding works like internal padding because it is
extra space that can be occupied by the widget's display. In contrast, the -padx and -pady attributes on an individual widget act like a spacer
that keeps the widget away from the edge of the cell. Example 26-9 shows the difference. The row padding increases the height of the row,
but the padding on .f1 keeps it away from the edge of the cell:
Example 26-9 Row padding compared to cell padding
. config -bg black
label .f1 -text left -bg #ccc
label .f2 -text right -bg #aaa
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
grid .f1 .f2 -sticky news
;# no padding
grid .f1 -padx 10 -pady 10
;# cell padding
grid rowconfigure . 0 -pad 20 ;# row padding
Minimum Size
The -minsize attribute restricts a column or row to be a minimum size. The row or column can grow bigger if its widget requests it, but they will
not get smaller than the minimum. One useful application of -minsize is to create empty rows or columns, which is more efficient than creating
an extra frame.
Managing Resize Behavior
If the master frame is bigger than the required size of the grid, it shrinks to be just large enough to contain the grid. You can turn off the
shrink-wrap behavior with grid propagate. If geometry propagation is off, then the grid is centered inside the master. If the master frame is too
small to fit the grid, then the grid is anchored to the upper-left corner of the master and clipped on the bottom-right.
By default, rows and columns do not resize when you grow the master frame. You enable resizing by specifying a -weight for a row or column
that is an integer value greater than zero. Example 26-10 grids a text widget and two scrollbars. The protocol between thescrollbar and the
text widget is described on page 501. The text widget is in row 0, column 0, and both of these can expand. The vertical scrollbar is in row 0,
column 1, so it only grows in the Y direction. The horizontal scrollbar is in row 1, column 0, so it only grows in the X direction:
Example 26-10 Gridding a text widget and scrollbar
text .text -yscrollcommand ".yscroll set" \
-xscrollcommand ".xscroll set"-width 40 -height 10
scrollbar .yscroll -command ".text yview" -orient vertical
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
scrollbar .xscroll -command ".text xview" -orient horizontal
grid .text .yscroll -sticky news
grid .xscroll -sticky ew
grid rowconfigure . 0 -weight 1
grid columnconfigure . 0 -weight 1
You can use different weights to let different rows and columns grow at different rates. However, there are some tricky issues because the
resize behavior applies to extra space, not total space. For example, suppose there are four columns that have widths 10, 20, 30, and 40
pixels, for a total of 100. If the master frame is grown to 140 pixels wide, then there are 40 extra pixels. If each column has weight 1, then
each column gets an equal share of the extra space, or 10 more pixels. Now suppose column 0 has weight 0, columns 1 and 2 have weight 1,
and column 3 has weight 2. Column 0 will not grow, columns 1 and 2 will get 10 more pixels, and column 3 will get 20 more pixels. In most
cases, weights of 0 or 1 make the most sense.
Weight works in reverse when shrinking.
If a row or column has to shrink, the weights are applied in reverse. A row or column with a higher weight will shrink more. For example, put
two equal sized frames in columns with different weights. When the user makes the window bigger, the frame in the column with more weight
gets larger more quickly. When the window is made smaller, that frame gets smaller more quickly.
Uniform Columns
The -uniform attribute makes it easy to create columns (or rows) that are the same width (or height). Use the-uniform attribute to create a
group of columns (or rows). The value of the attribute can by anything (e.g., xyz). All columns (or rows) with the same-uniform attribute are in
the same group. If they all have the same -weight value, then they are all the same size. If one column (or row) in a group has-weight
a
that is
twice what the other columns (or rows) have, then it is twice as big. This is illustrated in Example 26-11.
Example 26-11 Uniform column width
foreach x {alpha beta gamma x y z} {
label .$x -text $x
}
.beta config -bg white
.y config -bg white
grid .alpha .beta .gamma -sticky news
grid .x .y .z -sticky news
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
grid columnconfigure . "0 1 2" -uniform group1 -weight 1
grid columnconfigure . 1 -weight 2
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The grid Command
Table 26-1 summarizes the usage of the grid command. Table 26-2 summarizes the options for a widget set with thegrid configure command.
Table 26-1. The grid command
grid bbox master ?c1 r1? ?c2 r2?
Returns the bounding box, of the whole grid, the cell atc1, r1, or the cells from c1, r1 to c2, r2.
grid columnconfigure master col ?options?
Sets or queries the configuration of col. Options are-minsize, -weight, -pad, and -uniform.
grid configure win ?win ...? ?options?
Grids one or more widgets according to theoptions, which are given inTable 26-2.
grid forget win ?win...?
Unmaps the specified windows.
grid info win
Returns the grid options of win.
grid location master x y
Returns the cell column and row under the pointx, y in master.
grid propagate master ?boolean?
Enables or disables shrink-wrapping of master.
grid rowconfigure master row ?options?
Sets or queries the configuration of row. Options are-minsize, -weight, -pad, and -uniform.
grid remove slave
Unmaps slave, but remember its configuration.
grid size master
Returns the number of columns and rows.
grid slaves win ?-row r? ?-column c?
Returns the list of widgets managed bywin, or just those in the specified row or column.
Table 26-2. Grid widget options
-in win
Places inside win.
-column col
Column position. Columns count from zero.
-columnspan n
Spans n columns.
-ipadx pixels
Internal widget padding in the X direction, in screen units.
-ipady pixels
Internal widget padding in the Y direction, in screen units.
-padx pixels
External widget padding in the X direction, in screen units. May be a list of two screen units for asymmetric padding (Tk
8.4).
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
-pady pixels
External widget padding in the Y direction, in screen units. May be a list of two screen units for asymmetric padding (Tk
8.4).
-row row
Row position. Rows count from zero.
-rowspan n
Spans n rows.
-sticky how
Positions widget next to any combination of north (n), south (s), east (e), and west (w) sides of the cell. Use {} for center.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 27. The Place Geometry Manager
This chapter explores the place geometry manager that positions widgets on the screen.
The place geometry manager is much simpler thanpack and grid. You specify the exact position and size of a window, or you specify the
relative position and relative size of a widget. This is useful in a few situations, but it rapidly becomes tedious if you have to position lots of
windows. The best application of place is to create special-purpose geometry managers using its relative constraints. A standard application
of place is to adjust the boundary between two adjacent windows.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
place
Basics
The place command lets you specify the width and height of a window, and the X and Y locations of the window's anchor point. The size and
location can be specified in absolute or relative terms. Relative specifications are more powerful. Example 27-1 uses place to center a
window in its parent. You can use this command to position dialogs that you do not want to be detached top-level windows:
Example 27-1 Centering a window with place
place $w -in $parent -relx 0.5 -rely 0.5 -anchor center
The -relx and -rely specify the relative X and Y positions of the anchor point of the widget$w in $parent. A relative X (or Y) value of zero
corresponds to the left (or top) edge of $parent. A value of one corresponds to the right (or bottom) edge of$parent. A value of 0.5 specifies
the middle. The anchor point determines what point in $w is positioned according to the specifications. In Example 27-1 the center anchor
point is used so that the center of $w is centered in $parent.
The relative height and width settings are used to base a widget's size on another widget. Example 27-2 completely covers one window with
another window. It uses the default anchor point for windows, which is their upper-left hand corner (nw):
Example 27-2 Covering a window with place
place $w -in $parent -relwidth 1 -relheight 1 -x 0 -y 0
The absolute and relative size and position parameters are additive (e.g., -width and -relwidth). You can make a window slightly larger or
smaller than the parent by specifying both parameters. In Example 27-3, a negative width and height are used to make a window smaller than
another one:
Example 27-3 Combining relative and absolute sizes
place $w -in $parent -relwidth 1 -relheight 1 -x 0 -y 0 \
-width -4 -height -4
It is not necessary for $parent to actually be the parent widget of $w. The requirement is that $parent be the parent, or a descendant of the
parent, of $w. It also has to be in the same top-level window. This guarantees that$w is visible whenever $parent is visible. These are the
same restrictions imposed by the pack geometry manager.
It is not necessary to position a widget inside another widget, either. Example 27-4 positions a window five pixels above a sibling widget. If
$sibling is repositioned, then $w moves with it. This approach is useful when you decorate a resizable window by placing other widgets at its
corners or edges. When the window is resized, the decorations automatically move into place:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Example 27-4 Positioning a window above a sibling with place
place $w -in $sibling -relx 0.5 -y -5 -anchor s \
-bordermode outside
The -bordermode outside option is specified so that any decorative border in$sibling is ignored when positioning $w. In this case the position
is relative to the outside edge of $sibling. By default, the border is taken into account to make it easy to position widgets inside their parent's
border.
The parent widget does not have to be a frame. Example 27-1 can be used to place a dialog in the middle of atext widget. In Example 27-4,
$sibling and $w can both be label widgets.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The Pane Manager
The relative size and placement parameters of the place command can be used to create custom geometry managers.Example 27-5 shows a
paned layout manager. Two frames, or panes, are placed inside another frame. A small third frame represents a grip that is used to adjust the
boundary between the two panes.
Note that Tk 8.4 added apanedwindow widget, which can manage an arbitrary number of horizontal or
vertical panes. See Chapter 28 for information on how to use the panedwindow widget.
Example 27-5 Pane_Create sets up vertical or horizontal panes
proc Pane_Create {f1 f2 args} {
# Map optional arguments into array values
set t(-orient) vertical
set t(-percent) 0.5
set t(-in) [winfo parent $f1]
array set t $args
# Keep state in an array associated with the master frame
set master $t(-in)
upvar #0 Pane$master pane
array set pane [array get t]
# Create the grip and set placement attributes that
# will not change. A thin divider line is achieved by
# making the two frames one pixel smaller in the
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
# adjustable dimension and making the main frame black.
set pane(1) $f1
set pane(2) $f2
set pane(grip) [frame $master.grip -background gray50 \
-width 10 -height 10 -bd 1 -relief raised \
-cursor crosshair]
if {[string match vert* $pane(-orient)]} {
set pane(D) Y;# Adjust boundary in Y direction
place $pane(1) -in $master -x 0 -rely 0.0 -anchor nw \
-relwidth 1.0 -height -1
place $pane(2) -in $master -x 0 -rely 1.0 -anchor sw \
-relwidth 1.0 -height -1
place $pane(grip) -in $master -anchor c -relx 0.8
} else {
set pane(D) X ;# Adjust boundary in X direction
place $pane(1) -in $master -relx 0.0 -y 0 -anchor nw \
-relheight 1.0 -width -1
place $pane(2) -in $master -relx 1.0 -y 0 -anchor ne \
-relheight 1.0 -width -1
place $pane(grip) -in $master -anchor c -rely 0.8
}
$master configure -background black
# Set up bindings for resize, <Configure>, and
# for dragging the grip.
bind $master <Configure> [list PaneGeometry $master]
bind $pane(grip) <ButtonPress-1> \
[list PaneDrag $master %$pane(D)]
bind $pane(grip) <B1-Motion> \
[list PaneDrag $master %$pane(D)]
bind $pane(grip) <ButtonRelease-1> \
[list PaneStop $master]
# Do the initial layout
PaneGeometry $master
}
Parsing Arguments and Maintaining State
The Pane_Create procedure is given two widgets to manage, and an optional set of parameters. The general syntax ofPane_Create is:
Pane_Create f1 f2 ?-orient xy? ?-percent p? ?-in master?
All the optional arguments are available in $args. Its attribute-value structure is used to initialize a temporary arrayt. Default values are set
before the assignment from $args. The following code is compact but doesn't check errors in the optional arguments.
set t(-orient) vertical
set t(-percent) 0.5
set t(-in) [winfo parent $f1]
array set t $args
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Global state about the layout is kept in an array whose name is based on the master frame. The name of the master frame isn't known until
after arguments are parsed, which is why t is used. After the upvar the argument values are copied from the temporary array into the global
state array:
set master $t(-in)
upvar #0 Pane$master pane
array set pane [array get t]
Sticky Geometry Settings
Example 27-5 sets several place parameters on the frames when they are created. These are remembered, and other parameters are
adjusted later to dynamically adjust the boundary between the frames. All Tk geometry managers retain settings like this. The initial settings
for the vertical layout is shown here:
place $pane(1) -in $parent -x 0 -rely 0.0 -anchor nw \
-relwidth 1.0 -height -1
place $pane(2) -in $parent -x 0 -rely 1.0 -anchor sw \
-relwidth 1.0 -height -1
place $pane(grip) -in $parent -anchor c -relx 0.8
The position of the upper and lower frames is specified with an absolute X and a relative Y position, and the anchor setting is chosen to keep
the frame visible inside the main frame. For example, the lower frame is positioned at the bottom-left corner of the container with -x 0 and -rely
1.0. The -anchor sw attaches the lower-left corner of the frame to this position.
The size of the contained frames is also a combination of absolute and relative values. The width is set to the full width of the container with
-relwidth 1.0. The height is set to minus one with -height -1. This value gets added to a relative height that is determined later. It will leave a
little space between the two contained frames.
The resize grip is just a small frame positioned at the boundary. Initially it is just placed over toward one size with -relx 0.8. It gets positioned
on the boundary with a -rely setting later. It has a different cursor to indicate it is active.
Event Bindings
The example uses some event bindings that are described in more detail in Chapter 29. The <Configure> event occurs when the containing
frame is resized by the user. When the user presses the mouse button over the grip and drags it, there is a <ButtonPress-1> event, one or
more <B1-Motion> events, and finally a<ButtonRelease-1> event. Tcl commands are bound to these events:
bind $parent <Configure> [list PaneGeometry $parent]
bind $pane(grip) <ButtonPress-1> \
[list PaneDrag $parent %$pane(D)]
bind $pane(grip) <B1-Motion> \
[list PaneDrag $parent %$pane(D)]
bind $pane(grip) <ButtonRelease-1> [list PaneStop $parent]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Managing the Layout
The code is set up to work with either horizontal or vertical layouts. The pane(D) variable is either X, for a horizontal layout, orY, for a vertical
layout. This value is used in the bindings to get %X or %Y, which are replaced with the X and Y screen positions of the mouse when the
bindings fire. This value is passed to PaneDrag as the parameter D. The PaneDrag procedure remembers the previous position inpane(lastD)
and uses that to update the percentage split between the two contained panes:
Example 27-6 PaneDrag adjusts the percentage
proc PaneDrag {master D} {
upvar #0 Pane$master pane
if [info exists pane(lastD)] {
set delta [expr double($pane(lastD) - $D) \
/ $pane(size)]
set pane(-percent) [expr $pane(-percent) - $delta]
if {$pane(-percent) < 0.0} {
set pane(-percent) 0.0
} elseif {$pane(-percent) > 1.0} {
set pane(-percent) 1.0
}
PaneGeometry $master
}
set pane(lastD) $D
}
proc PaneStop {master} {
upvar #0 Pane$master pane
catch {unset pane(lastD)}
}
The PaneGeometry procedure adjusts the positions of the frames. It is called when the main window is resized, so it updatespane(size). It is
also called as the user drags the grip. For a vertical layout, the grip is moved by setting its relative Y position. The size of the two contained
frames is set with a relative height. Remember that this is combined with the fixed height of -1 to get some space between the two frames:
Example 27-7 PaneGeometry updates the layout
proc PaneGeometry {master} {
upvar #0 Pane$master pane
if {$pane(D) == "X"} {
place $pane(1) -relwidth $pane(-percent)
place $pane(2) -relwidth [expr 1.0 - $pane(-percent)]
place $pane(grip) -relx $pane(-percent)
set pane(size) [winfo width $master]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
} else {
place $pane(1) -relheight $pane(-percent)
place $pane(2) -relheight [expr 1.0 - $pane(-percent)]
place $pane(grip) -rely $pane(-percent)
set pane(size) [winfo height $master]
}
}
proc PaneTest {{p .p} {orient vert}} {
catch {destroy $p}
frame $p -width 200 -height 200
label $p.1 -bg blue -text foo
label $p.2 -bg green -text bar
pack $p -expand true -fill both
pack propagate $p off
Pane_Create $p.1 $p.2 -in $p -orient $orient -percent 0.3
}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The place Command
Table 27-1 summarizes the usage of the place command.
Table 27-1. The place command
place win ?win ..? ?options?
This is just like place configure.
place configure win ?win ...? ?options?
Places one or more widgets according to theoptions, which are givenTable 27-2.
place forget win ?win...?
Unmaps the specified windows.
place info win
Returns the placement parameters of win.
place slaves win
Returns the list of widgets managed bywin.
Table 27-2 summarizes the placement options for a widget, which you set with theplace configure command and retrieve with the place info
command.
Table 27-2. Placement options
-in win
Places inside (or relative to) win.
-anchor where
Anchors: center, n, ne, e, se, s, sw, w, or nw. Default: nw.
-x coord
X position, in screen units, of the anchor point.
-relx offset
Relative X position. 0.0 is the left edge. 1.0 is the right edge.
-y coord
Y position, in screen units, of the anchor point.
-rely offset
Relative Y position. 0.0 is the top edge. 1.0 is the bottom edge.
-width size
Width of the window, in screen units.
-relwidth size
Width relative to parent's width. 1.0 is full width.
-height size
Height of the window, in screen units.
-relheight size
Height relative to the parent's height. 1.0 is full height.
-bordermode
mode
If mode is inside, then size and position are inside the parent's border. If mode isoutside, then size and position are
relative to the outer edge of the parent. The default is inside.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 28. The Panedwindow Widget
The panedwindow widget, introduced in Tk 8.4, displays widgets in resizable horizontal or vertical panes.
A panedwindow contains any number of panes, arranged horizontally or vertically. Each pane contains one widget, and each pair of panes is
separated by a moveable sash, which causes the widgets on either side of the sash to be resized. When a panedwindow is resized externally
— for example, if the user resizes the toplevel containing the panedwindow — space is added or subtracted from the last pane (right-most or
bottom-most pane) in the widget.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Using the Panedwindow
The panedwindow is a relatively simple widget, requiring little configuration or programming in most applications. The most frequently used
configuration attribute is orient, which determines whether the widget has ahorizontal or vertical arrangement of panes. The other frequently
used configuration attribute is showHandle. The handle is a small square drawn on the sashes, giving users another visual cue that the
sashes are interactive. The default value of showHandle is False on Windows to match its native look and feel. Most other configuration
attributes control the size, positioning, and appearance of the handles, the sashes, and the widget in general.
Manipulating the Pane Contents
Once you've created the panedwindow, you add widgets to it with the add operation. You can add multiple widgets with a singleadd operation.
The panedwindow displays each widget added in its own pane, separated by sashes. By default, the widgets are arranged in the order
added. However, you can override this behavior with the -after and -before options to insert widgets after or before currently managed
widgets. You can add horizontal and vertical padding to the widgets in the panes with -padx and -pady options, just like with other geometry
managers. The -minsize attribute allows you to specify a minimum size for managed widgets (in any screen units supported by Tk.) You can
also control the position of a widget within its pane with the -sticky attribute, which operates similarly to grid's -sticky attribute. The
panedwindow's default -sticky setting is nsew, causing the managed widget to resize to completely fill its pane in both directions.
Don't pack, grid, or place the widgets in a panedwindow.
A panedwindow widget is not only a container for other widgets, but it is also a geometry manager. It controls the size and position of the
widgets that it manages. Therefore, don't use the pack, grid, or place commands to control the widgets that you add to a panedwindow.
Of course, for more complex interfaces, you can add frames as the managed widgets of a panedwindow, and then pack, grid, or place other
widgets within those frames. As an example, consider a layout with two text widgets. We'd like each text widget to have horizontal and vertical
scrollbars, which is a natural application of grid. But then we want the entire layout managed by a 2-pane vertical panedwindow. In this case,
we'll use a labelframe widget to contain each gridded text-and-scrollbar assembly, and then add each labelframe as a managed widget of our
panedwindow. The result is shown in Example 28-1.
Example 28-1 A panedwindow with complex managed widgets
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Create the panedwindow to manage the entire display
panedwindow .p -orient vertical -showhandle 1
pack .p -expand yes -fill both
# Create 2 labelframe widgets, each containing a
# gridded text and scrollbar assembly.
foreach {w label} {code "Code:" notes "Notes:"} {
set f [labelframe .p.$w -text $label]
text $f.t -height 10 -width 40 \
-wrap none -font {courier 12} \
-xscrollcommand [list $f.xbar set] \
-yscrollcommand [list $f.ybar set]
scrollbar $f.xbar -orient horizontal \
-command [list $f.t xview]
scrollbar $f.ybar -orient vertical \
-command [list $f.t yview]
grid $f.t -row 0 -column 0 -sticky news -padx 2 -pady 2
grid $f.ybar -row 0 -column 1 -sticky ns -padx 2 -pady 2
grid $f.xbar -row 1 -column 0 -sticky ew -padx 2 -pady 2
grid columnconfigure $f 0 -weight 1
grid rowconfigure $f 0 -weight 1
# Add the frame assembly to the panedwindow
.p add $f -minsize 1i -padx 4 -pady 6
}
The forget operation removes widgets from a panedwindow. The widgets aren't destroyed, but they are no longer managed by the paned
window, and the pane they formerly occupied is removed from the panedwindow. You can also get a list of the widgets currently managed by
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
a panedwindow (in the order in which they appear) with the panes operation.
Create the managed widgets as children of the panedwindow.
For best results, create the widgets managed by a panedwindow as children of that panedwindow. Tk then automatically handles the stacking
order for windows so that the child appears on top of the panedwindow. If you don't create the managed widget as a child of the
panedwindow, you either need to create the managed widget after the panedwindow, or else use the raise command to raise the managed
widget above the panedwindow, as discussed in "Window Stacking Order" on page 409.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Programming Panedwindow Widgets
Table 28-1 summarizes the operations for programming a panedwindow. In the table,$w is a panedwindow widget and win is a widget
managed by the panedwindow.
Table 28-1. Panedwindow operations
$w add win ?win...? ?option value...?
Adds one or more widgets to the panedwindow, each in a separate pane, with options as
described in Table 28-2.
$w cget option
Returns the value of the configuration option as described in Table 28-3.
$w configure ?option value...?
Queries or modifies the panedwindow configuration, with options as described in Table 28-3.
$w forget win ?win...?
Removes the pane(s) containing widget(s) from the panedwindow.
$w identify x y
Identifies the panedwindow component underneath the specified point.
$w panecget win option
Returns the value of the widget's configuration option as described in Table 28-2.
$w paneconfigure index ?option?
?value? ?...?
Queries or modifies the widget's configuration options as described in Table 28-2.
$w panes
Returns an ordered list of the widgets managed by the panedwindow.
$w proxy coord
Returns the current x and y coordinate pair for the sash proxy, used for rubberband-style pane
resizing.
$w proxy forget
Removes the sash proxy from the display.
$w proxy place x y
Places the sash proxy at the given coordinates.
$w sash coord index
Returns the current x and y coordinate pair for the sash indicated byindex.
$w sash dragto index x y
Moves the sash from the previous mark position.
$w sash mark index x y
Starts a sash movement operation. index is the sash to move, and x and y are widget-relative
screen coordinates.
$w sash place index x y
Places the indicated sash at the given coordinates.
Table 28-2 summarizes the panedwindow options for managed widgets. These are set when adding a widget to the paned window with the
add
operation or afterwards with paneconfigure operation. The current settings are returned by thepanecget operation.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 28-2. Panedwindow managed widget options
-after win
Inserts the widget after the specifiedwin.
-before win
Inserts the widget before the specified win.
-height
size
Height of the widget, including its border, in screen units. The actual widget height may vary based on -sticky settings,
-minsize
size
Minimum widget size of the widget in the paned dimension, specified in screen units.
-padx size
External widget padding in the X direction, in screen units.
-pady size
External widget padding in the Y direction, in screen units.
-sticky
how
Positions the widget next to any combination of north (n), south (s), east (w), and west (e) sides of the pane. Use {} for center.
-width size
Width of the widget, including its border, in screen units. The actual widget width may vary based on -sticky settings,
panedwindow resizing, and sash movement.
If opposing directions are specified (e.g., ns), the widget stretches to fill in those directions. Default:nsew.
panedwindow resizing, and sash movement.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Panedwindow Attributes
Table 28-3 lists the panedwindow widget attributes. The table uses the resource name for the attribute, which has capitals at internal word
boundaries. In Tcl commands these options are specified with a dash and all lowercase.
Table 28-3. Panedwindow attributes
background
Background color (also bg).
borderWidth
Extra space around the edge of the widget, in screen units.
cursor
Cursor to display when mouse is over the widget.
handlePad
When sash handles are drawn, the distance in screen units from the top or left end of the sash (depending on the
orientation) at which to draw the handle.
handleSize
The size of a sash handle, in screen units. Handles are always drawn as squares.
height
Height of the widget in screen units.
opaqueResize
Boolean. True indicates the panes should resize as a sash is moved. False (default) indicates resizing is deferred until
the sash is placed.
orient
horizontal or vertical
relief
flat, sunken, raised, groove, solid, or ridge.
sashCursor
Cursor to display over a sash. Defaults to a double-sided arrow.
sashPad
Padding on both sides of a sash.
sashRelief
Relief style for sashes.flat, sunken, raised (default), groove, solid, or ridge.
sashWidth
Width of each sash, in screen units.
showHandle
Boolean, whether or not to show the sash handles. Defaults to False on Windows, and True on other platforms.
width
Width of the widget in screen units.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 29. Binding Commands to Events
This chapter introduces the event binding mechanism in Tk. Bindings associate a Tcl command with an event like a mouse click or a key
stroke. There are also facilities to define virtual events like <<Cut>> and <<Paste>> that are associated with different keystrokes on different
platforms. Tcl commands discussed are: bind, bindtags, and event.
Bindings associate a Tcl command with a sequence of events from the window system. Events include key press, key release, button press,
button release, mouse entering a window, mouse leaving, window changing size, window open, window close, focus in, focus out, and widget
destroyed. The bindings are defined on binding tags, and each widget is associated with an ordered set of binding tags. The binding tags
provide a level of indirection between bindings and widgets that creates a flexible and powerful system.
Virtual events are used to support a different look and feel on different platforms. A virtual event is a higher-level name, like
<<Copy>>, for a
lower-level event name like <Control-c> or <Key-F6>. A virtual event hides the different keystrokes used on different platforms for the same
logical operation. Tk defines a few virtual events, and applications can define their own.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
[ Team LiB ]
The bind Command
The bind command creates event bindings, and it returns information about current bindings. The general form of the command is:
bind bindingTag ?eventSequence? ?command?
If all arguments are present, a binding from eventSequence to command is defined for bindingTag. The bindingTag is typically a widget class
name (e.g., Button) or a widget instance name (e.g., .buttons.foo). Binding tags are described in more detail later. Called with a single
argument, a binding tag, bind returns the events for which there are command bindings:
bind Menubutton
=> <Key-Return> <Key-space> <ButtonRelease-1>
<B1-Motion> <Motion> <Button-1> <Leave> <Enter>
The events in this example are keystroke and mouse events. <Button-1> is the event generated when the user presses the first, or left-hand,
mouse button. <B1-Motion> is generated when the user moves the mouse while holding down the first mouse button. The<Key-space> event
occurs when the user presses the space bar. The surrounding angle brackets delimit a single event, and you can define bindings for a
sequence of events. The event syntax is described on page 439, and event sequences are described on page 445.
If bind is given a binding tag and an event sequence, it returns the Tcl command bound to that event sequence:
bind Menubutton <B1-Motion>
=> tk::MbMotion %W down %X %Y
The Tcl commands in event bindings support an additional syntax for event keywords. These keywords begin with a percent sign and have
one more character that identifies some attribute of the event. The keywords are substituted with event-specific data before the Tcl command
is evaluated. For example, %W is replaced with the widget's pathname. The %X and %Y keywords are replaced with the coordinates of the
event relative to the screen. The %x and %y keywords are replaced with the coordinates of the event relative to the widget. Theevent
keywords are summarized on page 448.
The % substitutions are performed throughout the entire command bound to an event, without regard to other quoting schemes. You must use
%% to obtain a single percent sign. For this reason you should make your binding commands short, adding a new procedure if necessary (e.g.,
tk::MbMotion), instead of littering percent signs throughout your code.
A new binding is created by specifying a binding tag, an event sequence, and a command:
bind Menubutton <B1-Motion> {tk::MbMotion %W down %X %Y}
If the first character of the binding command is +, the command (without the +) is added to the commands, if any, for that event and binding
tag:
bind bindingTag event {+ command args}
To delete a binding for an event, bind the event to the null string:
bind bindingTag event {}
Bindings execute in the global scope.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
When a binding is triggered, the command is evaluated at the global scope. A very common mistake is to confuse the scope that is active
when the bind command creates a binding, and the scope that is active when the binding is triggered. The same problem crops up with the
commands associated with buttons, and it is discussed in more detail at the beginning of Chapter 30.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
The bindtags Command
A binding tag groups related bindings, and each widget is associated with an ordered set of binding tags. The level of indirection between
widgets and bindings lets you group functionality on binding tags and compose widget behavior from different binding tags.
For example, the all binding tag has bindings on <Tab> that change focus among widgets. TheText binding tag has bindings on keystrokes
that insert and edit text. Only text widgets use the Text binding tag, but all widgets share theall binding tag. You can introduce new binding
tags and change the association of widgets to binding tags dynamically. The result is a powerful and flexible way to manage bindings.
The bindtags command sets or queries the binding tags for a widget. The general form of thebindtags command is:
bindtags widget ?tagList?
The following command returns the binding tags for text widget .t:
bindtags .t
=> .t Text . all
You can change the binding tags and their order. The tagList argument to bindtags must be a proper Tcl list. The following command reorders
the binding tags for .t and eliminates the . binding tag:
bindtags .t [list all Text .t]
By default, all the Tk widgets, except a toplevel, have four binding tags in the following order:
The widget's Tk pathname (e.g., .t). Use this binding tag to provide special behavior to a particular widget. There are no bindings
on this bindtag by default.
The widget's class (e.g., Text). The class for a widget is derived from the name of the command that creates it. A button widget
has the class Button, a text has the classText, and so on. The Tk widgets define their default behavior with bindings on their class.
The Tk pathname of the widget's toplevel window (e.g., .). This is redundant in the case of a toplevel widget, so it is not used twice.
There are no bindings on this bindtag by default. The bindings on a toplevel window can be used in dialog boxes to handle
keyboard accelerators.
The global binding tag all. The default bindings on all are used to change focus among widgets. They are described on page 604.
When there is more than one binding tag on a widget, then one binding from each binding tag can match an event. The bindings are
processed in the order of the binding tags. By default, the most specific binding tag comes first, and the most general binding tag comes last.
Example 29-1 has two frame widgets that have the following behavior. When the mouse enters them, they turn red. They turn white when the
mouse leaves. When the user types <Control-c>, the frame under the mouse is destroyed. One of the frames,.two, reports the coordinates of
mouse clicks.
Example 29-1 Bindings on different binding tags
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
frame .one -width 30 -height 30
frame .two -width 30 -height 30
bind Frame <Enter> {%W config -bg red}
bind Frame <Leave> {%W config -bg white}
bind .two <Button> {puts "Button %b at %x %y"}
pack .one .two -side left
bind all <Control-c> {destroy %W}
bind all <Enter> {focus %W}
The Frame class has a binding on <Enter> and <Leave> that changes a frame's background color when the mouse moves in and out of the
window. This binding is shared by all the frames. There is also a binding on all for <Enter> that sets the keyboard focus. Both bindings will
trigger when the mouse enters a frame.
Focus and Key Events
The binding on <Control-c> is shared by all widgets. The binding destroys the target widget. Because this is a keystroke, it is important to get
the keyboard focus directed at the proper widget. By default, focus is on the main window, and destroying it terminates the entire application.
The global binding for <Enter> gives focus to a widget when you move the mouse over the widget. In this example, moving the mouse into a
widget and then typing <Control-c> destroys the widget. Bind thefocus command to <Button> instead of <Enter> if you prefer a click-to-type
focus model. Focus is described in Chapter 39.
Using break and continue in Bindings
The break and continue commands control the progression through the set of binding tags. Thebreak command stops the current binding and
suppresses the bindings from any remaining tags in the binding set order. The continue command in a binding stops the current binding and
continues with the command from the next binding tag.
For example, the Entry binding tag has bindings that insert and edit text in a one-line entry widget. You can put a binding on
<Return> that
executes a Tcl command using the value of the widget. The following example runs Some Command before the \r character is added to the
entry widget. The binding is on the name of the widget, which is first in the set of binding tags, so the break suppresses the Entry binding that
inserts the character:
bind .entry <Return> {Some Command ; break}
Note that you cannot use the break or continue commands inside a procedure that is called by the binding. This is because the procedure
mechanism will not propagate the break or continue signal. Instead, you could use the-code option to return, which is described on page 86:
return -code break
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Defining New Binding Tags
You introduce new binding tags just by using them in a bind or bindtags command. Binding tags are useful for grouping bindings into different
sets, such as specialized bindings for different modes of an editor. One way to emulate the vi editor, for example, is to use two bind tags, one
for insert mode and one for command mode. The user types i to enter insert mode, and they type<Escape> to enter command mode:
bindtags $t [list ViInsert Text $t all]
bind ViInsert <Escape> {bindtags %W {ViCmd %W all}}
bind ViCmd <Key-i> {bindtags %W {ViInsert Text %W all}}
The Text class bindings are used in insert mode. The command to put the widget into command mode is put on a new binding tag,
ViInsert,
instead of changing the default Text bindings. The bindtag command changes the mode by changing the set of binding tags for the widget.
The %W is replaced with the name of the widget, which is the same as$t in this example. Of course, you need to define many more bindings
to fully implement all the vi commands.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Event Syntax
The bind command uses the following syntax to describe events:
<modifier-modifier-type-detail>
<<Event>>
The first form is for physical events like keystrokes and mouse motion. The second form is for virtual events like Cut and Paste, which
correspond to different physical events on different platforms. Physical events are described in this section. Virtual events are described in
more detail on page 446.
The primary part of the description is the type, (e.g., Button or Motion). The detail is used in some events to identify keys or buttons, (.e.g.,
Key-a or Button-1). A modifier is another key or button that is already pressed when the event occurs, (e.g.,Control-Key-a or B2-Motion). There
can be multiple modifiers (e.g., Control-Shift-x). The < and > delimit a single event.
Table 29-1 lists all physical event types. When two event types are listed together (e.g.,ButtonPress and Button) they are equivalent.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 29-1. Event types
Activate
The application has been activated. (Macintosh)
ButtonPress, Button
A button is pressed (down).
ButtonRelease
A button is released (up).
Circulate
The stacking order of the window changed.
CirculateRequest
An application request to change its window stacking order. (Used by window managers.)
Colormap
The color map has changed.
Configure
The window changed size, position, border, or stacking order.
ConfigureRequest
An application request to change its window configuration. (Used by window managers.)
Create
An application request to create a window. (Used by window managers.)
Deactivate
The application has been deactivated. (Macintosh)
Destroy
The window has been destroyed.
Enter
The mouse has entered the window.
Expose
The window has been exposed.
FocusIn
The window has received focus.
FocusOut
The window has lost focus.
Gravity
The window has moved because of a change in size of its parent window.
KeyPress, Key
A key is pressed (down).
KeyRelease
A key is released (up).
Leave
The mouse is leaving the window.
Map
The window has been mapped (opened).
MapRequest
An application request to map a window. (Used by window managers.)
Motion
The mouse is moving in the window.
MouseWheel
The scrolling mouse wheel has moved.
Property
A property on the window has been changed or deleted.
Reparent
A window has been reparented.
ResizeRequest
An application request to resize window. (Used by window managers.)
Unmap
The window has been unmapped (iconified).
Visibility
The window has changed visibility.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Keyboard Events
The KeyPress type is distinguished from KeyRelease so that you can have different bindings for each of these events.KeyPress can be
abbreviated Key, and Key can be left off altogether if a detail is given to indicate what key. Finally, as a special case for
KeyPress events, the
angle brackets can also be left out. The following are all equivalent event specifications:
<KeyPress-a>
<Key-a>
<a>
a
The detail for a key is also known as the keysym, which refers to the graphic printed on the key of the keyboard. For punctuation and
non-printing characters, special keysyms are defined. Case is significant in keysyms, but unfortunately there is no consistent scheme. In
particular BackSpace has a capital B and a capital S. Commonly encountered keysyms include:Return, Escape, BackSpace, Tab, Up, Down,
Left, Right, comma, period, dollar, asciicircum, numbersign, exclam. Starting in Tk 8.3.2, the online documentation includes a newkeysym
reference page that documents all standard keysyms.
Finding out what keysyms are generated by your keyboard.
There are times when you do not know what keysym is generated by a special key on your keyboard. The keysyms are defined by the
window system implementation, and on UNIX systems they are affected by a dynamic keyboard map, the X modmap. You may find the next
binding useful to determine just what the keysym for a particular key is on your system:
bind $w <KeyPress> {puts stdout {%%K=%K %%A=%A}}
The %K keyword is replaced with the keysym from the event. The %A is replaced with the printing character that results from the event and
any modifiers like Shift. The %% is replaced with a single percent sign. Note that these substitutions occur in spite of the curly braces used for
grouping. If the user types a capital Q, there are twoKeyPress events, one for the Shift key, and one for theq key. The output is:
%K=Shift_R %A={}
%K=Q %A=Q
The Shift_R keysym indicates the right-hand shift key was pressed. The%A keyword is replaced with {} when modifier keys are pressed. You
can check for this in <KeyPress> bindings to avoid doing anything if only a modifier key is pressed. On Macintosh, there is no event at all
when the modifier keys are pressed. The following can be used with a text widget. The double quotes are necessary to force a string
comparison:
bind $w <KeyPress> {
if {"%A" != "{}"} {%W insert insert %A}
}
Mouse Events
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Button events also distinguish between ButtonPress, (or Button), and ButtonRelease. Button can be left off if a detail specifies a button by
number. The following are equivalent:
<ButtonPress-1>
<Button-1>
<1>
Note: The event <1> implies a ButtonPress event, while the event 1 implies a KeyPress event. To avoid confusion, always specify theKey or
Button type.
The mouse is tracked by binding to the Enter, Leave, and Motion events. Enter and Leave are triggered when the mouse comes into and exits
out of the widget, respectively. A Motion event is generated when the mouse moves within a widget.
The coordinates of the mouse event are represented by the %x and %y keywords in the binding command. The coordinates are
widget-relative, with the origin at the upper-left hand corner of a widget's window. The keywords %X and %Y represent the coordinates
relative to the screen:
bind $w <Enter> {puts stdout "Entered %W at %x %y"}
bind $w <Leave> {puts stdout "Left %W at %x %y"}
bind $w <Motion> {puts stdout "%W %x %y"}
A mouse drag event is a Motion event that occurs when the user holds down a mouse button. In this case the mouse button is modifier,
a
which
is discussed in more detail on page 443. The binding looks like this:
bind $w <B1-Motion> {puts stdout "%W %x %y"}
Other Events
The <Map> and <Unmap> events are generated when a window is opened and closed, or when a widget is packed or unpacked by its
geometry manager.
The <Activate> and <Deactivate> events are generated when an application is activated by the operating system. This applies to Macintosh
systems, and it occurs when the user clicks in the application window.
The <Configure> event is generated when the window changes size. A canvas that computes its display based on its size can bind a
redisplay procedure to the <Configure> event, for example. The <Configure> event can be caused by interactive resizing. It can also be
caused by a configure widget command that changes the size of the widget. You should not reconfigure a widget's size while processing a
<Configure> event to avoid an indefinite sequence of these events.
The <Destroy> event is generated when a widget is destroyed. You can intercept requests to delete windows, too. See also the description of
the wm command on page 657.
The <MouseWheel> event is generated on Windows by the small scrolling wheel built into the Microsoft Mouse. It reports a delta value using
the %D keyword. Currently the delta is an integer multiple of 120, where positive values indicate a scroll up, and negative values indicate a
scroll down. Note that most Unix systems don't report <MouseWheel> events, but some do report mousewheel movement via
<ButtonPress-4> and <ButtonPress-5> events.
Chapter 39 presents some examples that use the<FocusIn> and <FocusOut> events. The remaining events in Table 29-1 have to do with dark
corners of the X protocol, and they are seldom used. More information can be found on these events in the Event Reference section of the
Xlib Reference Manual (Adrian Nye, O'Reilly & Associates, Inc., 1992).
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Bindings on Top-level Windows
Bindings on toplevels are shared by widgets they contain.
Be careful when binding events to toplevel windows because their name is used as a binding tag on all the widgets contained in them. For
example, the following binding fires when the user destroys the main window, which means the application is about to exit:
bind . <Destroy> {puts "goodbye"}
Unfortunately, all widgets inside the main window are destroyed as a side effect, and they all share the name of their toplevel widget as a
binding tag. So this binding fires when every widget inside the main window is destroyed. Typically you only want to do something one time.
The following binding checks the identity of the widget before doing anything:
bind . <Destroy> {if {"%W" == "."} {puts "goodbye"}}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
[ Team LiB ]
Modifiers
A modifier indicates that another key or button is being held down at the time of the event. Typical modifiers are the Shift and Control keys.
The mouse buttons can also be used as modifiers. If an event does not specify any modifiers, the presence of a modifier key is ignored by
the event dispatcher. However, if there are two possible matching events, the more accurate match will be used. For example, consider these
three bindings:
bind $w <KeyPress> {puts "key=%A"}
bind $w <Key-c> {puts "just a c"}
bind $w <Control-Key-c> {exit}
The last event is more specific than the others. Its binding will be triggered when the user types c with the Control key held down. If the user
types c with the Meta key held down, the second binding will be triggered. TheMeta key is ignored because it does not match any binding. If
the user types something other than a c, the first binding is triggered. If the user presses theShift key, then the keysym that is generated isC,
not c, so the last two events do not match.
There are eight possible modifier keys. The Control, Shift, and Lock modifiers are found on nearly all keyboards. TheMeta and Alt modifiers
tend to vary from system to system, and they may not be defined at all. They are commonly mapped to be the same as Mod1 or Mod2, and
Tk will try to determine how the mappings are set. The Macintosh has a Command modifier that corresponds to the clover-leaf or apple key.
The remaining modifiers, Mod3 through Mod5, are sometimes mapped to other special keys. In OpenLook environments, for example, the
Paste function key is also mapped to the Mod5 modifier.
The button modifiers, B1 through B5, are most commonly used with theMotion event to distinguish different mouse dragging operations. For
example, <B1-Motion> is the event generated when the user drags the mouse with the first mouse button held down.
Double-click warning.
The Double, Triple, and Quadruple events match on repetitions of an event within a short period of time. These are commonly used with
mouse events. Be careful: The binding for the regular press event will match on the first press of the Double. Then the command bound to the
Double event will match on the second press. Similarly, aDouble event will match on the first two presses of aTriple event, and so on. Verify
this by trying out the following bindings:
bind . <1> {puts stdout 1}
bind . <Double-1> {puts stdout 2}
bind . <Triple-1> {puts stdout 3}
If you click the first mouse button several times quickly, you will see a 1, 2, and then a few3's output. Your bindings must take into
consideration that more than one binding might match a Double, Triple, or Quadruple event. This effect is compatible with an interface that
selects an object with the first click, and then operates on the selected object with a Double event. In an editor, character, word, line, and
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com
.
to register it. Thanks
[*]
paragraph selection on a single, double, triple, and quadruple click, respectively, is a good example.
[*]
If you really want to disable this, you can experiment with usingafter to postpone processing of one event. The time
constant in the bind implementation of <Double> is 500 milliseconds. At the single-click event, schedule its action to
occur after 600 milliseconds, and verify at that time that the <Double> event has not occurred.
Table 29-2 summarizes the modifiers.
Table 29-2. Event modifiers
Control
The control key.
Shift
The shift key.
Lock
The caps-lock key.
Command
The command key. (Macintosh)
Meta, M
Defined to be what ever modifier (M1 through M5) is mapped to the Meta_L and Meta_R keysyms.
Alt
Defined to be the modifier mapped to Alt_L and Alt_R.
Mod1, M1
The first modifier.
Mod2, M2, Alt
The second modifier.
Mod3, M3
Another modifier.
Mod4, M4
Another modifier.
Mod5, M5
Another modifier.
Button1, B1
The first mouse button (left).
Button2, B2
The second mouse button (middle).
Button3, B3
The third mouse button (right).
Button4, B4
The fourth mouse button.
Button5, B5
The fifth mouse button.
Double
Matches double-press event.
Triple
Matches triple-press event.
Quadruple
Matches quadruple-press event.
Any
Matches any combination of modifiers. (Before Tk 4.0)
The UNIX xmodmap program returns the current mappings from keys to these modifiers. The first column of its output lists the modifier. The
rest of each line identifies the keysym(s) and low-level keycodes that are mapped to each modifier. The xmodmap program can also be used
to change mappings. The following example shows the mappings on my system. Your setup may be different.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Example 29-2 Output from the UNIX xmodmap program
xmodmap: up to 3 keys per modifier,
(keycodes in parentheses):
shift Shift_L (0x6a), Shift_R (0x75)
lock Caps_Lock (0x7e)
control Control_L (0x53)
mod1 Meta_L (0x7f), Meta_R (0x81)
mod2 Mode_switch (0x14)
mod3 Num_Lock (0x69)
mod4 Alt_L (0x1a)
mod5 F13 (0x20), F18 (0x50), F20 (0x68)
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Event Sequences
The bind command accepts a sequence of events in a specification, and most commonly this is a sequence of key events. In the following
examples, the Key events are abbreviated to just the character detail, and soabc is a sequence of three Key events:
bind . a {puts stdout A}
bind . abc {puts stdout C}
With these bindings in effect, both bindings are executed when the user types abc. The binding for a is executed when a is pressed, even
though this event is also part of a longer sequence. This is similar to the behavior with Double and Triple event modifiers. For this reason you
must be careful when binding sequences. You can use break in the binding for the prefix to ensure that it does not do anything:
bindtags $w [list $w Text [winfo toplevel $w] all]
bind $w <Control-x> break
bind $w <Control-x><Control-s> {Save ; break}
bind $w <Control-x><Control-c> {Quit ; break}
The break ensures that the defaultText binding that inserts characters does not trigger. This trick is embodied byBindSequence in the next
example. If a sequence is detected, then a break binding is added for the prefix. The procedure also supports theemacs convention that
<Meta-x> is equivalent to <Escape>x. This convention arose because Meta is not that standard across keyboards. There is no meta key at all
on Windows and Macintosh keyboards. The regexp command is used to pick out the detail from the<Meta> event.
Example 29-3 Emacs-like binding convention for Meta and Escape
proc BindSequence { w seq cmd } {
bind $w $seq $cmd
# Double-bind Meta-key and Escape-key
if [regexp {<Meta-(.*)>} $seq match letter] {
bind $w <Escape><$letter> $cmd
}
# Make leading keystroke harmless
if [regexp {(<.+>)<.+>} $seq match prefix] {
bind $w $prefix break
}
}
The use of break and continue in bindings is not supported in Tk 3.6 and earlier. This is because only a single binding tag can match an
event. To make a prefix of a sequence harmless in Tk 3.6, bind a space to it:
bind $w $prefix { }
This installs a binding for the widget, which suppresses the class binding in Tk 3.6. The space is different than a null string, {}. Binding to a
null string deletes the current binding instead of replacing it with a harmless one.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Virtual Events
A virtual event corresponds to one or more event sequences. When any of the event sequences occurs, then the virtual event occurs.
Example 29-4 shows the cut, copy, and paste virtual events for each platform:
Example 29-4 Virtual events for cut, copy, and paste
switch $tcl_platform(platform) {
"unix" {
event add <<Cut>> <Control-Key-x> <Key-F20>
event add <<Copy>> <Control-Key-c> <Key-F16>
event add <<Paste>> <Control-Key-v> <Key-F18>
}
"windows" {
event add <<Cut>> <Control-Key-x> <Shift-Key-Delete>
event add <<Copy>> <Control-Key-c> <Control-Key-Insert>
event add <<Paste>> <Control-Key-v> <Shift-Key-Insert>
}
"macintosh" {
event add <<Cut>> <Control-Key-x> <Key-F2>
event add <<Copy>> <Control-Key-c> <Key-F3>
event add <<Paste>> <Control-Key-v> <Key-F4>
}
}
You can define more than one physical event that maps to the same virtual event:
event add <<Cancel>> <Control-c> <Escape> <Command-period>
With this definition any of the physical events will trigger a <<Cancel>>. This would be convenient if the same user commonly used your
application on different platforms. However, it is also possible that the physical bindings on different platforms overlap in conflicting ways.
By default, virtual event definitions add to existing definitions for the same virtual event. The previous command could be replaced with these
three:
event add <<Cancel>> <Control-c>
event add <<Cancel>> <Escape>
event add <<Cancel>> <Command-period>
Several widgets use virtual events as a notification mechanism. They generate virtual events in response to various conditions so that you
can create bindings to respond to those conditions. For example, the listbox widget generates a <<ListboxSelect>> virtual event whenever
the listbox selection changes. The easiest way to respond to changes to the listbox selection is to bind to this virtual event, for example:
bind .lbox <<ListboxSelect>> {ListboxChanged %W}
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Generating Events
Your application can use the event generate command to programmatically generate events, in essence emulating user interaction. You can
generate either standard windowing events or virtual events. However, you can generate events only for the current application; you can't
send events to other applications running on your system. (In other words, you can't use the event generate command to have your
application control another application.)
The first argument to event generate is the target widget for the event. You may provide either the path name of the widget, or the window
identifier (such as returned by winfo id) as long as it is for a window in the current application.
The second argument is an event specification, using the same syntax as for creating event bindings. (See "Event Syntax" on page 439.)
However, you can't generate an event sequence (such as <KeyPress-Escape><KeyPress-a>), only single events.
As an example, the following command delivers aButtonPress-3 event to a widget:
event generate .b <ButtonPress-3>
A widget must have focus to receive key events.
Remember that a widget must have keyboard focus to receive KeyPress or KeyRelease events. You can use the focus command to assign
keyboard focus to a widget:
focus .e1
event generate .e1 <KeyPress-a>
The event generate command also accepts options to specify additional attributes of the event, such as the x and y mouse position.Table 29-4
lists the event generate options. Of note is the-warp option, added in Tk 8.3. If you provide a-warp value of True, then the mouse pointer
moves to the x and y coordinates of the generated event; otherwise, the mouse pointer remains at its current location. For example, the
following commands moves the mouse pointer to the point 10,20 relative to the top-left corner of the main window:
event generate . <Motion> -x 10 -y 20 -warp 1
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Event Summary
Event Command Syntax
The event command is summarized in Table 29-3.
Table 29-3. The event command
event add virt phys1 phy2 ...
Adds a mapping from one or more physical events to virtual eventvirt.
event delete virt
Deletes virtual event virt.
event info
Returns the defined virtual events.
event info virt
Returns the physical events that map tovirt.
event generate win event ?opt val? ...
Generates event for window win. The options are listed in Table 29-4.
Event Keywords
Table 29-4 lists the percent keywords and the corresponding option to the event generate command. Remember that keyword substitutions
occur throughout the command, regardless of other Tcl quoting conventions. Keep your binding commands short, introducing procedures if
needed. For the details about various event fields, consult the Xlib Reference Manual (O'Reilly & Associates, Inc.). The string values for the
keyword substitutions are listed after a short description of the keyword. If no string values are listed, the keyword has an integer value like a
coordinate or a window ID.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Thanks
Table 29-4. A summary of the event keywords
%%
Use this to get a single percent sign. All events.
%#
-serial num
The serial number for the event. All events.
%a
-above win
The above field from the event. Configure event.
%b
-button num
Button number. Events: ButtonPress and ButtonRelease.
%c
-count num
The count field. Events: Expose and Map.
%d
-detail value
The detail field. Values: NotifyAncestor, NotifyNonlinearVirtual, NotifyDetailNone, NotifyPointer, NotifyInferior,
NotifyPointerRoot, NotifyNonlinear, or NotifyVirtual.
Events: Enter, Leave, FocusIn, and FocusOut.
%f
-focus boolean
The focus field (0 or 1). Events: Enter and Leave.
%h
-height num
The height field. Events: Configure and Expose.
%i
The window field from the event, represented as a hexadecimal integer. All events.
%k
-keycode num
The keycode field. Events: KeyPress and KeyRelease.
%m
-mode value
The mode field. Values: NotifyNormal, NotifyGrab, NotifyUngrab, or NotifyWhileGrabbed. Events: Enter, Leave,
FocusIn, and FocusOut.
%o
-override
The override_redirect field. Events: Map, Reparent, and Configure.
boolean
%p
-place value
The place field. Values: PlaceOnTop, PlaceOnBottom. Circulate event.
%s
-state value
The state field. A decimal string for events: ButtonPress, ButtonRelease, Enter, Leave, KeyPress, KeyRelease,
and Motion.
Values for the Visibility event: VisibilityUnobscured, VisibilityPartiallyObscured, or VisibilityFullyObscured.
%t
-time num
%v
The time field. All events.
The value_mask field. Configure event.
%w
-width num
The width field. Events: Configure and Expose.
%x
-x pixel
The X coordinate, widget relative. Mouse events.
%y
-y pixel
The Y coordinate, widget relative. Mouse events.
%A
The printing character from the event, or {}.
Events: KeyPress and KeyRelease.
%B
-borderwidth
The border width. Configure event.
num
%D
-delta value
The delta value. MouseWheel event.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
%E
-sendevent
The send_event field. All events.
bool
%K
-keysym
The keysym from the event. Events: KeyPress and KeyRelease.
symbol
%N
The keysym as a decimal number. Events: KeyPress and KeyRelease.
%P
The atom name for the property being changed or deleted.Property event.
%R
-root win
The root window ID. All events.
%S
-subwindow
The subwindow ID. All events.
win
%T
The type field. All events.
%W
The Tk pathname of the widget receiving the event. All events.
%X
-rootx pixel
The x_root field. Relative to the (virtual) root window. Events: ButtonPress, ButtonRelease, KeyPress,
KeyRelease, and Motion.
%Y
-rooty pixel
The y_root field. Relative to the (virtual) root window. Events: ButtonPress, ButtonRelease, KeyPress,
KeyRelease, and Motion.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Part IV: Tk Widgets
Part IV describes the Tk widgets. These are the components you use to build up your graphical user interface. Tk
widgets are simple to use, so you can rapidly develop your interface. At the same time, they have sophisticated
features that you can use to fine-tune your interface in response to user feedback.
Chapter 30 describes buttons and menus. Tk 8.0 adds native look and feel to these widgets, so a single script will look
different depending on the platform it is running on.
Associated with the widgets is a resource database that stores settings like colors and fonts. Chapter 31 describes the
resource database and generalizes it to store button and menu configurations.
Chapter 32 describes a few simple widgets. The frame, labelframe, and toplevel are containers for other widgets. The
label displays a text string. The message formats a long text string onto multiple lines. The scale represents a numeric
value. The bell command rings the terminal bell.
Chapter 33 describes scrollbars, which can be attached in a general way to other widgets.
Chapter 34 describes entry widgets, which provide one line of editable text and spinboxes, which allow users to select
from multiple values by "spinning" through selections.
Chapter 35 describes the listbox widget that displays several lines of text. The lines are manipulated as units.
Chapter 36 describes the general-purpose text widget. It can display multiple fonts and have binding tags on ranges of
text.
Chapter 37 describes the canvas widget. The canvas manages objects like lines, boxes, images, arcs, and text labels.
You can have binding tags on these objects and classes of objects.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Chapter 30. Buttons and Menus
Buttons and menus are the primary way that applications expose functions to users. This chapter describes how to create and manipulate
buttons and menus.
A button widget is associated with a Tcl command that invokes an action in the application. The checkbutton and radiobutton widgets affect
an application indirectly by controlling a Tcl variable. A menu elaborates on this concept by organizing button-like items into related sets,
including cascaded menus. The menubutton widget is a special kind of button that displays a menu when you click on it.
Tk 8.0 provides a cross-platform menu bar facility. The menu bar is really just a menu that is displayed horizontally along the top of your
application's main window. On the Macintosh, the menu bar appears at the top of the screen. You define the menu bar the same on all
platforms. Tk 8.0 also uses native button and menu widgets on the Windows and Macintosh platforms. This contributes to a native look and
feel for your application. In earlier versions, Tk displayed the widgets identically on all platforms.
Associating a command to a button is usually quite simple, as illustrated by the Tk "Hello, World!" example:
button .hello -command {puts stdout "Hello, World!"}
This chapter describes a few useful techniques for setting up the commands in more general cases. If you use variables inside button
commands, you have to understand the scoping rules that apply. This is the first topic of the chapter. Once you get scoping figured out, then
the other aspects of buttons and menus are quite straightforward.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Button Commands and Scope Issues
Perhaps the trickiest issue with button commands has to do with variable scoping. A button command is executed at the global scope, which
is outside of any procedure. If you create a button while inside a procedure, then the button command executes in a different scope later. The
commands used in event bindings also execute later at the global scope.
I think of this as the "now" (i.e., button definition) and "later" (i.e., button use) scope problem. For example, you may want to use the values of
some variables when you define a button command but use the value of other variables when the button command is used. When these two
contexts are mixed, it can be confusing. The next example illustrates the problem. The button's command involves two variables: x and val.
The global variable x is needed later, when the button's command executes. The local variableval is needed now, in order to define the
command. Example 30-1 shows this awkward mixture of scopes:
Example 30-1 A troublesome button command
proc Trouble {args} {
set b 0
# Display the value of x, a global variable
label .label -textvariable x
set f [frame .buttons -borderwidth 10]
# Create buttons that multiply x by their value
foreach val $args {
button $f.$b -text $val \
-command "set x \[expr \$x * $val\]"
pack $f.$b -side left
incr b
}
pack .label $f
}
set x 1
Trouble -1 4 7 36
The example uses a label widget to display the current value of x. The textvariable attribute is used so that the label displays the current value
of the variable, which is always a global variable. It is not necessary to have a global command inside Trouble because the value of x is not
used there. The button's command is executed later at the global scope.
The definition of the button's command is ugly, though. The value of the loop variable val is needed when the button is defined, but the rest of
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
the substitutions need to be deferred until later. The variable substitution of $x and the command substitution ofexpr are suppressed by
quoting with backslashes:
set x \[expr \$x * $val\]
In contrast, the following command assigns a constant expression to x each time the button is clicked, and it depends on the current value of
x, which is not defined the first time through the loop. Clearly, this is incorrect:
button $f.$b -text $val \
-command "set x [expr $x * $val]"
Another incorrect approach is to quote the whole command with braces. This defers too much, preventing the value of val from being used at
the correct time.
Use procedures for button commands.
The general technique for dealing with these sorts of scoping problems is to introduce Tcl procedures for use as the button commands.
Example 30-2 introduces a little procedure to encapsulate the expression:
Example 30-2 Fixing the troublesome situation
proc LessTrouble { args } {
set b 0
label .label -textvariable x
set f [frame .buttons -borderwidth 10]
foreach val $args {
button $f.$b -text $val \
-command "UpdateX $val"
pack $f.$b -side left
incr b
}
pack .label $f
}
proc UpdateX { val } {
global x
set x [expr $x * $val]
}
set x 1
LessTrouble -1 4 7 36
It may seem just like extra work to introduce the helper procedure, UpdateX. However, it makes the code clearer in two ways. First, you do
not have to struggle with backslashes to get the button command defined correctly. Second, the code is much clearer about the function of
the button. Its job is to update the global variable x.
You can generalize UpdateX to work on any variable by passing the name of the variable to update. Now it becomes much like theincr
command:
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
button $f.$b -text $val -command "Update x $val"
The definition of Update uses upvar, which is explained on page 91, to manipulate the named variable in the global scope:
proc Update {varname val} {
upvar #0 $varname x
set x [expr $x * $val]
}
Double quotes are used in the button command to allow $val to be substituted. Whenever you use quotes like this, you have to be aware of
the possible values for the substitutions. If you are not careful, the command you create may not be parsed correctly. The safest way to
generate the command is with list:
button $f.$b -text $val -command [list UpdateX $val]
Using list ensures that the command is a list of two elements,UpdateX and the value of val. This is important because UpdateX takes only a
single argument. If val contained white space, then the resulting command would be parsed into more words than you expected. Of course, in
this case we plan to always call LessTrouble with an integer value, which does not contain white space.
Example 30-3 provides a more straightforward application of procedures for button commands. In this case the advantage of the procedure
MaxLineLength is that it creates a scope for the local variables used during the button action. This ensures that the local variables do not
accidentally conflict with global variables used elsewhere in the program. There is also the standard advantage of a procedure, which is that
you may find another use for the action in another part of your program.
Example 30-3 A button associated with a Tcl procedure
proc MaxLineLength { file } {
set max 0
if [catch {open $file} in] {
return $in
}
foreach line [split [read $in] \n] {
set len [string length $line]
if {$len > $max} {
set max $len
}
}
return "Longest line is $max characters"
}
# Create an entry to accept the file name,
# a label to display the result
# and a button to invoke the action
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
. config -borderwidth 10
entry .e -width 30 -bg white -relief sunken
button .doit -text "Max Line Length" \
-command {.label config -text [MaxLineLength [.e get]]}
label .label -text "Enter file name"
pack .e .doit .label -side top -pady 5
The example is centered around the MaxLineLength procedure. This opens a file and loops over the lines finding the longest one. The file
open
is protected with catch in case the user enters a bogus file name. In that case, the procedure returns the error message from
open. Otherwise,
the procedure returns a message about the longest line in the file. The local variables in, max, and len are hidden inside the scope of the
procedure.
The user interface has three widgets: an entry for user input, the button, and a label to display the result. These are packed into a vertical
stack, and the main window is given a border. Obviously, this simple interface can be improved in several ways. There is no Quit button, for
example.
All the action happens in the button command:
.label config -text [MaxLineLength [.e get]]
Braces are used when defining the button command so that the command substitutions all happen when the button is clicked. The value of
the entry widget is obtained with .e get. This value is passed into MaxLineLength, and the result is configured as the text for the label. This
command is still a little complex for a button command. For example, suppose you wanted to invoke the same command when the user
pressed <Return> in the entry. You would end up repeating this command in the entry binding. It might be better to introduce a one-line
procedure to capture this action so that it is easy to bind the action to more than one user action. Here is how that might look:
proc Doit {} {
.label config -text [MaxLineLength [.e get]]
}
button .doit -text "Max Line Length" -command Doit
bind .e <Return> Doit
Chapter 29 describes the bind command in detail, Chapter 32 describes the label widget, andChapter 35 describes the entry widget.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Buttons Associated with Tcl Variables
The checkbutton and radiobutton widgets are associated with a global Tcl variable. When one of these buttons is clicked, a value is assigned
to the Tcl variable. In addition, if the variable is assigned a value elsewhere in the program, the appearance of the checkbutton or radiobutton
is updated to reflect the new value. A set of radiobuttons all share the same global variable. The set represents a choice among mutually
exclusive options. In contrast, each checkbutton has its own global variable.
The ShowChoices example uses a set of radiobuttons to display a set of mutually exclusive choices in a user interface. TheShowBooleans
example uses checkbutton widgets:
Example 30-4 Radiobuttons and checkbuttons
proc ShowChoices { parent varname args } {
set f [frame $parent.choices -borderwidth 5]
set b 0
foreach item $args {
radiobutton $f.$b -variable $varname \
-text $item -value $item
pack $f.$b -side left
incr b
}
pack $f -side top
}
proc ShowBooleans { parent args } {
set f [frame $parent.booleans -borderwidth 5]
set b 0
foreach item $args {
checkbutton $f.$b -text $item -variable $item
pack $f.$b -side left
incr b
}
pack $f -side top
}
set choice kiwi
ShowChoices {} choice apple orange peach kiwi strawberry
set Bold 1 ; set Italic 1
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
ShowBooleans {} Bold Italic Underline
The ShowChoices procedure takes as arguments the parent frame, the name of a variable, and a set of possible values for that variable. If
the parent frame is null, {}, then the interface is packed into the main window.ShowChoices creates a radiobutton for each value, and it puts
the value into the text of the button. It also has to specify the value to assign to the variable when the button is clicked because the default
value associated with a radiobutton is the empty string.
The ShowBooleans procedure is similar to ShowChoices. It takes a set of variable names as arguments, and it creates a checkbutton for each
variable. The default values for the variable associated with a checkbutton are zero and one, which is fine for this example. If you need
particular values, you can specify them with the -onvalue and -offvalue options.
Radiobuttons and checkbuttons can have commands associated with them, just like ordinary buttons. The command is invoked after the
associated Tcl variable has been updated. Remember that the Tcl variable associated with the button is defined in the global scope. For
example, you could log the changes to variables as shown in the next example.
Example 30-5 A command on a radiobutton or checkbutton
proc PrintByName { varname } {
upvar #0 $varname var
puts stdout "$varname = $var"
}
checkbutton $f.$b -text $item -variable $item \
-command [list PrintByName $item]
radiobutton $f.$b -variable $varname \
-text $item -value $item \
-command [list PrintByName $varname]
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Button Attributes
Table 30-1 lists the attributes for the button, checkbutton, menubutton, and radiobutton widgets. Unless otherwise indicated, the attributes
apply to all of these widget types. Chapters 40, 41, and 42 discuss many of these attributes in more detail. Some attributes are ignored on the
Windows and Macintosh platforms because they are not supported by the native button widgets.
The table uses the resource name for the attributes, which has capitals at internal word boundaries. In Tcl commands, the attributes are
specified with a dash and they are all lowercase. Compare:
option add *Menubutton.activeBackground: red
.mb configure -activebackground red
The first command defines a resource database entry that covers all menubuttons and gives them a red active background. This only affects
menubuttons created after the database entry is added. The second command changes an existing menubutton (.mb) to have a red active
background. Note the difference in capitalization of background in the two commands. Theresource database is introduced on page 372, and
Chapter 31 explains how to use the resource database in more detail.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 30-1. Resource names of attributes for all button widgets
activeBackground
Background color when the mouse is over the button.
activeForeground
Text color when the mouse is over the button.
anchor
Anchor point for positioning the text.
background
The normal background color.
bitmap
A bitmap to display instead of text.
borderWidth
Width of the border around the button.
command
Tcl command to invoke when button is clicked.
compound
Where the image or bitmap should be placed relative to the text: bottom, center, left, right, top or none (default). (Tk
8.4)
cursor
Cursor to display when mouse is over the widget.
default
active displays as a default button.normal and disabled display as normal button. See page 809 (Tk 8.0).
direction
up, down, left, right, active. Offset direction for posting menus.menubutton. (Tk 8.0).
disabledForeground
Foreground (text) color when button is disabled.
font
Font for the text.
foreground
Foreground (text) color. (Alsofg).
height
Height, in lines for text, or screen units for images.
highlightBackground
Focus highlight color when widget does not have focus.
highlightColor
Focus highlight color when widget has focus.
highlightThickness
Width of highlight border.
image
Image to display instead of text or bitmap.
indicatorOn
Boolean that controls if the indicator is displayed.
checkbutton, menubutton, and radiobutton.
justify
Text justification: center, left, or right.
menu
Menu posted when menubutton is clicked.
offRelief
Alternate relief style when the widget is deselected.checkbutton and radiobutton. (Tk 8.4)
offValue
Value for Tcl variable when checkbutton is not selected.
onValue
Value for Tcl variable when checkbutton is selected.
overRelief
Alternate relief style when mouse is over the widget.button, checkbutton, and radiobutton. (Tk 8.4)
padX
Extra space to the left and right of the button text.
padY
Extra space above and below the button text.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
relief
flat, sunken, raised, groove, solid or ridge.
repeatDelay
The number of milliseconds a button or key must be held down before it begins to auto-repeat. For button only. (Tk
8.4)
repeatInterval
The number of milliseconds between auto-repeats. Forbutton only. (Tk 8.4)
selectColor
Color for selector. checkbutton or radiobutton.
selectImage
Alternate graphic image for selector: checkbutton or radiobutton.
state
normal (enabled), disabled (deactivated), or active (when the mouse pointer is over the button).
takeFocus
Control focus changes from keyboard traversal.
text
Text to display in the button.
textVariable
Tcl variable that has the value of the text.
underline
Index of text character to underline.
value
Value for Tcl variable when radiobutton is selected.
variable
Tcl variable associated with the button: checkbutton or radiobutton.
width
Width in characters for text, or screen units for image. As of Tk 8.4, on Windows only, a negative value is treated as
a minimum width for button widgets only.
wrapLength
Maximum character length before text is wrapped, in screen units.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Button Operations
Table 30-2 summarizes the operations on button widgets. In the table,$w is a button, checkbutton, radiobutton, or menubutton, except when
noted. For the most part, these operations are used by the script libraries that implement the bindings for buttons. The cget and configure
operations are the most commonly used by applications.
Table 30-2. Button operations
$w cget option
Returns the value of the specified attribute.
$w configure ?option?
?value? ...
Queries or manipulates the configuration information for the widget.
$w deselect
Deselects the radiobutton or checkbutton. Set the radiobutton variable to the null string. Set the checkbutton
variable to the off value.
$w flash
Redisplays the button several times in alternate colors.
$w invoke
Invokes the command associated with the button.
$w select
Selects the radiobutton or checkbutton, setting the associated variable appropriately.
$w toggle
Toggles the state of the checkbutton, setting the associated variable appropriately.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Menus and Menubuttons
A menu presents a set of button-like menu entries to users. A menu entry is not a full fledged Tk widget. Instead, you create a menu widget
and then add entries to the menu as shown in the following examples. There are several kinds of menu entries:
Command entries are like buttons.
Check entries are like checkbuttons.
Radio entries are like radiobuttons.
Separator entries are used to visually set apart entries.
Cascade entries are used to post submenus.
Tear-off entries are used to detach a menu from its menu button so that it becomes a new top-level window.
A menubutton is a special kind of button that posts (i.e., displays) a menu when you press it. If you click on a menubutton, then the menu is
posted and remains posted until you click on a menu entry to select it, or click outside the menu to dismiss it. If you press and hold the
menubutton, then the menu is unposted when you release the mouse. If you release the mouse over the menu, it selects the menu entry that
was under the mouse.
You can have a command associated with a menubutton, too. The command is invoked before the menu is posted, which means you can
compute the menu contents when the user presses the menubutton.
Our first menu example creates a sampler of the different entry types:
Example 30-6 A menu sampler
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
menubutton .mb -text Sampler -menu .mb.menu
pack .mb -padx 10 -pady 10
set m [menu .mb.menu -tearoff 1]
$m add command -label Hello! -command {puts "Hello, World!"}
$m add check -label Boolean -variable foo \
-command {puts "foo = $foo"}
$m add separator
$m add cascade -label Fruit -menu $m.sub1
set m2 [menu $m.sub1 -tearoff 0]
$m2 add radio -label apple -variable fruit -value apple
$m2 add radio -label orange -variable fruit -value orange
$m2 add radio -label kiwi -variable fruit -value kiwi
The example creates a menubutton and two menus. The main menu .mb.menu is a child of the menubutton.mb. This relationship is
necessary so that the menu displays correctly when the menubutton is selected. Similarly, the cascaded submenu .mb.menu.sub1 is a child
of the main menu. The first menu entry is represented by the dashed line. This is a tear-off entry that, when selected, makes a copy of the
menu in a new top-level window. This is useful if the menu operations are invoked frequently. The -tearoff 0 argument is used when creating
the submenu to eliminate its tear-off entry.
The command, radio, and check entries are similar to the corresponding button types. The configuration options for menu entries are similar
to those for buttons. The main difference is that the text string in the menu entry is defined with the -label option, not -text. Table 30-6 gives
the complete set of options for menu entries.
The cascade menu entry is associated with another menu. It is distinguished by the small right arrow in the entry. When you select the entry,
the submenu is posted. It is possible to have several levels of cascaded menus. There is no limit to the number of levels, except that your
users will complain if you nest too many menus.
A Menu Bar
You can create a menu bar manually by packing several menubuttons into a frame. The default bindings on menubuttons are such that you
can drag your mouse over the menu bar and the different menus will display as you drag over their menubutton.
Tk 8.0 lets you create a menu bar as a horizontal menu that is associated with a top-level window. On Windows and UNIX the menu is
displayed along the top of the window. On Macintosh this menu replaces the main menu along the top of the screen when the window is
activated. The menu bar menu should have all cascade entries so that when you select an entry, another menu is displayed. This is illustrated
in Example 30-7. It defines variables that store the names of the menu widgets:
set $m [menu .menubar.m$m]
This creates a variable named File, Edit, and Help that store the names of the menu widgets. This trick is generalized on page 470 in a
package that hides the menu widget names.
Example 30-7 A menu bar in Tk 8.0
menu .menubar
# attach it to the main window
. config -menu .menubar
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
# Create more cascade menus
foreach m {File Edit Help} {
set $m [menu .menubar.m$m]
.menubar add cascade -label $m -menu .menubar.m$m
}
$File add command -label Quit -command exit
# add more menu items...
System Menus
The Tk 8.0 menu bar implementation can add entries to the Windows system menu, the Macintosh Apple menu, and the Help menu on all
platforms. This works by recognizing special names. For example, if the menu bar is .menubar, then the special names are.menubar.system,
.menubar.apple, and .menubar.help. The Help menu is right justified on all platforms. The Apple menu is normally used by applications for their
About... entry. The entries you add to the Apple menu are added to the top of the menu. The System menu appears in the Windows title bar
and has entries such as Close and Minimize.
Pop-Up Menus
A pop-up menu is not associated with a menubutton. Instead, it is posted in response to a keystroke or other event in the application. The
tk_popup command posts a pop-up menu:
tk_popup menu x y ?entry?
The last argument specifies the entry to activate when the menu is posted. It is an optional parameter that defaults to 1, which avoids the
tear-off entry in position zero. The menu is posted at the specified X and Y coordinates in its parent widget.
Option Menus
An option menu represents a choice with a set of radio entries, and it displays the current choice in the text of the menubutton. The
tk_optionMenu command creates a menubutton and a menu full of radio entries:
tk_optionMenu w varname firstValue ?value value ...?
The first argument is the pathname of the menubutton to create. The second is the variable name. The third is the initial value for the variable,
and the rest are the other choices for the value. The menubutton displays the current choice and a small symbol, the indicator, to indicate it is
an option menu.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register
. it. Than
Multicolumn Palette Menus
Tk 8.0 adds a -columnbreak menu entry attribute that puts the entry at the top of a new column. This is most useful when the menu consists
of several images that are arranged as a palette. Set the entry's image with the -image attribute. You can create checkbutton and radiobutton
entries that have images and no indicator by using the -hidemargin attribute. In this case, a selected entry is indicated by drawing a solid
rectangle around it.
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com
.
to register it. Thanks
[ Team LiB ]
Menu Bindings and Events
Keyboard Traversal
The default bindings for menus allow for keyboard selection of menu entries. The selection process is started by pressing <Alt-x>, where x is
the distinguishing letter for a menubutton or a menu bar's cascade entry. The underline attribute is used to highlight the appropriate letter. The
underline value is a number that specifies a character position, and the count starts at zero. For example, File
a menu with a highlighted F is
created for a menubutton like this:
menubutton .menubar.file -text File -underline 0 \
-menu .menubar.file.m
If the File menu is implemented as a menu bar cascade, you create the traversal highlight like this:
menu .mbar
. configure -menu .mbar
.mbar add cascade -label File -underline 0 \
-menu .mbar.file
When the user types <Alt-f> over the main window, the menu is posted. The case of the highlighted letter is not important.
After a menu is posted, the arrow keys change the selected entry. The <Up> and <Down> keys move within a menu, and the<Left> and
<Right> keys move between adjacent menus. The bindings assume that you create your menus from left to right.
If any of the menu entries have a letter highlighted with the -underline option, typing that letter invokes that menu entry. For example, anExport
entry that is invoked by typing x can be created like this:
.menubar.file.m add command -label Export -underline 1 \
-command File_Export
The <space> and <Return> keys invoke the menu entry that is currently selected. The<Escape> key aborts the menu selection and removes
the menu.
Menu Virtual Events
As of Tk 8.0, a menu widget generates a <<MenuSelect>> virtual event whenever the menu's active entry changes. The event is fired after
the menu selection has changed, so the binding action can access the new selection. The easiest way to be aware of changes to the menu
selection is to bind to this virtual event, as shown in Example 30-8. Notification like this is useful for features such as context-sensitive help.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Example 30-8 Using the <<MenuSelect>> virtual event
proc MenuChanged {w} {
puts "Menu $w selection: [$w entrycget active -label]"
}
bind .mbar.file <<MenuSelect>> {MenuChanged %W}
[ Team LiB ]
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
Manipulating Menus and Menu Entries
There are a number of operations that apply to menu entries. We have already introduced the add operation. The entryconfigure operation is
similar to the configure operation for widgets. It accepts the same attribute-value pairs used when the menu entry was added. The
delete
operation removes a range of menu entries. The rest of the operations are used by the library scripts that implement the standard bindings for
menus.
A menu entry is referred to by an index. The index can be numerical, counting from zero, or symbolic.Table
30-3 summarizes the index formats. One of the most useful indices is a pattern that matches thelabel in the
menu entry. The pattern matching is done with the rules of string match. Using a pattern eliminates the need
to keep track of the numerical indices.
Table 30-3. Menu entry index keywords
index
A numerical index counting from zero.
active
The activated entry, either because it is under the mouse or has been activated by keyboard traversal.
end
The last menu entry.
last
The same as end.
none
No entry at all.
@ycoord
The entry under the given Y coordinate. Use@%y in bindings.
pattern
A string match pattern to match the label of a menu entry.
Table 30-4 summarizes the complete set of menu operations. In the table,$w is a menu widget.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Table 30-4. Menu operations
$w activate index
Highlights the specified entry.
$w add type ?option value? ...
Adds a new menu entry of the specified type with the given values for various attributes.
$w cget option
Returns the value for the configuration option.
$w clone
Makes a linked copy of the menu. This is used to implement tear-offs and menu bars.
$w configure ?option? ?value? ...
Returns the configuration information for the menu.
$w delete i1 ?i2?
Deletes the menu entries from index i1 to i2.
$w entrycget index option
Returns the value of option for the specified entry.
$w entryconfigure index ?option? ?value? ...
Queries or modifies the configuration information for the specified menu entry.
$w index index
Returns the numerical value of index.
$w insert type index ?option value? ...
Like add, but inserts the new entry after the specifiedindex.
$w invoke index
Invokes the command associated with the entry.
$w post x y
Displays the menu at the specified coordinates.
$w postcascade index
Displays the cascade menu from entry index.
$w type index
Returns the type of the entry at index.
$w unpost
Unmaps the menu.
$w yposition index
Returns the Y coordinate of the top of the entry.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
Menu Attributes
A menu has a few global attributes, and then each menu entry has many button-like attributes that describe its appearance and behavior.
Table 30-5 specifies the attributes that apply globally to the menu, unless overridden by a per-entry attribute. The table uses the X resource
names, which may have a capital at interior word boundaries. In Tcl commands, use all lowercase and a leading dash.
Table 30-5. Menu attribute resource names
activeBackground
Background color when the mouse is over a menu entry.
activeBorderWidth
Width of the raised border around active entries.
activeForeground
Text color when the mouse is over a menu entry.
background
The normal background color for menu entries.
borderWidth
Width of the border around the menu (except on systems where native menus are used, such as Windows).
cursor
Cursor to display when mouse is over the menu.
disabledForeground
Foreground (text) color when menu entries are disabled.
font
Default font for the text.
foreground
Foreground color. (Also fg).
postCommand
Tcl command to run just before the menu is posted.
relief
The relief style of the menu (except on systems where native menus are used such, as Windows).
selectColor
Color for selector in check and radio type entries.
takeFocus
Control focus changes from keyboard traversal.
tearOff
True if menu should contain a tear-off entry.
tearOffCommand
Command to execute when menu is torn off. Two arguments are added: the original menu and the new tear-off.
title
Title for the window created when the menu is torn off. If this is an empty string (default), the title is the text of the
menubutton or cascade item from which this menu was torn off. (Tk 8.0)
type
(Read-only) normal, menubar, or tearoff. (Tk 8.0).
Table 30-6 describes the attributes for menu entries, as you would use them in a Tcl command (i.e., all lowercase with a leading dash.) The
attributes for menu entries are not supported directly by the resource database. However, Example 31-6 on page 481 describes how you can
use the resource database for menu entries.
Table 30-6. Attributes for menu entries
-activebackground
Background color when the mouse is over the entry.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
-activeforeground
Foreground (text) color with mouse is over the entry.
-accelerator
Text to display as a reminder about keystroke binding.
-background
The normal background color.
-bitmap
A bitmap to display instead of text.
-columnbreak
Puts the entry at the start of a new column. (Tk 8.0).
-command
Tcl command to invoke when entry is invoked.
-compound
Where the image or bitmap should be placed relative to the text:bottom, center, left, right, top or none (default). (Tk 8.4)
-font
Default font for the text.
-foreground
Foreground color. (Also fg).
-hidemargin
Suppresses the margin reserved for button indicators. (Tk 8.0).
-image
Image to display instead of text or bitmap.
-indicatoron
Boolean that controls if the indicator is displayed:check and radio entries.
-label
Text to display in the menu entry.
-menu
Menu posted when cascade entry is invoked.
-offvalue
Variable value when check entry is not selected.
-onvalue
Value for Tcl variable when check entry is selected.
-selectcolor
Color for selector: check and radio entries.
-selectimage
Alternate image to use when entry is selected: check and radio entries.
-state
The state: normal, active, or disabled
-underline
Index of text character to underline.
-value
Value for Tcl variable when radiobutton entry is selected.
-variable
Tcl variable associated with the check or radio entry.
[ Team LiB ]
.
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks
[ Team LiB ]
A Menu by Name Package
If your applicati
Download