1.3.4 Shorter Scripts for Lazy Programmers . . . . . . . . . . . . . . . . 15. 1.3.5 The
Ultimate Goal: Getting Rid of the Script File . . . . . 15. 1.3.6 Perl Has a Grep
Function ...
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 4 7 8 8 10 12 13 14 14 15 15 15 17 17 18 19 20 21 24 25 26 26 27 28 32 33 34 34 34 35 36 36 36 37 38 39
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 . . . . . . . . .
40 40 42 46 49 51 52 52 52 53 54 54 54 56 57 59 60 62 62 63 63 65 67
2 Introduction to Tcl/Tk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 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 . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71 71 71 72 73 73 76 77 77 79 81 82 82 83 84 85 85 86 87
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 . . . . . . . . . . . . . . . . . . . . . . . .
87 88 91 91 91 91 92 92 92 93 93 93 95 95 96 97 98 99 99 100 101 102
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 3Basic Pythonchapter.135 in [?]. If you know the examples in a Python context from Chapters 2Getting Started with Python Scriptingchapter.49 and 3Basic Pythonchapter.135 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.1A Scientific Hello World Scriptsection.50–2.3Gluing Stand-Alone Applicationssection.89 and 3Basic Pythonchapter.135 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 2Getting Started with Python Scriptingchapter.49 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
2
1. Introduction to Perl
and 1.8 deal with graphical user interfaces: standard GUIs and dynamic Web pages, respectively.
1.1
A Scientific Hello World Script
Our first look at Perl will be the Scientific Hello World script from Chapter 2.1A Scientific Hello World Scriptsection.50 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;
1.1. A Scientific Hello World Script
3
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 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.2Working with Files and Datasection.59 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.
4
1. Introduction to Perl
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
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
1.1. A Scientific Hello World Script
5
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 }
In the present script we want to split the line into an array of words, separated by whitespace. The split function performs this task: ($x, $y) = split(’ ’, $line);
# extract x and y value
Having the coordinates $x and $y available, we can transform the y value by calling a function myfunc, $fy = myfunc($y);
# transform y value
One way of printing the transformed coordinate pair to the output file is to apply the printf function: printf(OUTFILE "%g
%12.5e\n", $x, $fy);
The core of a printf call is the format string, which follows the same syntax as in C and Python (and all other languages that supports the C’s printf style for formatting). Perl’s ordinary print function can also be used for writing to files, e.g., print OUTFILE "$x $fy\n"; The myfunc function is defined as sub myfunc { my ($y) = @_; if ($y >= 0.0) { return y**5.0*exp(-$y); } else { return 0.0; } }
Functions are referred to as subroutines in Perl. Their look is typically sub name { # all subroutine arguments are stored in the array @_ ... return ... }
The most striking difference from subprograms in other languages is that the argument list is not a part of the subroutine heading. Instead, all arguments are available in an array @_. The first step is normally to store the arguments in local variables: 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); 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
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 --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 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 $tstop $dt ";
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, $_; 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:
16
1. Introduction to Perl #!/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; 9
# same as shift @_;
See [?] for a precise explanation of the my keyword.
1.4. Frequently Encountered Tasks
29
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);
# usage
sub statistics { # arguments are available in the array @_ my $avg = 0; my $n = 0; # local variables 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); 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
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. 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"; 11
This is the counterpart to Python’s keyword arguments, see page 111Keyword Argumentssubsection.175 in [?].
1.4. Frequently Encountered Tasks
31
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");
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++; } }
32
1. Introduction to Perl
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 list in page 88Lists and Tuplessubsection.148 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
1.4. Frequently Encountered Tasks
33
$VAR1 = ’u1.dat’; $VAR2 = [ [ 0, 0 ], [ ’0.1’, ’1.2’ ], [ ’0.3’, 0 ], [ ’0.5’, ’-1.9’ ] ]; $VAR3 = ’H1.dat’; $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:
34
1. Introduction to Perl 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"
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:
1.4. Frequently Encountered Tasks
35
$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.17
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);
36
1. Introduction to Perl
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");
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/:
1.4. Frequently Encountered Tasks
37
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 #!/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); } } }
38
1. Introduction to Perl
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 54). 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: 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. Frequently Encountered Tasks
1.4.23
39
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.1CPUTime Measurementssubsection.577 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";
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";
40
1. Introduction to Perl
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.1CPU-Time Measurementssubsection.577 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
# warning # error write files: myfile!";
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. Web Interfaces and CGI Programming
1.8.3
67
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 64. 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 ", $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.
68
1. Introduction to Perl
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 => [ { -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.4A General Shell Script Wrapper for CGI Scriptssubsection.421 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
1.8. Web Interfaces and CGI Programming
69
script, which is a counterpart to the CGI script cg/src/python/simviz1.py.cgi from Chapter 7.2Adding Web Interfaces to Scriptssection.425 in [?]. The purpose of this script is to create a Web interface to the oscillator code from Chapter 2.3Gluing Stand-Alone Applicationssection.89 in [?]. The user can type in values of the mathematical parameteres and get a plot of the solution.
2.5. GUI Programming with Tcl/Tk
101
pack $hw.text -side top -pady 20 # second frame consists of the sine computations: set sine [ frame $top.sine ] 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.6An Alternative to Tkinter Variablessubsection.322 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
102
2. Introduction to Tcl/Tk 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 }
2.5.7
The Grid Geometry Manager
The grid geometry manager works as explained in Chapter 6.1.8An Introduction to the Grid Geometry Managersubsection.333 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.2Adding GUIs to Scriptssection.345 in [?]. Write the simviz1.py script from Chapter 6.2Adding GUIs to Scriptssection.345 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.3A List of Common Widget Operationssection.356 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