Scripting with Perl and Tcl

48 downloads 578 Views 423KB Size Report
1.7.2 The Similarity of Python/Tkinter and Perl/Tk . . . . . . . . 61 .... This script reads a real number from the command line, takes the sine of the number, and writes ...
Scripting with Perl and Tcl Hans Petter Langtangen Simula Research Laboratory and Department of Informatics University of Oslo

Table of Contents

1 Introduction to Perl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1

1.2

1.3

1.4

A Scientific Hello World Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.1 Reading and Writing Data Files . . . . . . . . . . . . . . . . . . . . . 1.1.2 The Complete Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.3 Dissection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1.4 The Concept of Context in Perl . . . . . . . . . . . . . . . . . . . . . Automating Simulation and Visualization . . . . . . . . . . . . . . . . . . . 1.2.1 The Complete Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.2 Dissection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . There’s More Than One Way To Do It . . . . . . . . . . . . . . . . . . . . . . 1.3.1 A Script for Perl Beginners . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.2 Using the Underscore Variable . . . . . . . . . . . . . . . . . . . . . . . 1.3.3 A Script Written in Typical Perl Style . . . . . . . . . . . . . . . . 1.3.4 Shorter Scripts for Lazy Programmers . . . . . . . . . . . . . . . . 1.3.5 The Ultimate Goal: Getting Rid of the Script File . . . . . 1.3.6 Perl Has a Grep Function Too . . . . . . . . . . . . . . . . . . . . . . . Frequently Encountered Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.1 Basic Control Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.2 File Reading and Writing . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.3 Running an Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.4 One-Line Perl Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.5 Array and List Operations . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.6 Hash Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.7 Splitting and Joining Text . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.8 Text Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.9 String Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.10 Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.11 Subroutines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.12 Nested, Heterogeneous Data Structures . . . . . . . . . . . . . . . 1.4.13 Testing a Variable’s Type . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.14 Numerical Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.15 Listing of Files in a Directory . . . . . . . . . . . . . . . . . . . . . . . 1.4.16 Testing File Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.17 Copying and Renaming Files . . . . . . . . . . . . . . . . . . . . . . . . 1.4.18 Creating and Moving to Directories . . . . . . . . . . . . . . . . . . 1.4.19 Removing Files and Directories . . . . . . . . . . . . . . . . . . . . . . 1.4.20 Splitting Pathnames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.21 Traversing Directory Trees . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.22 Downloading Internet Files . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.23 CPU-Time Measurements . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 2 3 3 3 7 8 8 10 12 13 13 14 15 15 15 16 17 18 19 20 21 24 25 25 26 27 28 31 33 33 34 34 35 35 35 36 36 37 38

1.5

1.6

1.7

1.8

1.4.24 Programming with Classes . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.25 Debugging Perl Scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.26 Regular Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.27 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4.28 Building and Using Modules . . . . . . . . . . . . . . . . . . . . . . . . 1.4.29 Binary Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installing Perl and Additional Modules . . . . . . . . . . . . . . . . . . . . . . 1.5.1 Installing Basic Perl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.5.2 Manual Installation of Perl Modules . . . . . . . . . . . . . . . . . . 1.5.3 Automatic Installation of Perl Modules . . . . . . . . . . . . . . . 1.5.4 The Required Perl Modules . . . . . . . . . . . . . . . . . . . . . . . . . Perl Versus Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.1 Python’s Advantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.2 Perl’s Advantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.6.3 Efficiency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . GUI Programming with Perl/Tk . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.1 The First Perl/Tk Encounter . . . . . . . . . . . . . . . . . . . . . . . . 1.7.2 The Similarity of Python/Tkinter and Perl/Tk . . . . . . . . 1.7.3 Binding Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Web Interfaces and CGI Programming . . . . . . . . . . . . . . . . . . . . . . 1.8.1 Web Versions of the Scientific Hello World Program . . . . 1.8.2 Debugging CGI Scripts in Perl with CGI::Debug . . . . . . . 1.8.3 Using Perl’s CGI Module to Construct Forms . . . . . . . . .

39 39 41 45 48 50 51 51 51 52 53 53 53 55 56 57 59 61 61 62 62 64 65

2 Introduction to Tcl/Tk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 2.1

2.2

2.3

2.4

A Scientific Hello World Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 The Complete Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.2 Dissection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reading and Writing Data Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 The Complete Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.2 Dissection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.3 Double Quotes, Braces, Brackets, and Variable Substitution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Automating Simulation and Visualization . . . . . . . . . . . . . . . . . . . 2.3.1 The Complete Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.2 Dissection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Frequently Encountered Tasks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 File Reading and Writing . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.2 Running an Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.3 List Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.4 Associative Array Operations . . . . . . . . . . . . . . . . . . . . . . . 2.4.5 Splitting and Joining Text . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.6 Text Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.7 String Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.8 Numerical Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

68 69 69 70 70 71 74 75 75 77 79 80 80 81 82 83 83 84 85

2.5

2.4.9 Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.10 Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.11 Listing of Files in a Directory . . . . . . . . . . . . . . . . . . . . . . . 2.4.12 Testing File Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.13 Copying and Renaming Files . . . . . . . . . . . . . . . . . . . . . . . . 2.4.14 Creating and Moving to Directories . . . . . . . . . . . . . . . . . . 2.4.15 Removing Files and Directories . . . . . . . . . . . . . . . . . . . . . . 2.4.16 Splitting Pathnames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.17 Traversing Directory Trees . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.18 CPU-Time Measurements . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.19 Programming with Classes . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.20 Building and Using Packages . . . . . . . . . . . . . . . . . . . . . . . . 2.4.21 Regular Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.22 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . GUI Programming with Tcl/Tk . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.1 The First Tcl/Tk Encounter . . . . . . . . . . . . . . . . . . . . . . . . 2.5.2 Binding Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.3 Widget Name Hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.4 The Similarity of Python/Tkinter and Tcl/Tk . . . . . . . . . 2.5.5 Using Variables in Widget Names . . . . . . . . . . . . . . . . . . . . 2.5.6 Configuring Widgets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.7 The Grid Geometry Manager . . . . . . . . . . . . . . . . . . . . . . . .

85 86 89 89 89 89 90 90 90 91 91 91 93 93 94 95 96 97 97 98 99 100

Preface The purpose of this document is to show how the introductory programming examples from the book “Python Scripting for Computational Science” [?] can be implemented in Perl and Tcl. In addition, we list some core functionality of these scripting languages, typically corresponding to the same information and examples as in Chapter 3 in [?]. If you know the examples in a Python context from Chapters 2 and 3 in [?], it is quite easy to pick up basic Perl and Tcl from the present note. The Perl and Tcl chapters can be read independently. The author has a desire to include other scripting languages, e.g., Ruby and Scheme. Potential authors of such (independent) chapters, with the same structuring as the Perl and Tcl chapters, are encouraged to drop me an email ([email protected]). The present printing of the document contains the Perl part only.

Chapter 1

Introduction to Perl This chapter gives a quick introduction to the Perl language for readers who are familiar (at least to some extent) with the Python scripts from Chapters 2.1–2.3 and 3 in the book [?]. We shall look at the same sample scripts and show how the syntax changes when we program in Perl. Recommended Documentation. As a companion to the introductory examples and the overview of basic Perl functionality provided in this appendix, you need the Perl man pages. These come along with the Perl distribution. I find it convenient to read the man pages in plain text format using the perldoc tool. Some common ways of looking up information with perldoc are exemplified below. perldoc perldoc perldoc perldoc perldoc

perl perlsub Cwd -f open -q cgi

# # # # #

overview of all Perl man pages read about subroutines look up a special module, here ’Cwd’ look up a special function, here ’open’ seach the FAQ for the text ’cgi’

A Web version of the man pages can be found in the doc.html file. There you can also find the Perl FAQ and a quick reference. Having grasped the basic introduction to Perl from this appendix, you will find the definite Perl reference, the famous “Camel book” [?], very useful. However, much of the text in [?] coincides with the Perl man pages. If you feel that a more comprehensive introduction to Perl is needed, “Learning Perl” [?] and [?] are recommended. Ready-made recipes for numerous common tasks in scripting are collected in the highly recommended “Perl Cookbook” [?]. Advanced features of Perl are well discussed in [?] and [?]. Some Web resources regarding Perl topics are listed in doc.html. The first Perl encounter consists of three of the examples from the introduction to Python in Chapter 2 in [?]. We start out with a Hello World script, before continuing with a script concerning file handling and array processing. Thereafter we present a script gluing a simulation and a visualization program. All these scripts referred to in this section are found in src/perl. Thereafter, in Chapter 1.4 we list, in an example-oriented way, some basic and useful Perl functionality for quick reference. Chapter 1.5 explains how to install Perl and additional modules. A brief comparison of Perl versus Python appears in Chapter 1.6, while Chapters 1.7 and 1.8 deal with graphical user interfaces: standard GUIs and dynamic Web pages, respectively.

2

1. Introduction to Perl

1.1

A Scientific Hello World Script

Our first look at Perl will be the Scientific Hello World script from Chapter 2.1 in [?]. This script reads a real number from the command line, takes the sine of the number, and writes “Hello, World! sin(r)=s” with the appropriate values of the numbers r and s. In Perl, we can write the script like this: #!/usr/bin/perl $r = $ARGV[0]; # fetch the first ([0]) command-line argument $s = sin($r); # compute sin(r) and store in variable s print "Hello, World! sin($r)=$s\n"; # print to standard output

Comments in Perl start with # and continue for the rest of the line. However, the first line #/usr/bin/perl! has a special meaning: Under Unix it tells that the script, if run as an executable file, is to be interpreted by the program /usr/bin/perl. If the executable Perl interpreter is stored in another path on your system, you must write the correct full path in the top line of the script or (usually better) use a different header to be presented in Chapter 1.1.1. Scalar variables in Perl are always preceded by a $ sign, i.e., $r and $s are scalar variables in the present script. The command-line arguments to a Perl script are automatically stored in the array ARGV. Subscripting this array is done as in $ARGV[0] (which implies extracting the first entry; arrays in Perl start with 0 as in C and Python). The length of the array is $#ARGV+1, i.e., $ARGV[$#ARGV] is the last entry of the array. The array itself as a variable is reached with the syntax @ARGV (and one can say, e.g., print "ARGV=@ARGV"). Variables can be directly inserted into a text string, a convenient feature called variable interpolation: print "Hello, World! sin($r)=$s\n"; # print to screen

Such variable interpolation works only if the string is surrounded by double quotes. Single quotes just leads to output of text with dollar characters. Perl’s syntax is much inspired by C. For example, the newline character is \n and all statements are terminated by a semicolon. As usual in scripting, variables are never declared; the context determines the type. Contrary to Python, a variable can be used both as a string and a floating-point number. For example, $r is initialized to a text, but can be sent to the sine function, which expects a floating-point variable, without any explicit type conversion. Perl’s printf function gives good control of the output format of numbers and strings: printf "Hello, World! sin(%g)=%12.5e\n", $r, $s;

There is no possibility to control the format when using variable interpolation (i.e., Python’s %(s)12.5e is not supported). If the script is stored in a file hw.pl, you can execute the script by typing

1.1. A Scientific Hello World Script

3

perl hw.pl 0.1

or you can make the file executable under Unix (chmod a+x hw.pl) and then just write ./hw.pl 0.1

1.1.1

Reading and Writing Data Files

Chapter 2.2 in [?] deals with a script for reading a file with (x, y) data points in two columns and writing a new two-column file with transformed data points (x, f (y)). On the next pages we shall present and explain a Perl counterpart to the Python scripts. This case study demonstrates how to work with files, subroutines, and arrays in Perl. 1.1.2

The Complete Code

: # *-*-perl-*-* eval ’exec perl -w -S $0 ${1+"$@"}’ if 0; # if running under some shell die "Usage: $0 infilename outfilename\n" if $#ARGV < 1; ($infilename, $outfilename) = @ARGV; open(INFILE, "$outfilename"); # open for writing # read one line at a time: while (defined($line=)) { ($x, $y) = split(’ ’, $line); # extract x and y value $fy = myfunc($y); # transform y value printf(OUTFILE "%g %12.5e\n", $x, $fy); } close(INFILE); close(OUTFILE); sub myfunc { my ($y) = @_; if ($y >= 0.0) { return $y**5.0*exp(-$y); } else { return 0.0; } }

This script is stored in src/perl/datatrans1.pl. 1.1.3

Dissection

The Perl script starts with a header : # *-*-perl-*-* eval ’exec perl -w -S $0 ${1+"$@"}’ if 0; # if running under some shell

4

1. Introduction to Perl

This header ensures that executing the script as ./datatrans1.pl infile outfile

implies interpreting the code by the first perl program encountered in the directories listed in your PATH environment variable. The explanation of all the details in our Perl header is intricate, but it can be found in the file src/perl/headerfun.sh. (This is actually a document written in Bash (!) so you need to run the file to get the document printed.) In the case where the user has failed to provide two command-line arguments, we want to write a usage message and abort the script. This is accomplished by Perl’s die statement: die prints a string on standard error and terminates the script. In the present example the script dies if there are less than two command-line arguments: die "Usage: $0 infilename outfilename\n" if $#ARGV < 1;

Recall that $#ARGV is the last legal index in @ARGV, i.e., the length of @ARGV is $#ARGV+1, so the test is $#ARGV+1 < 2, leading to $#ARGV < 1. Extracting the first two command-line arguments can be performed by standard subscripting: $infilename = $ARGV[0]; $outfilename = $ARGV[1];

However, it is more common (and elegant) to use Perl’s list assignment construction: ($infilename, $outfilename) = @ARGV;

The list on the left-hand side is set equal, entry by entry, to the entries in the array on the right-hand side. We refer to the remark at the end of this section for an explanation of the difference between list and array in Perl terminology. Opening files in Perl is done with the open function: open(INFILE, "$outfilename"); # open for writing

The first argument to open is a file handle, which is used for accessing the file in the Perl code. Input files are recognized by < in front of the name1 , > signifies an output file, and >> implies that text will be appended to the file. Reading from a file handle, line by line, is accomplished by while (defined($line=)) { # process $line } 1

If there is no < symbol, the file is opened for reading. In fact, opentt(F,"$outfilename") or die "unsuccessful opening of $outfilename; $!\n"; for ($i = 0; $i \$c, "func=s" => \$func, "A=f" => \$A, "w=f" => \$w, "y0=f" => \$y0, "tstop=f" => \$tstop, "dt=f" => \$dt, "case=f" => \$case, "screenplot!" => \$screenplot);

The syntax m=f means searching for the command-line argument --m and loading the proceding argument as a floating-point number (=f) into the Perl variable $m. A single hyphen as in -m works too. Similarly, func=s specifies 3

Experienced Perl programers will often write just $options = shift; because shift without arguments implies shifting @ARGV. More examples regarding such shortcuts in Perl are provided in Chapter 1.3.

1.2. Automating Simulation and Visualization

11

--func to take a string argument. The specification of the flag screenplot allows us to use either --screenplot for setting $screenplot to a true value or --noscreenplot for setting $screenplot to a false value (note to get this on/off behavior, the exclamation mark is required in "screenplot” =¿ $screenplot!). The GetOptions function has a rich functionality; the purpose here just

is to notify the reader about the existence of such a handy function. Instructive information is obtained from perldoc Getopt::Long. There are several other modules in the Getopt family. For example: Getopt::Simple for a simplified interface to Getopt::Long, Getopt::Std for single-character options, Getopt::Mixed for long and single-character options, and Getopt::Declare for handling command-line options or configuration files with associated help text and initialization code. The next step in our script is to move to the prescribed directory. However, we should first check whether the directory exists, and if so, we should delete it and recreate it to avoid mismatch between old and new result files. Checking if a directory exists is done by the command if (-d $directoryname) in Perl. Removing a non-empty directory can be conveniently done by first loading an external Perl module, use File::Path, and then calling the function rmtree in that module: use File::Path; # has the rmtree function if (-d $dir) { # does $dir exist? rmtree($dir); # remove directory (old files) } mkdir($dir, 0755) or die "Could not create $dir; $!\n"; chdir($dir) or die "Could not move to $dir; $!\n";

Observe that we test for success of mkdir. For example, insufficient permission to create a new directory will not be noticable when running the script unless we include the or die statement4 . The next task is to write an input file for the oscillator program. Multiline output can easily be created through an ordinary string with embedded newlines5 print F " $m $b $c $func $A $w $y0 $tstop $dt "; 4

5

Python will in such cases abort the script and write a “Permission denied” message to standard output. See Exercise 1.8. Python requires a triple quoted string for this purpose.

12

1. Introduction to Perl

Alternatively, we can use a special Perl construction (stemming from Unix shells), known as a here document: print F = 2; ($pattern, @files) = @ARGV; foreach (@files) { next unless -f; open FILE, $_;

1.3. There’s More Than One Way To Do It

15

foreach () { print if /$pattern/; } close FILE; }

The next unless -f statement means that one jumps to the next iteration in the loop unless the test if (-f $_) is true, i.e., unless the current filename ($_) is an existing file. 1.3.4

Shorter Scripts for Lazy Programmers

There are many shortcuts in Perl aimed at lazy programmers. Here is an example of a grep script equivalent to those above, but with a much more compact file reading construction: #!/usr/bin/perl $pattern = shift; # shift; means shift @ARGV while () { # read line by line in file by file print if /$pattern/o; # o increases the efficiency }

The while () loop implies reading all lines in all files whose names are in @ARGV. (If there are no filenames on the command line, reads from standard input.) Since processing a list of files in a line-oriented fashion is a frequently encountered task in scripts, while () is a popular and widely used construction that saves quite some typing. It goes without saying that each line is available in the $_ variable. 1.3.5

The Ultimate Goal: Getting Rid of the Script File

We can also do the grep operation with a command-line Perl script: perl -n -e ’print if /superLibFunc/;’ file1 file2 file3

Here, the -n option tells Perl to invoke a loop over all lines in all files specified on the command line (equivalent to while ()) and execute the string after -e as a Perl script applied to each line. Implicit here is that the line is stored in the $_ variable. 1.3.6

Perl Has a Grep Function Too

The grep operation is so common that Perl has in fact a built-in grep function: #!/usr/bin/perl die "Usage: $0 pattern file1 file2 ...\n" unless @ARGV >= 2; ($pattern, @files) = @ARGV; foreach $file (@files) { if (-f $file) { open FILE, "$outfilename") # open for writing or die "Cannot write to file $outfilename; $!\n"; $line_no = 0; # count the line number in @lines foreach $line (@lines) { $line_no++; print OUTFILE "$line_no: $line"; } close(OUTFILE);

We can proceed with appending text to a file, using Perl’s features for writing (large) blocks of text in one output statement, with embedded variables if desired: open(OUTFILE, ">>$filename") # open for appending or die "Cannot append to file $filename; $!\n"; # print multiple lines at once, using a ‘‘here document’’: print OUTFILE $b) { $max = $a$; } else { $max = $b; } return $max; }

The my keyword makes variables local to the subroutine9 . Unless you specify a variable with my it is treated as a global variable whose value is visible outside the routine as well. Frequently, one maps the @_ array onto suitable local values using convenient list techniques, e.g., my ($a, $b) = @_;

This allows working with scalars, such as $a and $b, instead of the array entries $_[0] and $_[1]. Alternatively, we can extract $a and $b using the shift operator: my $a = shift; my $b = shift;

# same as shift @_;

Variable Number of Arguments. Here is a subroutine statistics, with a variable number of arguments, which returns a list containing the average and the minimum and maximum value of all the arguments: ($avg, $min, $max) = statistics($v1, $v2, $v3, $b); sub statistics { # arguments are available in the array @_ my $avg = 0; my $n = 0; # local variables 9

See [?] for a precise explanation of the my keyword.

# usage

1.4. Frequently Encountered Tasks

29

foreach $term (@_) { $n++; $avg += $term; } $avg = $avg / $n; my $min = $_[0]; my $max = $_[0]; shift @_; # swallow first arg., it’s already treated foreach $term (@_) { if ($term < $min) { $min = $term; } if ($term > $max) { $max = $term; } } return ($avg, $min, $max); }

Call by Reference. Modifying the arguments inside the subroutine, i.e., call by reference, is enabled by working directly on the @_ array. For example, swap($v1, $v2); # swap the values of $v1 and $v2 sub swap { my $tmp = $_[0]; $_[0] = $_[1]; $_[1] = $tmp; }

That is, @_ contains references to the variables used in the subroutine call10 . We remark that the swap function is just an example on call by reference; the elegant Perl way of swapping two variables reads ($v2,$v1)=($v1,$v2). One can also pass references to variables to subroutines and in this way get the effect of call by reference. A reference to a variable $a reads \$a. Having the reference as a variable $a_ref, we can extract its value by ${$a ref}. We may then write the swap function as sub swap { my ($a_ref, $b_ref) = @_; # extract references # swap the contents of the underlying variables my $tmp = ${$a_ref}; ${$a_ref} = ${$b_ref}; ${$b_ref} = $tmp; } swap(\$v1, \$v2);

Alternatively, we can just swap the references themselves: sub swap2 { my ($a_ref, $b_ref) = @_; # swap references: my $tmp = $a_ref; $a_ref = $b_ref; $b_ref = $tmp; }

Another example on using references in Perl appears on page 31. 10

Perl applies call by reference, and copying the arguments in @ into local variables in a my statement simulates call by value.

30

1. Introduction to Perl

Keyword Arguments. By using a hash to hold the arguments passed to a subroutine, one can obtain a very readable syntax and the possibility for assigning default values to an arbitrary set of the arguments11 . Here is an example, where we call a subroutine with two parameters, message and file: $filename = "my.tmp"; print2file(message => "testing hash args", file => $filename); sub print2file { my %args = (message => "no message", # default file => "tmp.tmp", # default @_); # assign and override open(FILE,">$args{file}"); print FILE "$args{message}\n\n"; close(FILE); }

Inside the subroutine we first assign default values to the hash entries and thereafter we insert the argument list @_, which can be interpreted as a hash as well. This latter hash might then override our default values. For example, calling print2file(file => $filename);

leaves $args{message} as no message, but $args{file} is overwritten by the $filename variable inside the print2file subroutine. The use of a hash in subroutine calls also makes the sequence of arguments irrelevant. The technique is used throughout Perl’s Tk module for creating graphical user interfaces and (see Chapter 1.7). Omitting Parenthesis in a Call. If a subroutine is declared before you call it, you can omit the parenthesis in the call statement, e.g., sub myproc { my $file1 = shift; // implicit shift on @_ my $file2 = shift; ... } # call myproc without parenthesis: myproc $myfile, "$yourdir/$yourfile";

All the subroutines in the Perl libraries are declared before you use them so you can omit parenthesis if you desire. Here are some examples: print "No of iterations=$iter\n"; print("No of iterations=$iter\n"); open TMPFILE, ">$tmpfile"; open(TMPFILE, ">$tmpfile"); system "simulator -q 1.2"; system("simulator -q 1.2"); 11

This is the counterpart to Python’s keyword arguments, see page 101 in [?].

1.4. Frequently Encountered Tasks

31

Multiple Arrays as Arguments. If you want to send several arrays to a subroutine, you need to explicitly pass references to the arrays. Otherwise, one cannot detect where one array stops and the next starts in @_. We shall now show an example where we transfer two arrays to a subroutine and print them out simultaneously in a nice format: @curvelist = (’curve1’, ’curve2’, ’curve3’); @explanations = (’initial shape of u’, ’initial shape of H’, ’shape of u at t=2.5’); # send the two arrays to displaylist, using references # (\@list is a reference to the array @list): displaylist(list => \@curvelist, help => \@explanations);

The implementation of the displaylist routine, taking two array arguments transferred by references, is listed next. sub displaylist { my %args = (@_); # extract the two lists from the my $list_ref = $args{’list’}; # my @list = @$list_ref; # my $help_ref = $args{’help’}; # my @help = @$help_ref; #

two references: extract reference extract array from reference extract reference extract array from reference

my $index = 0; my $item; for $item (@list) { printf("item %d: %-20s description: %s\n", $index, $item, $help[$index]); $index++; } # Alternative, without lots of local variables: $index = 0; for $item (@{$args{’list’}}) { printf("item %d: %-20s description: %s\n", $index, $item, ${@{$args{’help’}}}[$index]); $index++; } }

The output of displaylist looks like this: item 0: curve1 item 1: curve2 item 2: curve3

description: initial shape of u description: initial shape of H description: shape of u at t=2.5

We refer to the Pass by Reference section of perldoc perlsub (or the equivalent text in [?, p. 116-118]) for more information. 1.4.12

Nested, Heterogeneous Data Structures

The problems with displaylist and the need for references also occur in nested, heterogeneous data structures. Say we want a list such as the curves1

32

1. Introduction to Perl

list in page 81 in [?]. In Perl we could build some of its components first, which are straight arrays: @point1 @point2 @point3 @point4

= = = =

(0,0); (0.1,1.2); (0.3,0); (0.5,-1.9);

A list of these points must be a list of references to @point1, @point2, etc.: @points = (\@point1, \@point2, \@point3, \@point4);

Now, suppose we have an array @xy1 similar to @points. The curves1 array is supposed to contain a string, @points, another string, and @xy1. Again, references are required to avoid “flattening” the structure: @curves1 = ("u1.dat", \@points, "H1.dat", \@xy1);

It is tedious to write the sublist as separate variables so we can do with @curves1 = ("u1.dat", [[0,0], [0.1,1.2], [0.3,0], [0.5,-1.9]], "H1.dat", \@xy1);

That is, lists in square brackets provides a reference to an array. Indexing is performed with a syntax similar to Python. For example, $a = $curves1[1][1][0];

yields $a as 0.1. Nested data structures in Perl must make use of references, and it can be troublesome to debug such structures. The Data::Dumper module converts Perl data structures to readable strings: print Dumper(@curves1) results in the present case in $VAR1 = ’u1.dat’; $VAR2 = [ [ 0, 0 ], [ ’0.1’, ’1.2’ ], [ ’0.3’, 0 ], [ ’0.5’, ’-1.9’ ] ]; $VAR3 = ’H1.dat’;

1.4. Frequently Encountered Tasks

33

$VAR4 = [ [ ’0.3’, 0 ], [ ’0.5’, ’-1.9’ ] ];

The Data::Dumper module supports lots of output formats, see perldoc Data::Dumper. More information about references can be found in perldoc perlreftut. 1.4.13

Testing a Variable’s Type

An ordinary Perl variable is either a scalar, an array, or a hash. The prefix determines the type of the variable, so the variable name together with its prefix shows its type; it is no need to test on the variable’s type (as in Python). Writing $var = 1; @var = (1, 2); %var = (key1 => 1, key2 => ’two’);

# scalar # array # hash

creates three different Perl variables. Every time we use one of the variables, the prefix immediately shows its type. However, when working with references the prefix is always a dollar. The function ref can be used to test what kind of underlying data structure the reference is pointing to. The return value in a scalar context is a string, like ’SCALAR’, ’ARRAY’, or ’HASH’. In a boolean context, ref returns true if its argument is a reference: if (ref($r) eq "HASH") { # test return value print "r is a reference to a hash.\n"; } unless (ref($r)) { # use in boolean context print "r is not a reference at all.\n"; }

The ref function is handy when you work with nested, heterogeneous data structures. See perldoc -f ref and perldoc perlref for more information. 1.4.14

Numerical Expressions

Perl supports the same numerical expressions as C. Strings are automatically transformed to numbers when required: $b = 1.2; $b = "1.2"; $a = 0.5 * $b;

# b is a number # b is a string # b is converted to a real number before mult.

if ($b < 100) { print "ok\n"; } else { print "error!\n"; } # prints "ok"

34

1. Introduction to Perl

In the last test, the < operator works on numbers, and $b is interpreted as a number (, ==, =!, etc. are the comparison operators for numbers, whereas strings must be compared with lt, gt, eq, ne, etc.). 1.4.15

Listing of Files in a Directory

The following statements return a list of files (in the current working directory) having extensions .ps or .gif: @filelist = glob("*.ps *.gif"); # alternative: @filelist = ;

A more sophisticated glob function is also available, see perldoc File::Glob. 1.4.16

Testing File Types

Perl supports a range of tests for classifying files: if if if if if if

(-f (-d (-x (-z (-T (-B

$myfile) $myfile) $myfile) $myfile) $myfile) $myfile)

{ { { { { {

print print print print print print

"$myfile "$myfile "$myfile "$myfile "$myfile "$myfile

is is is is is is

a plain file\n"; } a directory\n"; } executable\n"; } empty(zero size)\n"; } a text file\n"; } a binary file\n"; }

There are also tests for the size and age of a file: $size = -s $myfile; $days_since_last_access = -A $myfile; $days_since_last_modification = -M $myfile;

See perldoc perlfunc and search for -f, -d, and so on for information about file tests. The stat function gives more detailed results about a file: ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($myfile);

A quote from the description of stat in the man page perlfunc explains what the various list entries above mean: 0 1 2 3 4 5 6 7 8 9 10 11 12

dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks

device number of filesystem inode number file mode (type and permissions) number of (hard) links to the file numeric user ID of file’s owner numeric group ID of file’s owner the device identifier (special files only) total size of file, in bytes last access time since the epoch last modify time since the epoch inode change time (NOT creation time!) since the epoch preferred block size for file system I/O actual number of blocks allocated

There is an alternative stat function in the File::stat module, see perldoc File::stat.

1.4. Frequently Encountered Tasks

1.4.17

35

Copying and Renaming Files

Renaming a file is simple: rename($myfile, "tmp.1");

# rename $myfile to tmp.1

Moving files across file systems is reliably done with the move function in Perl’s File::Copy library: use File::Copy; move($myfile, "/work/temp") or die "Could not rename file\n";

Copying a file $file to a file $tmpfile is performed with the copy function in the File::Copy library: use File::Copy; copy($file, $tmpfile);

1.4.18

Creating and Moving to Directories

Creating a directory and moving to a directory are tasks performed with the mkdir and chdir functions, respectively: use Cwd; $origdir = cwd; # remember where we are $dir = "../mynewdir"; mkdir($dir, 0755) or die "$0: couldn’t create dir; $!\n"; chdir($dir); ... chdir($origdir); # move back to the original directory chdir; # move to your home directory ($ENV{HOME})

Suppose you want to create a new directory perl/projects/test1 in your home directory, but neither perl, nor projects and test1 exist. Instead of using repeated mkdir commands, Perl offers the mkpath command, from the File::Path module, to create the whole path in one statement: use File::Path; mkpath("$ENV{HOME}/perl/projects/test1");

1.4.19

Removing Files and Directories

Single files are removed by the unlink statement, e.g., unlink("myfile") or die "Could not remove file\n";

A list of files can also be transferred to unlink: unlink(@files); unlink(glob("*.ps *.gif")); unlink "myfile", ’yourfile’, @thosefiles, "$file.tmp" or \ die "Could not remove files\n";

Frequently, one wants to remove a directory tree, possibly full of files, an action that requires the rmtree function from the File::Path library: use File::Path; rmtree("mydir");

36

1. Introduction to Perl

1.4.20

Splitting Pathnames

Let $fname be a filename containing a possibly long path, e.g., $fname = /usr/home/hpl/scripting/perl/intro/hw2a.pl

Occasionally, one wants to split this filename into the basename hw2a.pl and the directory name /usr/home/hpl/scripting/perl/intro/: use File::Basename; $basename = basename($fname); $dirname = dirname($fname);

One can also extract the base of the basename, hw2a, either by $base = $basename; # or by substituting the file extension by an empty string: $base =~ s/\.pl$//g;

or by the fileparse function: ($base, $dirname, $extension) = fileparse($fname,".pl");

The fileparse function can take an arbitrary number of possible extensions. 1.4.21

Traversing Directory Trees

The very useful Unix find command can be implemented in a cross-platform fashion in Perl using the File::Find library and its find function. The basic recipe for using Perl’s find goes as follows. use File::Find; # run through directory trees dir1, dir2, and dir3, and # for each file call the user-provided subroutine ourfunc: find(\&ourfunc, "dir1", "dir2", "dir3"); sub ourfunc { # $_ contains the name of the selected file $file = $_; # process $file # $File::Find::dir contains the current directory # (you are automatically chdir()’ed to this directory) # $File::Find::name contains $File::Find::dir/$file }

We shall now implement a script that lists all files larger than 1Mb in the home directory tree. The easiest way to extract the size of a file is to write $size = -s $file;

Our script in Perl might then look like

1.4. Frequently Encountered Tasks

37

#!/usr/bin/perl use File::Find; find(\&printsize, $ENV{HOME}); # traverse home-directory tree sub printsize { $file = $_; # more descriptive variable name... if (-f $file) { # is $file a plain file, not a directory? $size = -s $file; # or $size = (stat($file))[7]; if ($size > 1000000) { printf("%.1fMb %s in %s\n",$size/1000000.0,$file, $File::Find::dir); } } }

We recommend to read perldoc File::Find to see the many possibilities that Perl’s find function offers. There is a program find2perl that translates a Unix find command into the equivalent Perl program. The resulting program is not always easy to read for newcomers to Perl so writing the Perl script yourself gives better control of what you want to do. In the present example you can try find2perl find $HOME -name ’*’ -type f -size +2000 -exec ls -s {} \;

and realize that the resulting code has 55 (!) lines and is less cross-platform than our hand-coded version. 1.4.22

Downloading Internet Files

The libwww-perl package contains numerous modules and scripts for working with the World Wide Web. You can easily test if libwww-perl is already installed on your system by trying perl -e ’use LWP::Simple’

If this one-liner gives an error message, you need to get libwww-perl from CPAN (see page 53). The Perl script lwp-download (from the libwww-perl package) fetches a single file whose URL is known: lwp-download http://www.ifi.uio.no/~hpl/downloadme.dat

The script looks at the file contents and creates a suitable local filename for the copy. In this case, downloadme.dat is a text file that lwp-download stores as downloadme.dat.txt. A second argument to lwp-download can be used to specify a local filename. Inside a Perl script we can easily copy a file, given as a URL, to a local file:

38

1. Introduction to Perl use LWP::Simple; $URL = "http://www.ifi.uio.no/~hpl/downloadme.dat"; getstore($URL, "downloadme.dat"); # copy only if local file is not up-to-date: mirror($URL, "downloadme.dat.pl");

or we can load the remote file directly into an array of lines: @lines = get($URL);

The URL in these examples could also have been an ftp address, e.g., ftp://ftp.ifi.uio.no/pub/blab/xite/xite3_4.tar.gz

Check out perldoc LWP::Simple for details regarding more functionality. 1.4.23

CPU-Time Measurements

Measurement of elapsed time in Perl can be done with the time function: $t0 = time; # elapsed time in seconds since the epoch # do tasks... $elapsed_time = time - $t0;

Because time is measured in seconds, you need to perform efficiency tests that last several seconds. Timing with finer resolution is possible, see the Perl FAQ: perldoc -q ’time under a second’. Throughout this section we assume that the reader is familiar with terms like epoch, elapsed time, system time, CPU time, and the difference between children and parent processes, as briefly explained in Chapter 8.10.1 in [?]. A more sophisticated function times returns an array with four entries. The first two represent the user and system times of the current process while the next two contain the user and system times of the current process’ child processes. @t0 = times; # do tasks... system "$time_consuming_command" # child process @t1 = times; $user_time = $t1[0] - $t0[0]; $system_time = $t1[1] - $t0[1]; $cpu_time = $user_time + $system_time; $cpu_time_system_call = $t1[2] - $t0[2] + $t1[3] - $t0[3]

There is also a higher-level module Benchmark, based on the time and times functions, with various support for timing of Perl scripts. The usuage goes as follows. use Benchmark; $t0 = new Benchmark; # do some tasks... $t1 = new Benchmark; $td = timediff($t1, $t0); # time difference between $t0 and $t1 $nice_td_formatting = timestr($td, ’noc’); print "tasks: $nice_td_formatting\n";

1.4. Frequently Encountered Tasks

39

The output looks like this: tasks: 9 wallclock secs( 3.12 usr +

0.10 sys =

3.22 CPU)

The Benchmark module has also a function timeit that runs a piece of Perl code a specified number of times: use Benchmark; print "100 runs took", timestr(timeit(100,\&somefunc)), "\n";

We refer to perldoc Benchmark for more details about this module. From a pedagogical point of view it might be instructive to write a function like timeit in the Benchmark module. Doing this we also have the possibility of tailoring such a timing function to suit our needs. The function, here called timer, can take four arguments: (i) a function to call, (ii) a list of arguments to be used in the function to call, (iii) the number of call repetitions, and (iv) the name of the function to call. In Perl we would represent the first two arguments by a function reference and a reference to a list. The complete function could then take the following form: sub timer { my ($func_ref, $args_ref, $repetitions, $func_name) = @_; my $t0 = time; # initial elapsed time my ($u0, $s0, $rest) = times; # initial user and system time for (my $i = 0; $i < $repetitions; $i++) { &$func_ref(@$args_ref); } my @t1 = times; printf("$func_name: elapsed=%g, CPU=%g\n", time - $t0, $t1[0] - $u0 + $t1[1] - $s0); }

The similar Python function is presented in Chapter 8.10.1 in [?]. 1.4.24

Programming with Classes

Classes are implemented in Perl using quite advanced concepts like references and packages. Although Perl fans claim that classes in Perl are much more flexible than those in C++ and Java, it is no doubt that programming with classes is more weird in Perl than in C++, Java, and Python. Explaining Perl classes in a couple pages without first covering references and packages is difficult and therefore omitted here. 1.4.25

Debugging Perl Scripts

Unfortunately, Perl is by default quite silent about errors. The following short script, which tries to open a non-existing file, illustrates the point: perl -w -e ’open(F,"param("undefined_key")\n"; # CGI scripts are normally not allowed to open(FILE, ">myfile") or die "Cannot open 16

# warning # error write files: myfile!";

The need for a wrapper script, as explained in Chapter 7.1.4 in [?], is not necessary unless you have linked your Perl interpreter with special, local shared libraries.

1.8. Web Interfaces and CGI Programming

65

Running the script in a browser leads to a compilation error from Perl and a corresponding report written to the browser by CGI::Debug. The report is something like syntax error at /some/path/hw2e.pl.cgi line 13, near ""$form->param("undefined_key" Execution of /some/path/hw2e.pl.cgi aborted due to compilation errors. Your program doesn’t produce ANY output! Parameters ---------equalsbutton = r =

6[equals] 3[1.2]

followed by a listing of the contents of all environment variables related to CGI programming. Let us look at the effect of removing the wrong hash index. Running the script again leads to a run-time error since the CGI script is run by a “nobody” who is not likely to have permission to new files for writing. The open command will work if the owner of the directory creates a file myfile and gives all others permission to write on it, or if the owner gives all others write access to the current directory. CGI::Debug will in the present example write the error message from the die statement in a new Web page. Of course, both the undefined key and the undefined variable will be caught by running the script from the command line (the owner executes the script has write permission in the current directory). Opening a file, on the other hand, works well when running the script directly from the command line, but not when the script is run in a browser. This latter type of error is very common, and CGI::Debug helps you detect it easily. Finally, we can remove the open statement and the script runs correctly, but Perl issues a warning about the undefined_var variable. This warning is written by CGI::Debug at the end of the Web page. 1.8.3

Using Perl’s CGI Module to Construct Forms

The CGI module has numerous functions for writing HTML code. Consider the CGI script hw2.pl.cgi from page 63. Instead of writing the HTML code as plain text in print statements, we employ some hopefully self-explanatory functions from the CGI module: #!/usr/local/bin/perl use CGI; $wp = CGI->new(); print $wp->header, $wp->start_html(-title=>"Hello, Web World!", -BGCOLOR=>’white’), #$wp->start_form(-action=>’hw3.pl.cgi’), # default $wp->start_form, "Hello, World! The sine of ",

66

1. Introduction to Perl $wp->textfield(-name=>’r’, -default=>1.2, -size=>10), "\n", $wp->submit(-name=>’equals’), " "; if ($wp->param()) { $r = $wp->param("r"); $s = sin($r); } else { $s = sin(1.2); } print $s, "\n", $wp->end_form, $wp->end_html, "\n";

The complete source code is found in src/perl/hw3.pl.cgi. This code can be made nicer by removing the explicit appearance of the CGI object, i.e., the $wp-> construct. The statement use CGI qw/:standard/;

imports the most standard CGI functions directly into the namespace so do not need to work with a CGI object. This enables the following more readable version of the script: #!/usr/local/bin/perl use CGI qw/:standard/; print header, start_html(-title=>"Hello, Web World!", -BGCOLOR=>’white’), start_form, "Hello, World! The sine of ", textfield(-name=>’r’, -default=>1.2, -size=>10), "\n", submit(-name=>’equals’), " "; if (param()) { $r = param("r"); $s = sin($r); } else { $s = sin(1.2); } print $s, "\n", end_form, end_html, "\n";

The source code is found in src/perl/hw4.pl.cgi. More information on features in the CGI module can be obtained from the well-written man page; just type perldoc CGI. Using Perl’s CGI::QuickForm Module to Construct Forms. Plain output of HTML text or using the CGI utilities for generating HTML code quickly leads to somewhat lengthy scripts, especially when forms with many elements need to use HTML tables for achieving satisfactory layout. The CGI::QuickForm module takes the specification of a form and automatically generates an HTML page, typeset in a nicely formatted way. The Web version of our interactive Scientific Hello World program can be expressed as follows using the CGI::QuickForm module: #!/ifi/ganglot/k00/inf3330/www_docs/packages/SunOS/bin/perl use CGI qw/:standard/; use CGI::QuickForm; show_form( -ACCEPT => \&on_valid_form, # must be supplied -TITLE => "Hello, Web World!", -FIELDS => [

1.8. Web Interfaces and CGI Programming

67

{ -LABEL => ’Hello, World! The sine of ’, -TYPE => ’textfield’, -name => ’r’, -default => 1.2, }, ], -BUTTONS => [ {-name => ’compute’}, ], # "submit" button(s) ); sub on_valid_form { my $r = param(’r’); my $s = sin($r); print header, $s; # write new page with the answer }

You can find the source in src/perl/hw5.pl.cgi. Unfortunately, few standard Perl installations contain the CGI::QuickForm module. We therefore have to hardcode the path to our own Perl interpreter, which knows about CGI::QuickForm (if you have followed the instructions in Chapter 1.5), or we can apply a use lib statement to notify the Perl interpreter where our CGI::QuickForm module is installed. Since CGI scripts are run by a “nobody” user and not yourself, the script will not be aware of your special environment variables. You therefore need to hardcode the complete path in a use lib statement. If you need to make use of environment variables that are under your control, you can wrap a Bourne shell script around your CGI script; details are provided in Chapter 7.1.4 in [?]. When you let a CGI script use your own Perl, make sure that the Perl interpreter and its libraries are readable for all users. The CGI::QuickForm man page offers a good introduction to the many features of this module. Although the use of CGI::QuickForm in our Scientific Hello World example is an overkill, the advantages of the module become more apparent when the form contains several form elements and when validation of user input is desired. One example showing that CGI::QuickForm is handy is the src/perl/simviz1.pl.cgi

script, which is a counterpart to the CGI script cg/src/python/simviz1.py.cgi from Chapter 7.2 in [?]. The purpose of this script is to create a Web interface to the oscillator code from Chapter 2.3 in [?]. The user can type in values of the mathematical parameteres and get a plot of the solution.

Chapter 2

Introduction to Tcl/Tk Tcl is a widespread scripting language with some interesting features. For example, Tcl has an extremely simple syntax that is very popular among people with little or no previous experience in programming. Hence, if you plan to make a scripting interface to a tool, Tcl is worth considering if the users are expected to lack programming experience. Tcl has a large userbase. This has resulted in a considerable amount of Tcl scripts, especially graphical applications utilizing Tk, which are freely available on the Internet. Customizing such scripts to your own needs is easy if you know the basics of the Tcl syntax. The author’s opinion is that Perl and Python have considerably more functionality than Tcl and a syntax that is more familiar to programmers. The purpose of this chapter is to provide a quick introduction to Tcl, as well as Tk programming with Tcl syntax. Assuming familiarity Chapters 2 (except Chapter 2.4) and 3 of the book [?], we provide in the present chapter a Tcl version of most of the same examples and topics1 . Recommended Documentation. While programming in Tcl/Tk you need access to complete reference information about the language. The man pages are always useful and can be accessed from the doc.html page. The online reference documentation of Tcl is quite sparse, so in practice you need a textbook, e.g. [?], to look up details. The first Tcl encounter consists of three of the examples from the introduction to Python in Chapter 2 in [?]. We start out with a Hello World script, before continuing with a script concerning file handling and array processing. Thereafter we present a script gluing a simulation and a visualization program. All these scripts referred to in this section are found in src/tcl. Thereafter, in Chapter 2.4 we list, in an example-oriented way, some basic and useful Perl functionality for quick reference. Chapter 2.5 deals with GUI programming using Tcl/Tk. How to install Tcl/Tk is explained in Appendix A.1.3 in [?]; you most likely have Tcl/Tk installed if Python/Tkinter works properly.

2.1

A Scientific Hello World Script

The Scientific Hello World program from Chapter 2.1 in [?] is a good starting point for introducing a new language. The script takes a floating-point num1

The main exception is CGI programming, which is not covered in a Tcl context herein. Tcl has support for CGI programming, cf. a link in doc.html.

2.1. A Scientific Hello World Script

69

ber as command-line argument and writes the compulsory “Hello, World!” proceeded by the sine of the number on the command line. With a few lines we get an example on how to use variables in Tcl scripts, how to call functions, and how to write a mixture of plain text and floating-point numbers. The Tcl script might look as outlined in the next section. 2.1.1

The Complete Code

#!/usr/bin/tclsh set r [lindex $argv 0]; # load first command-line argument set s [expr sin($r)]; # store sin(r) in s puts "Hello, World! sin($r)=$s"

The script is stored in a file hw.tcl in src/tcl. Tcl scripts normally have the suffix .tcl. 2.1.2

Dissection

The first line specifies the program that should be used to interpret the script, here /usr/bin/tclsh. This line is only relevant if we execute script by just typing the name of the script, ./hw.tcl

Alternatively, we can explicitly write the name of the interpreter, e.g., tclsh hw.tcl

Now hw.tcl is interpreted by the first tclsh program encountered in one’s path. In this latter case, the first code line is treated as a comment. Tcl has an extremely simple syntax. Every statement is of the form procedure arg1 arg2 arg3 ...

That is, we write the name of the procedure and append a list of arguments, separated by whitespace. There are no operators (!), and in particular no assignment operator, which means that a = 15 does not make sense; one must call a procedure set with two arguments: set a 15. Writing just a means the variable a, while $a is the value of a. The result of a command is made available by a pair of brackets []. For example, set r [lindex $argv 0]

which means that the command lindex $argv 0 is first executed, and its result is set equal to r. The lindex $argv 0 command means extracting the first (index 0) entry in the array argv holding all the command-line arguments to the script. That is, one applies the lindex (list index) procedure for subscripting lists. Comments in Tcl start with #. It is not necessary to end Tcl statements with a semicolon, but you need the semicolon when you want to add a comment at the end of a line:

70

2. Introduction to Tcl/Tk set b $c set b $c;

# illegal # this is okay

The next statement in our first Tcl script sets the variable s equal to the outcome of the Tcl command expr sin($r): set s [expr sin($r)]

Mathematical expressions must be evaluated by calling the expr procedure. Although r is equal to a text (the first entry in the argv list), it is correctly treated as a floating-point number in the evaluation of the sine function. The last statement calls the puts (put string) procedure to print out the message: puts "Hello, World! sin($r)=$s"

Note that the Tcl variables can be directly embedded in the output string, a feature referred to as variable substitution in Tcl terminology2 . A newline character is automatically appended to a string written by puts, unless you add the -nonewline option. Tcl offers C’s printf-type format specifications for controlling the output format of numbers and text. The last puts line could, for instance, be written like this: puts [format "Hello, World! sin(%g)=%g" r s]

or puts [format "Hello, World! sin(%g)=%12.5e" r s]

We refer to page 72 in [?] (and the reference therein) or man sprintf or perldoc -f sprintf for a list of common format specifications.

2.2

Reading and Writing Data Files

Our next Tcl example concerns the file reading/writing scripts explained in Chapter 2.2 in [?]. The aim is to write a script which reads (x, y) data points from an input file with the x and y data in two columns, and write out a two-column file with transformed data points (x, f (y)). 2.2.1

The Complete Code

#!/bin/sh # execute the tclsh in the path \ exec tclsh "$0" ${1+"$@"} # issue error message if not two arguments are given: 2

The feature is called variable interpolation in Perl terminology.

2.2. Reading and Writing Data Files

71

if {$argc < 2} { puts "Usage: $argv0 infilename outfilename ..." exit 1 } set infilename [lindex $argv 0] set outfilename [lindex $argv 1] set INFILE [open $infilename r]; set OUTFILE [open $outfilename w];

# open for reading # open for writing

proc myfunc { y } { if {$y >= 0.0} { return [expr pow($y,5.0)*exp(-$y)] } else { return 0.0; } } # read one line at a time: while {[gets $INFILE line] >=0} { # replace multiple whitespace chars by " " to avoid # empty strings when splitting the line into words; # remove leading and trailing white space: set line [string trim $line] # replace one or more white spaces (regex \s+) by " ": regsub -all {\s+} $line " " line2 # split line2 into a list of words (x and y coord.): set xy [split $line2 " "]; # xy is a list of words in line2 set xi [lindex $xy 0]; set yi [lindex $xy 1] set fy [myfunc $yi]; # transform y value puts $OUTFILE [format "%g %12.5e" $xi $fy] } close $INFILE; close $OUTFILE

This script is found in src/tcl/datatrans1.tcl. 2.2.2

Dissection

The explicit path to the Tcl interpreter (tchsh) #!/usr/bin/tclsh

appearing in the heading of the script is often a disadvantage. A more robust statement, which guarantees execution of the first tclsh interpreter in your path (and thereby enabling you to easily run your own tclsh, not the “official” one on your computer system), reads #!/bin/sh \ # execute the tclsh in the path \ exec tclsh -f "$0" ${1+"$@"}

This header comes into play when we run the script by just typing the name of the file containing the script. The first line indicates that the script is to interpreted as a Bourne shell script. Bourne shell ignores the second line since it is a comment, but the third line is executed. This lines starts the first tclsh

72

2. Introduction to Tcl/Tk

program encountered in the user’s path. When the same script is interpreted again under tclsh, the backslash on the second line works as a continuation of the comment such that third line becomes a part of a comment. The rest of the file is plain Tcl code to be interpreted by tclsh. The datatrans1.tcl script takes two command-line arguments. If the user failed to provide two filenames on the command line, we write an error message and abort the script: if {$argc < 2} { puts "Usage: $argv0 infilename outfilename ..." exit 1 }

The name of the current script is stored in the special Tcl variable argv0, and argc holds the number of command-line arguments. Note that the if statement is simply a call to a procedure if with two arguments. The first argument is a Tcl expression that is evaluated as true or false. In case of a true value, the next argument is executed as a set of Tcl commands. Extraction of the user-provided filenames can be done by indexing the list argv, which holds all the command-line arguments: set infilename [lindex $argv 0] set outfilename [lindex $argv 1]

Opening a file for reading or writing is performed with the open command, which returns a file handle that we can store in a Tcl variable: set INFILE [open $infilename r]; set OUTFILE [open $outfilename w];

# open for reading # open for writing

The file handles are used for accessing the files later. Subprograms in Tcl are called procedures, and they must be defined before they can be called. Our function f (y) for transforming the y values read from file is implemented as a Tcl procedure with one argument: proc myfunc { y } { if {$y >= 0.0} { return [expr pow($y,5.0)*exp(-$y)] } \ else { return 0.0; } }

Note that numerical calculations are always performed with the expr command. You can look up the Tcl man pages from doc.html to see the mathematical functions that are supported by expr. Reading of the file, line by line, is accomplished by while {[gets $INFILE line] >=0} { # process $line

The gets procedure grabs the next line, stores it in the string line, and returns the number of bytes read. If the latter number is -1, end of file is reached.

2.2. Reading and Writing Data Files

73

The Perl and Python versions of this script could easily split a line into words separated by whitespaces. Tcl also has a split command, but repeated whitespaces result in empty strings returned from split (see page 83). We therefore need to modify the line read from file: (i) remove leading and trailing whitespace (use Tcl’s string trim command), and (ii) substitute strings consisting of one or more whitespaces by a single space character. The two operations are carried out by two lines of code: # remove leading and trailing whitespace: set line [string trim $line] # replace one or more whitespaces (regex \s+) by " ": regsub -all {\s+} $line " " line2

The regsub command offers string substitutions using regular expressions. The result of the substitution is stored in the variable line2, which we can split to extract the x and y value on this line: set xy [split $line2 " "]; # xy is a list of words in line2 set xi [lindex $xy 0]; set yi [lindex $xy 1]

We should, however, test that xy really contains two elements before extracting the x and y values, because a final newline gives an empty list after the split. The next statement calls our own procedure myfunc with one argument and stores the return value in a variable: set fy [myfunc $yi];

# transform y value

Writing the transformed data pair to the output file, with printf-like control of the output format, can be accomplished by puts $OUTFILE [format "%g

%12.5e" $xi $fy]

Chapter 2.2 in [?] describes some modifications of the current script: (i) the file is loaded into a list of lines, (ii) the x and y values read from file are stored in two lists, and (iii) the lists are subscripted in a standard for loop over the list indices when writing the output file. The following statements load a file into a list of lines: set filestr [read $INFILE]; # read file into a string set lines [split $filestr "\n"]; # split string wrt. newline

When running through the lines in the file we can append the x and y coordinates to the lists x and y: foreach line $lines { # fix the line: set line [string trim $line] regsub -all {\s+} $line " " line2 # split into x and y coordinate (in a list): set xy [split $line2 " "]

74

2. Introduction to Tcl/Tk if {[llength $xy] == 2} { # add coordinates to their respective lists (x and y): lappend x [lindex $xy 0] lappend y [lindex $xy 1] } # else: newline at the end gives extra blank line in $lines }

The output file can now be generated by a standard for loop with a running index i: set OUTFILE [open $outfilename w]; # open for writing set npairs [llength $x] for {set i 0} {$i 1 } { set option [lindex $argv 0] if { [string compare $option "-m"] == 0 } { # remove 1st command-line arg.: set argv [lreplace $argv 0 0]; # assign (the new) 1st command-line arg. to m: set m [lindex $argv 0] } \ elseif { [string compare $option "-b"] == 0 } { set argv [lreplace $argv 0 0] set b [lindex $argv 0] } \ elseif { [string compare $option "-c"] == 0 } { set argv [lreplace $argv 0 0] set c [lindex $argv 0] } \ elseif { [string compare $option "-func"] == 0 } { set argv [lreplace $argv 0 0] set func [lindex $argv 0] } \ elseif { [string compare $option "-A"] == 0 } { set argv [lreplace $argv 0 0]

76

2. Introduction to Tcl/Tk set A [lindex $argv 0] } \ elseif { [string compare $option "-w"] == 0 } { set argv [lreplace $argv 0 0] set w [lindex $argv 0] } \ elseif { [string compare $option "-y0"] == 0 } { set argv [lreplace $argv 0 0] set y0 [lindex $argv 0] } \ elseif { [string compare $option "-tstop"] == 0 } { set argv [lreplace $argv 0 0] set tstop [lindex $argv 0] } \ elseif { [string compare $option "-dt"] == 0 } { set argv [lreplace $argv 0 0] set dt [lindex $argv 0] } \ elseif { [string compare $option "-case"] == 0 } { set argv [lreplace $argv 0 0] set case [lindex $argv 0] } \ else { puts "$argv0: invalid option \"$option\""; exit 1 } set argv [lreplace $argv 0 0]; # remove the option’s value } # change current working directory: set dir $case if { [file isdirectory $dir] != 0 } { file delete -force $dir } file mkdir $dir cd $dir # make input file to the program: set F [open "$case.i" w] # write multi-line string with variable substitution: puts $F " $m $b $c $func $A $w $y0 $tstop $dt " close $F # run simulator: set cmd "oscillator < $case.i"; # command to run # this one works: eval exec $cmd >@stdout exec sh -c $cmd >@stdout; # safe, but tied to Unix # make gnuplot script: set F [open "$case.gnuplot" w]

2.3. Automating Simulation and Visualization

77

# write multi-line string with variable substitution: puts $F " set title ’$case: m=$m b=$b c=$c f(y)=$func A=$A w=$w y0=$y0 dt=$dt’; set size ratio 0.3 1.5, 1.0; # define the postscript output format: set term postscript eps monochrome dashed ’Times-Roman’ 28; # output file containing the plot: set output ’$case.ps’; # basic plot command: plot ’sim.dat’ title ’y(t)’ with lines; # make a plot in PNG format as well: set term png small color; set output ’$case.png’; plot ’sim.dat’ title ’y(t)’ with lines; " close $F # make plot: set cmd "gnuplot $case.gnuplot"; exec sh -c $cmd >@stdout

# command to run

The complete source code appears in src/tcl/simviz1.tcl. 2.3.2

Dissection

The first three lines ensure that the script is interpreted by the first Tcl interpreter (tclsh) found in your path, in the case you execute the script by writing just the name of the script file. When writing set m 1.0, m is set equal to the string 1.0. In other words, set m 1.0 and set m "1.0" are equivalent. Roughly speaking, all expressions and variables in Tcl are strings. You need to enclose a string in double quotes only if it contains whitespaces. Parsing the command line can be done in various ways. Here, we look at the first command-line argument, check if it is an option, and if so, we remove the argument from the argv list using the lreplace command. The first command-line argument is now the value of the variable associated with the current option. After having loaded this value into the relevant variable we remove the argument so that the next option can be investigated by looking at the first command-line argument. The recipe is summarized as follows. while { [llength $argv] > 1 } { set option [lindex $argv 0] if { [string compare $option "-m"] == 0 } { # remove 1st command-line arg.: set argv [lreplace $argv 0 0]; # assign (the new) 1st command-line arg. to m: set m [lindex $argv 0] } \ elseif { [string compare $option "-b"] == 0 } { set argv [lreplace $argv 0 0] set b [lindex $argv 0] } \

78

2. Introduction to Tcl/Tk ... else { puts "$argv0: invalid option \"$option\""; exit 1 } set argv [lreplace $argv 0 0]; # remove the option’s value }

The while loop works in the same way as the if procedure; as long as the first argument (enclosed in curly braces) is true, while keeps on executing the second argument as a Tcl script. Checking if a directory exists and removing it, including all its files, goes like this: set dir $case if { [file isdirectory $dir] != 0 } { file delete -force $dir }

File and directory manipulations are performed with the file command. Creation of new directories is enabled by file mkdir: file mkdir $dir cd $dir

Before running the oscillator code, we need to create an input file to the program: set F [open "$case.i" w] # write multi-line string with variable substitution: puts $F " $m ... " close $F

We are now ready to run the simulator. Usually, it is convenient to store the command to be executed in a local variable: set cmd "oscillator < $case.i";

# command to run

The “official” Tcl command for running applications or operating system commands is exec, e.g., exec $cmd

However, this statement leads to an error message because the whole cmd string is taken as the program name (which of course then does not exist). The following construction works in a cross-platform fashion: eval exec $cmd

although the safest way of interpreting the command-line arguments to an application correctly is to run it directly under Unix:

2.4. Frequently Encountered Tasks

79

exec sh -c $cmd

This is the approach followed here, with the disadvantage of not being a crossplatform valid command. It is important that the command to be executed is stored in a string when using the exec sh -c syntax. We also want the output from the exec process to be displayed in the terminal window (standard output): exec sh -c $cmd >@stdout

A similar problem with a straight exec appears in many other contexts, e.g., exec rm *.o will not work [?, p. 114] – the simplest working form is exec sh -c rm *.o (valid on Unix only, of course). The final part of the simviz1.tcl script is devoted to writing a Gnuplot script and running Gnuplot. Again a multi-line string with embedded variables (variable substitution) is the key tool to write the file in a clear and readable fashion.

2.4

Frequently Encountered Tasks

The next pages provides a kind of quick reference, with embedded examples, for many common tasks encountered when developing Tcl scripts. Topics to be covered include – file reading and writing, – (multi-line) output with format control, – executing other programs, – working with lists and arrays (hashes), – splitting, joining, searching, and replacing text, – writing and calling Tcl procedures, – checking a file’s type, size, and age, – listing and removing files, – creating and removing directories, – moving to directories, traversing directory trees, – measuring CPU time, – building Tcl packages, – working with regular expressions.

80

2. Introduction to Tcl/Tk

2.4.1

File Reading and Writing

set infilename ".myprog.cpp" set INFILE [open $infilename r] ; # open for reading set filestr [read $INFILE]; # load file into a string set lines [split $filestr "\n"]; # split string into list of lines # alternative reading, line by line: while {[gets $INFILE line] >= 0} { # process $line } close $INFILE set outfilename ".myprog2.cpp" set OUTFILE [open $outfilename w]; # open for writing set line_no 0; # count the line number in the output file foreach line $lines { incr line_no puts $OUTFILE "$line_no: $line" } close $OUTFILE set filename $outfilename set OUTFILE [open $filename a]; # open for appending # multi-line output with variable substitution: puts $OUTFILE \ "/* This file, \"$outfilename\", is a version of \"$infilename\" where each line is numbered. */" # puts -nonewline avoids automatic appending of \n close $OUTFILE

2.4.2

Running an Application

Suppose we want to run a stand-alone application myprog with some commandline arguments. A reliable (but Unix-specific) approach is to store the command to be run in a string and then execute the string by an exec sh -c command: set cmd "myprog -c file.1 -p -f -q" # exec executes $cmd under the operating system, # output is directed to the file res exec sh -c $cmd > res # output from myprog appears in the terminal window: exec sh -c $cmd >@stdout

Redirection of the output from the application into a list of lines is straightforward: set cmd "myprog -c file.1 -p -f -q" set res [ exec sh -c $cmd ]

The exec command does not accept return of non-zero values from applications or writing of messages on standard error. If this happens, one should enclose exec in a catch statement:

2.4. Frequently Encountered Tasks

81

if [catch { exec sh -c $cmd } result] { puts "Trouble with $cmd,\nmessage=$result" } else { # ok }

Interactive applications can be run directly from a script using pipes. Here is an example where we open a pipe to the interactive Gnuplot program, ask it to draw a sine function, then wait 10 seconds before we quit and close the plot window: set PIPE [open "| gnuplot" w] puts $PIPE "set xrange [0:10]; set yrange [-2:2]" puts $PIPE "plot sin(x)" close $PIPE

2.4.3

List Operations

Two of the most common ways of creating lists are exemplified as follows: # make a list of words: set arglist [list $myarg1 "displacement" tmp.ps] # push an item onto a list: set myvar2 $somevar lappend arglist $myvar2

Extracting elements from a list is done through indexing: # extract elements form a list: set filename [lindex $arglist 0]; # first(0) entry set plottitle [lindex $arglist 1] set ppsfile [lindex $arglist 2]

The standard way of traversing a list reads # traverse a list: foreach entry $arglist { puts "entry is $entry" }

One can also traverse a list using a plain C-like for loop over the list indices: for {set i 0} {$i < [llength $arglist]} {incr i} { puts "entry is [lindex $arglist $i]" }

Sorting the entries in a list is done with the Tcllsort! command: set newlist [ lsort $mylist ]

Options to lsort control the sort criterium, for instance, -integer, -real, or -decreasing. One can also provide one’s own comparison function as a procedure:

82

2. Introduction to Tcl/Tk proc ignorecase_sort { a b } { set a [ string tolower $a ] set b [ string tolower $b ] return [ string compare $a $b ] # or shorter (the result of the last statement is returned): # string compare $a $b } set newlist [ lsort -command ignorecase_sort $mylist ]

2.4.4

Associative Array Operations

The term “array” in Tcl terminology usually means associative array or hash, that is, an array where the index can be an arbitrary text4 . For example, all variables that can be specified on the command line could be placed in an associative array, with the name of the variable as key: set cmlargs(-m) 1.2 set cmlargs(-tstop) 6.0

This allows for easy processing of a large number of command-line arguments and corresponding script variables: # initialize an array in a one-line operation: array set cmlargs [list -tstop 6.0 -m 1.2] # initialize a list of (simulated) command-line arguments: set myarg [ list "-myopt" 1.2 "-m" 9.8 "-tstop" 6.1 ] # run through each command-line argument: set arg_counter 0 set nmyargs [llength $myarg] while { $arg_counter < $nmyargs } { set option [lindex $myarg $arg_counter ] if { [string length [array names cmlargs $option]] > 0 } { incr arg_counter set value [lindex $myarg $arg_counter ] set cmlargs($option) $value } else { error "The option $option is not registered"; } incr arg_counter } # print the array: foreach item [ array names cmlargs ] { puts "cmlargs($item)=$cmlargs($item)" } 4

The Python counterpart is called dictionary.

2.4. Frequently Encountered Tasks

2.4.5

83

Splitting and Joining Text

Splitting of a string into substrings is performed with the split command. Its second argument contains the split characters, in this example a single whitespace: set line1 "iteration 12: eps= 1.245E-05" set words1 [split $line1 " "]

The words1 list contains the following substrings: # # # # # # #

words1 words1 words1 words1 words1 words1 words1

entry entry entry entry entry entry entry

0 1 2 3 4 5 6

is is is is is is is

"iteration" "12:" "" "" "" "eps=" "1.245E-05"

We must emphasize that Tcl’s split is less general than the similar split commands in Perl and Python, which can split with respect to a regular expression. This can be used to avoid the three empty substrings (entry 2-4), see pages 87 in [?] and 25 in the present document. Empty strings can be avoided by first removing leading and trailing blanks by the string trim command and thereafter using the regsub -all command to replace all multiple whitespaces \s+ by a single space character ’ ’ (see the datatrans1.tcl script for an example). The join command is the reverse of split: set newline1 [join $words1 "#"]

The newline1 string becomes iteration#12:####eps=#1.245E-05

2.4.6

Text Processing

Checking if a string contains a type of text can be done in various ways: # exact string match: is $line equal to "double"? if {[string compare double $line]==0} { # matching with Unix shell-style wildcard notation, # does $line contain double? if {[string match double $line]==0} { # matching with full regular expressions, # does $line contain double? if [ regexp {double} $line ] { # (here, double can be replaced by any valid regular expression)

84

2. Introduction to Tcl/Tk

Substitution of a text regex (in general a regular expression) by another text replacement in a string str is performed with the regsub command in Tcl: regsub -all regex $str replacement str2 set str $str2; # store modified text back in str variable

Omitting the -all option makes regsub substitute only the first occurence of regex in str. If regex starts with - regsub will treat the argument as an option. To avoid this, write -- before regex, e.g., regsub -all -- -myoption $str -youroption str2

Here is a complete substitution example where double is substituted by float everywhere in a file: # make a copy of the old file: set copyfilename "$filename.old~~" file rename -force "$filename" "$copyfilename" # read file into a string: set FILE [open "$copyfilename" r] set lines [read $FILE] close $FILE # substitute all occurrences of float by double: regsub -all float $lines double lines2 # write the modified set of lines to file again: set FILE [open $filename w] puts $FILE $lines2 close $FILE

2.4.7

String Operations

Common ways of constructing strings in Tcl are exemplified below. set s1 "somestring" set s2 somestring set s3 {something} set s4 {no variable substition of e.g. $myvar} set s5 "variable substition of e.g. $myvar" set s6 " Tcl allows multi-line strings "

Strings are concatenated using the append command: append myfile $filename "_tmp" ".dat"

2.4. Frequently Encountered Tasks

85

resulting myfile being case1_tmp.dat if filename is the string case1. Extracting a substring of filename is performed with string range, taking two arguments: the first and the last index of the substring: set teststr 0123456789 set strpart [string range $teststr 0 4] # result: ’01234’ # another example: set strpart [string range $teststr 3 7] # result: ’34567’

Consult Tcl’s man page for the string command, or [?, Ch. 4], for more functionality. 2.4.8

Numerical Expressions

Tcl supports the same mathematical expressions as C, but numerical operations must be performed by the expr procedure. Strings are automatically transformed to numbers when required: set b "1.2"; # b is a string set b 1.2; # Tcl treats b as a string here too set a [ expr 0.5 * $b ]; # b is converted to a real number if { $b < 100 } { puts "$b100" }

In the last test, the number b (1.2) is compared with 100, yielding 1.2 $max } { set max $term } } set r [list $avg $min $max] return $r } set v1 1.1; set v2 5.8; set v3 9; set b 1; set r [ statistics $v1 $v2 $v3 $b ] puts -nonewline "\n\nstatistics: avg=[lindex $r 0], " puts "min=[lindex $r 1], max=[lindex $r 2]"

All variables inside the procedure are by default local. To operate on a variable that is visible outside the procedure, one must use the keyword global: set counter 0 proc myroutine { a b } { global counter incr counter ... } # now counter is 1

Modifying the arguments inside the subroutine, i.e., call by reference, is enabled by the upvar keyword to declare a direct reference to the physical address (here a1 and b1) of an argument:

2.4. Frequently Encountered Tasks

87

proc swap { a b } { upvar $a a1 upvar $b b1 set tmp $a1 set a1 $b1 set b1 $tmp } swap v1 v2;

# swap the values of $v1 and $v2

The call swap $v1 $v2 does not work here; we must send the variables themselves, not their values. Default values of arguments are specified by enclosing the argument and its default value in curly braces: proc default_test { { message "no message" } { file "tmp.tmp" } } { puts "default_test: message=$message, file=$file" } default_test "testing array args" "my.tmp" default_test

The results of the two default_test calls are default_test: message=testing array args, file=my.tmp default_test: message=no message, file=tmp.tmp

Arrays are transferred to procedures as ordinary variables, as shown in the next example. Keyword arguments, also called named arguments, increase the readability and user-friendliness of procedures significantly. A typical example might be displaylist -list $curves -help $explanations -label $leading_text

The displaylist function prints a list, here curves, and corresponding help text, here explanations, in a nice format preceded by a leading text, here leading_text. Python has a built-in mechanism for keyword arguments, while in Perl the effect can be simulated by a hash. A variant of the Perl technique can be implemented in Tcl as well. The idea is to store all the named arguments and their values in an associative array inside the procedure, that is, we work with the variables options(-list), options(-help), and options(-label). First, default values are assigned to the associative array. Thereafter we work our way through the arguments sent to the procedure and search for keys in the associative array and set the corresponding value. Here is a rough example indicating the basic ideas: proc displaylist { args } { # store arguments in an associative array: # set default values of arguments:

88

2. Introduction to Tcl/Tk array set options [list "-list" 0 "-help" 0 "-label" " " ] # traverse the args list, extract option names and values: set arg_counter 0 set nargs [llength $args] while { $arg_counter < $nargs } { set name [lindex $args $arg_counter ] incr arg_counter set value [lindex $args $arg_counter ] # is name an option we know of? foreach option [ array names options ] { if {[ string compare $name $option ] == 0} { set options($name) $value } } # ready for next item in args: incr arg_counter }

Now all the possible arguments to the procedure are available in the options array. It is then easy to write out the contents of options, foreach option [ array names options ] { puts "options($option)=$options($option)" }

as well as writing the two lists in a formatted fashion: puts "\n$options(-label):\n"; set index 0 foreach item $options(-list) { set helptext [lindex $options(-help) $index] puts [format "item %d: %-10s description: %s" \ $index $item $helptext] incr index }

Setting set curves [list curve1 curve2 curve3] set explanations [list "initial shape of u" \ "initial shape of H" \ "shape of u at time=2.5"] set leading_text "pretty print of two lists"

and performing the displaylist call listed eariler, the output becomes options(-label)=pretty print of two lists options(-list)=curve1 curve2 curve3 options(-help)={initial shape of u} \ {initial shape of H} \ {shape of u at time=2.5} pretty print of two lists: item 0: curve1 item 1: curve2 item 2: curve3

description: initial shape of u description: initial shape of H description: shape of u at time=2.5

2.4. Frequently Encountered Tasks

2.4.11

89

Listing of Files in a Directory

A list of all files (say) *.ps and *.gif, where * is interpreted according to Unix shell-style wildcard notation, is easily generated by the glob procedure: set filelist [glob *.ps *.gif]

2.4.12

Testing File Types

Scripts frequently need to check if a file or directory exists. Tcl’s file command is handy in this context, e.g., if [ file isfile $myfile ] { puts "$myfile is a file" } if [ file isdirectory $myfile ] { puts "$myfile is a directory" } if [ file executable $myfile ] { puts "$myfile is executable" }

The file stat function gives more detailed results. The result of file stat is stored in an associative array, here called statvar: file stat $myfile statvar # convert time values to convenient human-readable form: set atime [clock format $statvar(atime)] set mtime [clock format $statvar(mtime)] puts "$myfile has atime=$atime, nmtime=$mtime), \ \n size=$statvar(size) bytes"

We refer to the Tcl man pages for more information about the contents of the associative array returned from file stat. 2.4.13

Copying and Renaming Files

The file command is used for copying and renaming files: file rename -force $myfile "tmp.1" file copy -force myfile $tmpfile

2.4.14

Creating and Moving to Directories

Creating directories is performed with file mkdir, and cd takes care of moving to directories. For instance, set thisdir [pwd]; # extract current working directory set dir $mynewdir if [ file isdirectory $dir] {} else { file mkdir $dir cd $dir } cd; # move to home directory cd $thisdir; # back to original directory

When creating a directory with the file mkdir command, Tcl automatically creates non-existing directories in the path. For example, file mkdir "$env(HOME)/my/new/long/path/to/tcl/stuff"

works well even if the directory my does not exist.

90

2. Introduction to Tcl/Tk

2.4.15

Removing Files and Directories

Single files and directories are removed by the file delete command: file file file file

delete delete delete delete

-force -force -force -force

$myfile $mydirectorytree [glob *.ps *.gif] "$env(HOME)/tcl"

The -force option is required if the directory is non-empty. 2.4.16

Splitting Pathnames

Let fname be a filename containing a possibly long path, e.g., /usr/home/hpl/scripting/tcl/intro/hw2a.tcl

Occasionally, one wants to split this filename into the basename hw2a.tcl and the directory name /usr/home/hpl/scripting/tcl/intro: set basename [file tail $fname] set dirname [file dirname $fname]

2.4.17

Traversing Directory Trees

Recursive traversal of directories trees is not supported by a Tcl command, but the functionality can easily be implemented. Suppose we want to walk through a directory tree and for each file call a user-specified procedure that notifies us if the file is larger than 1 Mb. A possible implementation in Tcl goes as follows. proc find { func dir } { # visit all files in dir # if dir is a directory, call find again # if dir is a regular file, call the user-speficied # function func foreach file [ glob "$dir/*" "$dir/.*" ] { # prevent recursive visiting of current and parent dir: if { [string compare [file tail $file] "." ] != 0 && \ [string compare [file tail $file] ".."] != 0 } { if [ file isdirectory $file ] { # recurse into new directory: find $func $file } elseif [ file isfile $file ] { # file is regular, call user-provided func: $func $file } else { #puts "cannot treat $file" } }

2.4. Frequently Encountered Tasks

91

} } proc writesize { file } { set size [ file size $file ] if { $size > 1000000 } { puts [format "%.2fMb file in %s" \ [expr $size/1000000.0] $file] } } # traverse the home directory tree: find writesize "$env(HOME)"

The code is found in src/tcl/findsize.tcl. 2.4.18

CPU-Time Measurements

To measure the elapsed time in a Tcl script, one can use the clock command: set t0 [clock clicks] # do tasks... set elapsed_time [expr [clock clicks] - t0]

As an alternative to clock clicks, clock seconds provides a coarser measurements of time, in units of seconds. To my knowledge, basic Tcl does not provide information that allows us to compute the CPU time of a script. An analog to the timer functions in Perl and Python (e.g. Chapter 8.10.1 in [?]) is time: set elapsed_time [time { myproc $arg1 $arg2 } $repetitions]

This command returns the elapsed time of running the procedure myproc with two arguments arg1 and arg2 a specified number (repetitions) of times. 2.4.19

Programming with Classes

Tcl has no support for the class concept or object-oriented programming. However, an extension of Tcl, called [incr Tcl] (the Tcl way of expressing Tcl++), offers good support for programming with objects. The Tcl/Tk Tools book [?] has a chapter with an [incr Tcl] tutorial. Electronic documentation is also available, see the doc.html page. 2.4.20

Building and Using Packages

Tcl has support for creating packages, which are the equivalent to modules in Python. Only a brief sketch of the set-up and basic usage is provided here. Standard Tcl code in a package can be collected in a file with a package statement in the heading:

92

2. Introduction to Tcl/Tk package require sometools 2.3 package provide MyMod 1.0

This means that the package MyMod, version 1.0, is dependent on the package sometools, version 2.3.

The various procedures and global variables in a package should be declared inside a namespace to avoid clash with other packages that employ the same procedure or variables names. Suppose our package has a global variable logfile (within the namespace) and two functions set_logfile and another_routine. We can then define a namespace consisting of these three attributes: namespace eval MyMod { variable logfile "" namespace export set_logfile another_routine } proc set_logfile { filename } { variable logfile set logfile filename ... } proc another_routine { } { ... }

Here is an example on using this package: package require MyMod 1.0 MyMod::set_logfile "myfile.log" MyMod::another_routine puts "MyMod::logfile=$MyMod::logfile"

There are two ways to help Tcl find your package. You can either add the directory containing your package to Tcl’s global auto_path variable, lappend auto_path "$env(scripting)/src/tools"

or you can add the directory to the TCLLIBPATH environment variable in your shell’s start-up file: # .bashrc: export TCLLIBPATH=$TCLLIBPATH:$scripting/src/tools # .cshrc: setenv TCLLIBPATH $TCLLIBPATH’:’$scripting/src/tools

2.4. Frequently Encountered Tasks

2.4.21

93

Regular Expressions

Matching regular expressions in Tcl follows this basic syntax: if [ regexp {regex} $string ]

{ ... }

An example is if [ regexp {\.tmp$} $filename ] { puts "$filename has suffix .tmp"

Enclosing the regular expression in curly braces here prevents Tcl from treating $ as a special Tcl symbol. We remark that if the regular expression is stored in a string variable, e.g. myregex, curly braces {$myregex} will not substitute the string variable correctly. One should in such cases write something like set myregex {\.tmp$}; # or set myregex "\\.tmp\$" if [ regexp $myregex $filename ] { puts "$filename has suffix .tmp"

Substitution by means of regular expressions follows the syntax regsub -all {regex} $string {replacement} newstring

The -all means that all occurrences of regex in string are replaced by the regex replacement. Replacement of the first ocurrence only is implied by omitting the -all option. A specific example on regsub might be # change suffix .tmp to .temp: regsub -all {.tmp$} $filename .temp newfilename set filename $newfilename

The syntax of Tcl’s regular expressions follows that of Perl, but not all of Perl’s regular expression options are available in Tcl. The full documentation of the regular expression syntax in Tcl is provided by the man page re_syntax (a link from the man page for regex exists). Remark. Tcl versions prior to 8.1 have a significantly more primitive regex support than Perl and Python. 2.4.22

Exercises

Many of the Python exercises in [?] are well suited for implementation in Tcl as well. A hint for Exercise 3.16 on page 117 in [?] is to find the current date using either exec date (i.e., simply running the Unix date command) or the Tcl command clock (basically clock format [clock clicks]). Look up man date or Tcl’s clock man page to see the various options for extracting the month and year in a desired format. In Exercise 3.13 in [?] one can use the string match command (cf. the man page for string) to check if a filename contains the string tmp.

94

2. Introduction to Tcl/Tk

2.5

GUI Programming with Tcl/Tk

The Tk package was created and is now maintained and further developed by a Tcl-based community. Much of the useful Tk documentation is thus available in a Tcl context. This is the reason why it pays off to learn some Tk with Tcl syntax even if you work with Tk from Perl or Python. Readers planning to write Tk GUIs are therefore encouraged to scan the present section. The text assumes familiarity with Python/Tkinter programming in the examples from Chapter 6 in [?]. Documentation. When programming Tcl/Tk you will certainly need a more complete reference to the programming language than what is offered herein. In the everyday programming the man pages in electronic form are indispensable (see links in doc.html). The textbook [?] by the original developer of Tcl/Tk, John Ousterhout, is technically somewhat outdated, but contains compact and very clear examples on many aspects of Tcl and Tk. As a complete Tcl/Tk reference, the book by Welch [?] is highly recommended. There is also a Tcl/Tk quick reference book [?]. The doc.html page contains links to Web pages with an overview of Tcl/Tk books. Tcl/Tk programming is greatly simplified if you find some examples that can be adapted to parts of your GUI. The present section contains some starting points. In addition, a demo of the most common Tk and the popular Tk extension [incr Widgets] is offered as an example script in src/tcl/demo.tcl, which is a line-by-line counterpart to the demo.py script explained in Chapter 6.3 in [?]. The Tk source code distribution contains no examples on usage of the widgets, but the [incr Widgets] distributions comes with a demos directory, containing a demo script for every megawidget, plus very instructive electronic documentation with many examples. Megawidgets. Tk contains quite primitive widgets, and the programmer’s efficiency is enhanced by utilizing more advanced composite widgets, referred to as megawidgets. One popular Tk extension has been Tix (a link to the Tix home page is available from doc.html). Having downloaded and installed Tix, one can go to the subdirectory demos and start the widget script, which then gives an interactive demonstration of the capabilities of Tix. The Tix package contains numerous useful, sophisticated composite widgets. Unfortunately, the maintenance of Tix and adaption of Tix to newer versions of Tcl receive little attention. After Tcl was extended with classes and object-oriented programming in the Tcl extension [incr Tcl], it became easy to build more advanced composite widgets by programming with classes. This resulted in the [incr Widgets] widget set, which contains widgets of the same type as in Tix. In order to use [incr Widgets] you must have the itcl package installed. Appropriate links to the source code and documentation is provided in doc.html. To see more of the capabilities of [incr Widgets], go to the demonstration subdirectory and run the various scripts (one script for each widget type).

2.5. GUI Programming with Tcl/Tk

95

Another useful extension of the Tk toolkit is BLT, which is of particular interest in scientific computing since it contains a widget for plotting graphs. It also offers drag–and–drop functionality, improved handling of PostScript images in canvas widgets, and a hypertext widget, among other things. The most important BLT widget for a scientist or engineer is the very flexible curve plot widget, which can also be reached in Python from the Pmw.Blt widget. We demonstrate some attractive capabilities of this plotting widget in Chapter 11.1.1 in [?]. The Tcl/Tk scripts referred to in the following are found in the directory src/tcl. 2.5.1

The First Tcl/Tk Encounter

Our first example of a Tcl/Tk GUI is the graphical counterpart to our Scientific Hello World script. Several GUI versions of the Scientific Hello World script are described in Chapter 6.1 in [?], and the present text assumes familiarity with that material. The first version of the GUI is displayed in Figure 6.1 on page 209 in [?] and enables the user to fill in a number in a text field, push a button equals and then view the sine of the written number. This single GUI can be realized by the following Tcl/Tk code: #!/bin/sh # execute the wish in the path \ exec wish -f "$0" ${1+"$@"} label .hwtext -text "Hello, World! The sine of" pack .hwtext -side left set r 1.2; # default entry .r -width 6 -relief sunken -textvariable r pack .r -side left button .compute -text " equals " -command { comp_s } pack .compute -side left proc comp_s { } { global r; global s; set s [ expr sin($r) ] } label .s -textvariable s -width 14 pack .s -side left

The first three lines ensure that the script is run under the wish (window shell) interpreter, which contains the tclsh interpreter plus the functionality of the Tk package. A typical widget, such as the label, is created by the syntax label .hwtext -text "Hello, World! The sine of"

This is a call to the label procedure followed by some arguments. The first argument is the name of the widget, here .hwtext. Note the opening dot in the name – it is required of reasons that we shall come back to. Thereafter one lists the keywords and corresponding values of the options that one wants

96

2. Introduction to Tcl/Tk

to specify. In the case with the present label widget we just set the text, but other options are possible, e.g., label .hwtext -text "Hello, World! The sine of" \ -font "times 18 bold" -foreground blue -background yellow

Packing the widget is done with the pack procedure, which takes the name of the widget as first argument, followed by the common Tk options to pack: pack .hwtext -side left

The next field is a text entry where one can write the argument of the sine function. Defining the text entry is slightly simpler than in the Python/Tkinter counterpart since we in Tcl/Tk are allowed to tie an ordinary Tcl variable to the contents of the text entry: set r 1.2; # default value of the variable r entry .r -width 6 -relief sunken -textvariable r pack .r -side left

The syntax for defining a button, with an associated function to be called when being pressed, reads button .compute -text " equals " -command { comp_s } proc comp_s { } { global r; global s; set s [ expr sin($r) ] }

The comp_s procedure evaluates the sine function and places the result in the global variable s. To display s, we create a label with s, being computed in comp_s, tied to the text in that label: label .s -textvariable s -width 18

We remark that we could define all the widgets first and then pack them with a common pack statement: pack .hwtext .r .compute .s -side left

2.5.2

Binding Events

Binding the event of pressing return in the text entry to computing the sine is enabled by this syntax (hwGUI2.tcl): bind .r comp_s

The main window has name . (dot) so killing the GUI by pressing the characgter ’q’ on the keyboard is implemented like this (hwGUI3.tcl): bind . exit

2.5. GUI Programming with Tcl/Tk

2.5.3

97

Widget Name Hierarchy

Figure 6.4 on page 213 in [?] shows a GUI with a slightly more complicated layout, realized using frames. Putting the label “Hello, World!” in a frame is performed by the following statements in Tcl/Tk: frame pack label pack

.hw .hw -side top .hw.text -text "Hello, World!" .hw.text -side top

The name of the frame is .hw, and the name of the label in this frame is .hw.text. This name is similar to a file path with a dot instead of a slash as delimiter. The name .hw.text means that the label widget is a part of .hw, which is a part of the top widget, the main window, having just the name . (dot). The main window is automatically created by Tcl/Tk. Note that

whether we pack from top to bottom or from left to right does not matter as there is only a question of packing one widget inside a frame. The middle row of widgets in Figure 6.4 in [?] consists of the text entry field, the equals label, and the label holding the result of the sine function. All these widgets are packed from left to right inside a frame .sine: frame pack label set r entry label label pack

.sine .sine -side top .sine.intro -text "The sine of " 1.2; # default value .sine.r -width 6 -relief sunken -textvariable r .sine.eq -text " equals" .sine.s -textvariable s -width 14 .sine.intro .sine.r .sine.eq .sine.s -side left

Note the naming of the widgets; all of them are subwidgets of the .sine frame and must therefore start with .sine. The complete Tcl/Tk script realizing the GUI in Figure 6.4 in [?] is found in src/tcl/hwGUI5.tcl. In Chapter 6.1 in [?] we demonstrate how the layout can be manipulated using various options to pack. The same options are, of course, available in Tcl/Tk, just the call syntax is different: pack .quit -side top -pady 5 -anchor w -ipadx 30 -ipady 30

The scripts hwGUI1.tcl up to hwGUI9.tcl correspond to the similar Python scripts hwGUI1.py up to hwGUI9.py. 2.5.4

The Similarity of Python/Tkinter and Tcl/Tk

By now, the reader should have notified the syntax differences between Python/Tkinter and Tcl/Tk programming. A typical Tcl/Tk declaration of a widget reads widget_type name -opt1 v1 -opt2 v2 -command myfunc

98

2. Introduction to Tcl/Tk

The similar statement takes the following form in Python: widget_var = widget_type(parent_widget, opt1=v1, opt2=v2, command=myfunc)

The name argument has no counterpart in Python, where we instead pass the parent widget as argument and hold a widget variable to refer to the current widget. 2.5.5

Using Variables in Widget Names

The final GUI version, implemented in hwGUI9.tcl and displayed in Figure 6.6 on page 215 in [?] makes use of a slightly different coding of the names of the widgets such that the Tcl/Tk program looks more like its counterparts in Python. This means that (i) we start with a frame covering all widgets and subframes in our GUI, and (ii) we use a variable as root name of all widget names. These two features make it much easier to embed our set of widgets (i.e. our GUI) in a bigger GUI without needing to edit all the widget names. In Python you can easily build a GUI and include it another GUI; this is only a matter of changing the parent widget of the top frame of the, whereas in Tcl/Tk, the parent widget is normally hardcoded in all widget names. Here is an example on how we introduce variables holding the names of the widgets: set top [ frame .everything ] pack $top -side top set hw [ frame $top.hw ] pack $hw -side top label $hw.text -text "Hello, World!" pack $hw.text -side top -pady 20

Redefining the content of the top variable makes it trivial to embed the GUI in another GUI since the widget names are parameterized (implicitly) via the top variable. The complete hwGUI9.tcl script looks as follows: #!/bin/sh # execute the wish in the path \ exec wish -f "$0" ${1+"$@"} # define a frame for all subframes: set top [ frame .everything ] pack $top -side top # first frame consists of the Hello World text: set font "times 18 bold" set hw [ frame $top.hw ] pack $hw -side top label $hw.text -text "Hello, World!" -font $font; pack $hw.text -side top -pady 20 # second frame consists of the sine computations: set sine [ frame $top.sine ]

2.5. GUI Programming with Tcl/Tk

99

pack $sine -side top -pady 20 -padx 10 label $sine.intro -text "The sine of " set r 1.2; # default entry $sine.r -width 6 -relief sunken -textvariable r button $sine.eq -text " equals" -command comp_s -relief flat label $sine.s -textvariable s -width 14 pack $sine.intro $sine.r $sine.eq $sine.s -side left # third frame consists of a quit button (we drop the # frame and add the button straight into the toplevel widget) button $top.quit -text "Goodbye, GUI World!" -command exit \ -background yellow -foreground blue pack $top.quit -side top -pady 5 -fill x bind $sine.r comp_s proc comp_s { } { global r; global s; set s [ expr sin($r) ] } bind . exit

2.5.6

Configuring Widgets

In Chapter 6.1.6 in [?] we also develop a version of the Scientific Hello World GUI where we do not tie variables to widgets but instead extracts their content with a get function and set their values either with set or configure. The hwGUI7_novar.tcl script is a version of hwGUI7.tcl employing this strategy. After having created an entry, label .sine.intro entry .sine.r

-text "The sine of " -width 6 -relief sunken

we insert a default value: .sine.r insert end 1.2;

# default value

The default value can, of course, also be inserted at construction time of the entry.

When we need to read the contents of the entry (in the comp_s routine) we simply use the get command: set r [ .sine.r get ] set s [ expr sin($r) ]

Updating the contents of the result label is done with configure: .sine.s configure -text $s

where .sine.s is the name of a label widget. This means that the comp_s function takes the form proc comp_s { } { global .sine.r .sine.s; # access global widgets set r [ .sine.r get ] set s [ expr sin($r) ] .sine.s configure -text $s }

100

2. Introduction to Tcl/Tk

2.5.7

The Grid Geometry Manager

The grid geometry manager works as explained in Chapter 6.1.8 in [?], and a Tcl/Tk version of the Scientific Hello World GUI is found in the file hwGUI9_grid.tcl. With a background from these examples and some experience with the Tcl syntax, the reader should have a good starting point for being productive with Tcl/Tk programming, or at least, the reader should be able to look up Tk documentation written with Tcl syntax (for example, the original Tk man pages). The Tk functions and options are almost identical regardless of the language we use. Unfortunately, some small differences exist, and these can be annoying. If a particular Tk construction triggers an error when expressed in another language, you should try to find man pages for the Tk bindings in the language in question and check if the option has a different name or different legal values. Exercise 2.1. Tcl/Tk version of the GUI in Chapter 6.2 in [?]. Write the simviz1.py script from Chapter 6.2 in [?] in Tcl/Tk. You can use the Scientific Hello World GUIs as starting point, but you need to consult the Tk man pages to find the proper syntax for a slider and a picture in Tcl/Tk. Hint: PhotoImage in the Python script is a counterpart to the image procedure in Tk, being an example of different names in the Python and Tcl interfaces to Tk. The construction of the image is also different.  A List of Common Widget Operations. Chapter 6.3 in [?] presents a demo script containing many of the most important widgets in Tk and Tk extensions. A simpler type of demo script has been written in Tcl/Tk and [incr Widgets], see demoGUI.tcl in src/tcl. The demoGUI.tcl script serves as rough overview of common GUI constructions in Tcl/Tk and [incr Widgets].

Bibliography

Index

BLT extension, 94

– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

command-line arguments, 2, 69, 77, 82 – storage in Perl hash, 24 datatrans1.pl, 3 datatrans1.tcl, 71 demoGUI.pl, 62 demoGUI.tcl, 100

functions (Perl), 28 functions (Tcl), 86 grep program – in Perl, 12 headers in scripts, 71 Hello World programs – CGI script in Perl, 62 – Perl, 2 – Perl/Tk GUI, 58 – Tcl/Tk GUI, 95 hw.pl, 2 hw.tcl, 69 hw1.pl.cgi, 63 hw2.pl.cgi, 63 hw2e.pl.cgi, 64 hw3.pl.cgi, 65 hw4.pl.cgi, 66 hwGUI1.pl, 59 hwGUI1.tcl, 95 hwGUI7 novar.tcl, 99 Perl – -x like shell option, 41 – $ , 13 – array operations, 21 – associative arrays, 24 – basename of path, 36 – basic Tk widgets, 59 – binary I/O, 50 102

call by reference, 29 change directory, 35 classes, 39 command-line arguments, 2, 10 copy file, 35 CPU time, 38 create directory tree, 35 create file path, 35 data struct. print, 32 debug data struct., 32 debugger, 39, 40 debugging regular expressions, 44 demoGUI.pl widget overview, 62 diagnostic module, 40 directory path (filename), 36 environment variables, 27 executing OS commands, 19 Fatal module, 40 file globbing, 34 file handles, 19 file type tests, 34 FileHandle, 19 find command, 36 functions, 28 glob, 34 grep, 12 hash operations, 24 headers in scripts, 3 Hello World, 2 Hello World GUI, 58 here document, 12, 18 join text, 25 locating libraries, 64 make directories, 35 make file path, 35 matching regex, 25 modules, 48 move to directory, 35 multi-line output, 11 my operator, 5 my operator, 40

Index

– numerical expressions, 33 – packages, 48 – parenthesis in subroutine calls, 30 – pathname split, 36 – pipe, 20 – pretty print of data struct., 32 – qw operator, 22 – references, 29 – remove directory (tree), 35 – remove file, 35 – rename file, 35 – reverse, 23 – running applications, 19 – Scientific Hello World, 2 – sort, 23 – split text, 25 – stat, 34 – strict module, 40 – subroutines, 28 – substitution (file/text), 26 – substitution (one-line), 20 – system command, 19 – text processing, 25 – time a function, 39 – timing utilities, 38 – Tk programming, 58 – trace facility, 41 – traverse directories, 36 – warnings, 40 – warnings module, 40 – widget tour, 62 simviz1.pl, 8 simviz1.pl.cgi, 67 simviz1.tcl, 75

subroutines, 28 Tcl – arrays, 82 – associative arrays, 82 – basename of path, 90 – BLT extension, 94 – change directory, 89 – command-line arguments, 69, 82

– – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

103

configure (Tk), 99 configuring widgets (Tk), 99 copy file, 89 CPU time, 91 create directory tree, 89 create file path, 89 directory path (filename), 90 environment variables, 85 executing OS commands, 80 file globbing, 89 file type tests, 89 glob, 89 hash operations, 82 headers in scripts, 95 [incr Widgets], 94 itcl, 94 join text, 83 list and array op., 81 make directories, 89 matching regex, 83 modules (packages), 91 move to directory, 89 multi-line output, 80 numerical expressions, 85 packages, 91 pathname split, 90 pipe, 81 procedures, 86 regular expressions, 93 remove directory (tree), 90 remove file, 90 rename file, 89 running applications, 80 sort, 81 split text, 83 substitution (file/text), 84 system command, 80 text processing, 83 time a function, 91 timing utilities, 91 Tix extension, 94 widget tour, 100

wish, 95