The midterm questions can be reviewed via this link. Here's a sample script that answers the first question:
#!perl
if ($ENV{'REQUEST_METHOD'} eq "GET") {
@pairs = split(/&/, $ENV{'QUERY_STRING'});
}
elsif ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
@pairs = split(/&/, $buffer);
}
else {
print "Content-type: text/html\n\n";
print <<"end_tag";
<HTML>
<HEAD>
<TITLE>Form Error</TITLE>
</HEAD>
<BODY>
<P><B>The METHOD of your request for this script must be either GET or POST</B>
</BODY>
</HTML>
end_tag
exit;
}
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$name =~ tr/+/ /;
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
if ($in{$name}) {
$in{$name} = $in{$name} . "," . $value;
}
else {
$in{$name} = $value;
}
}
push (@errors, "your first name") unless ($in{'firstname'} =~ /\w+/);
push (@errors, "your last name") unless ($in{'lastname'} =~ /\w+/);
push (@errors, "a valid street address") unless ($in{'street'} =~ /\w+/);
push (@errors, "your city") unless ($in{'city'} =~ /\w+/);
push (@errors, "your state") unless ($in{'state'} =~ /^[a-zA-Z]{2}$/);
push (@errors, "your zip code") unless ($in{'zip'} =~ /^\d{5}$/);
if (@errors) {
print "Content-type: text/html\n\n";
print <<"end_tag";
<HTML>
<HEAD>
<TITLE>Input Error</TITLE>
</HEAD>
<BODY>
<P>Please click your browser's <I>back</I> button and enter:
<UL>
end_tag
foreach $error(@errors) {
print "<LI>$error\n";
}
print <<"end_tag";
</UL>
</BODY>
</HTML>
end_tag
exit;
}
print qq^Content-type: text/html\n\n
<HTML>
<HEAD>
<TITLE>Your Name and Address</TITLE>
</HEAD>
<BODY>
<H1>Your Name and Address</H1>
<P><B>Your first name:</B> $in{'firstname'}
<BR><B>Your last name:</B> $in{'lastname'}
<BR><B>Street address:</B> $in{'street'}
<BR><B>City:</B> $in{'city'}
<BR><B>State:</B> $in{'state'}
<BR><B>Zip:</B> $in{'zip'}
</BODY>
</HTML>
^
And here's a script that answers the second question:
#!perl
use CGI qw(:standard);
print header;
if (param('page') == 1) {
print
start_html('Your Name and Address'),
h1('Your Name and Address'),
br, b('First Name: '), param('firstname'),
br, b('Last Name: '), param('lastname'),
br, b('Street Address: '), param('street'),
br, b('City: '), param('city'),
br, b('State: '), param('state'),
br, b('Zip: '), param('zip'),
start_form,
p, b('What is your phone number? '), textfield('phone'),
hidden(-name=>'firstname', -default=>param('firstname')),
hidden(-name=>'lastname', -default=>param('lastname')),
hidden(-name=>'street', -default=>param('street')),
hidden(-name=>'city', -default=>param('city')),
hidden(-name=>'state', -default=>param('state')),
hidden(-name=>'zip', -default=>param('zip')),
p, submit,
end_form;
}
else {
print
start_html('Your Name, Address, and Phone Number'),
h1('Your Name, Address, and Phone Number'),
br, b('First name: '), param('firstname'),
br, b('Last name: '), param('lastname'),
br, b('Street Address: '), param('street'),
br, b('City: '), param('city'),
br, b('State: '), param('state'),
br, b('Zip: '), param('zip'),
br, b('Phone number: '), param('phone'),
}
print end_html;
To read, modify or create a file, you use the open function. open has the following syntax:
open (filevar, filename);
filevar represents the name you want to use in your Perl script to refer to the file. This is known as a file variable, or more commonly, as a file handle. It can be any sequence of letters, digits, and underscores, as long as the first character is a letter. It is common practice to have a file handle be all in uppercase.
filename represents the name and location of the file on your computer.
There are three different possible file access modes you can use when opening a file. The first is read mode, which makes the contents of the file available for your script to read. Here's an example:
open (MYFILE, 'C:\temp\test.txt');
Note that I put the filename in single quotes. This is important when referring to an MS-DOS directory. If I had used double-quotes, Perl would have read the \ as an escape character telling Perl to ignore the next character. So, it would have tried to open the file C:empest.txt, which is definitely not what we want! Another option is to use forward slashes instead of back slashes - then you can use double-quotes.
You can also use a variable for the filename, like this:
$myfile = 'C:\temp\test.txt'; open (MYFILE, $myfile);
The next mode is write mode, which destroys the current contents of the specified file and overwrites them with the output supplied by your Perl script. If the specified file does not yet exist, it is created. Here's an example:
open (MYFILE, '>C:\temp\test.txt');
The last mode is append mode, which appends the output supplied by your script to the existing contents of the specified file. If the file does not yet exist, it is created.
open (MYFILE, '>>C:\temp\test.txt');
Before using a file you've opened, you should always first check to see if your open attempt has given you the access you want. It may be that you've tried to read a file that doesn't exist, or you've tried to write to a file that your script does not have permission to modify. It's easiest to do this with a simple if or unless statement. If the attempt to open the file in the mode you requested succeeded, the open function will return true. For example:
if (open (MYFILE, 'C:\temp\test.txt')) {
print "File successfully opened for reading.\n";
}
else {
print "Unable to open the file.\n"
}
The following is an example of reading all the lines from a file and printing them to the screen:
if (open (MYFILE, 'C:\temp\test.txt')) {
$line = <MYFILE>;
while ($line ne "") {
print $line;
$line = <MYFILE>;
}
}
else {
print "Unable to open the file.\n"
}
The syntax of $line = <MYFILE> should look very familiar. Instead of reading a line from <STDIN> like we do when getting user input, we're now reading a line of text from the file <MYFILE>. The while loop iterates through each subsequent line of the file until it reaches the end of the file.
We can simplify the foregoing script by assigning <MYFILE> to an array instead of to a scalar. Perl will then read the entire file into the array variable, with each line of the file constituting an element of the array:
if (open (MYFILE, 'C:\temp\test.txt')) {
@lines = <MYFILE>;
foreach $x(@lines) {
print $x;
}
}
else {
print "Unable to open the file.\n"
}
Note that in each of these examples, I did not have to add a linefeed when printing the lines of the file. This is because the linefeeds that existed in the file are preserved when you assign the lines to a scalar or to an array. This means that chomp is still useful, as you'll often want to remove those linefeeds.
Here's an example:
if (open (MYFILE, '>C:\temp\test.txt')) {
print MYFILE "this is a line of text.\n";
}
else {
print "Unable to open the file.\n"
}
After opening the file in write mode, you use a print statement, but you include the file handle to indicate that you want to print to the file, and not to standard output (STDOUT). So far in class we've always printed to STDOUT, which is the default for the print function. That is:
print "hello";
and
print STDOUT "hello";
give exactly the same result.
When running a script in an MS-DOS window, STDOUT is your screen. When running a script via CGI, STDOUT is the user's web browser. By default, STDOUT is always open in write mode.
If you're using a text file to save data from a CGI application, it's important to lock a file when you're writing to it. This is necessary to avoid two or more users trying to write to a file simultaneously, and possibly overwriting each other, resulting in data loss. When you lock a file, you can guarantee that only one user at a time is modifying its contents.
This is done in Perl with the flock function (short for "file lock"). The first thing to note about flock is that it is unavailable on Windows 95 and 98. This is because they are single-user operator systems, so the notion of simultaneous multi-user access is simply not relevant. But flock works fine on Unix and Windows NT. Here is an example:
open(BDAYS,'>>c:\temp\birthdays.txt'); flock(BDAYS,LOCK_EX); print BDAYS "howdy\n"; flock(BDAYS,LOCK_UN); close(BDAYS);
flock accepts two arguments. The first is the file handle of the file you want to lock or unlock. For the second argument, there are three values you can use:
When you're done using a file, you should close the file using the close function. close has the following syntax:
close (filevar);
Files are automatically closed when your script is done executing, or if you use the same file handle to open a different file. However, it's recommended that you always use the close function, as it helps to keep your code clear and understandable. Also, it's strongly recommended that you do not use the same file handle for opening different files in the same script - it's too easy to lose track of which file variable belongs to which file on any given line of code.
The unlink function accepts a list of file names to delete. For example:
@delete = ("C:/temp/test1.txt", "C:/temp/test2.txt");
unlink(@delete);
Note that deleted files are irrevocably gone from your hard drive - you won't be able to retrieve them from the Recycle Bin.
In the above examples we've used an if statement to test the returned result from the open function to see whether the file has been opened successfully. If open fails, you'll probably want to know why. You can find out by using file-test operators.
Page 89 of Teach Yourself Perl lists most of the file test operators. The most commonly used ones are:
If you're on a UNIX system, these are also commonly used:
Here's one of the previous examples, enhanced with a file test operator:
$myfile = 'C:\temp\test.txt';
if (open (MYFILE, $myfile)) {
@lines = <MYFILE>;
foreach $x(@lines) {
print $x;
}
}
else {
if (-e $myfile) {
print "The file $myfile exists, but cannot be opened.\n"
}
else {
print "The file $myfile does not exist.\n";
}
}
If you've done programming in other languages, you may be used to having the ability to define your own functions. In Perl, you can use subroutines for this purpose. Subroutines and user-defined functions are the same thing in Perl. Note that some Perl programmers, such as the author of our textbook, call them functions. I prefer to call them subroutines, so that we don't confuse them with built-in Perl functions, like print.
Subroutines are useful for two purposes:
Here's a simple example of a using a subroutine:
$total = 0;
&getnumbers;
foreach $number(@numbers) {
$total += $number;
}
print "the total is $total\n";
sub getnumbers {
@numbers = (1,2,3,4,5);
}
Note that you can have a subroutine and a variable with the same name - e.g. $something and &something - Perl will not confuse them. However, it is not recommended that you do this - although Perl won't confuse them, it's easy for humans to confuse them.
For organizing your code, you should group your subroutines together either at the beginning or end of your script. I put mine at the end, but you'll sometimes see others put them at the beginning.
In Perl, the last expression evaluated in a subroutine is considered the return value. This is the value that the subroutine passes back to the main body of the script. For example:
$total = 0;
@numbers = &getnumbers;
foreach $number(@numbers) {
$total += $number;
}
print "the total is $total\n";
sub getnumbers {
$nums = "1,2,3,4,5";
split(/,/,$nums);
}
If you call a subroutine several times in a script, this is a useful feature, as it allows you to easily assign the results of the subroutine to a different variable each time you call it. Be careful using this feature, however, as you may not get what you expect if you have a conditional statement at the end of the subroutine. For example, if you have an if statement at the end of your subroutine and its condition is not met, then any values assignments within the if statement will not occur.
It's often best to be explicit and use the return statement in your subroutine. This allows you to specify what value gets returned, and to cease the execution of your subroutine. For example:
$name = "Mike";
print (&checkName);
sub checkName {
if ($name eq "Mike") {
return("your name is Mike");
}
return("your name is not Mike");
}
Perl allows you to define a variable that exists only within your subroutine. You can have a variable with the same name outside of the subroutine, and the two will not interfere with each other. For example:
$name = "Mike";
print "Your name is $name\n";
&newName;
sub newName {
my($name);
$name = "Joe";
print "His name is $name\n";
}
print "Your name is still $name\n";
my was introduced in Perl 5. Any my variables exist only within the statement block in which they are defined. Their values are not accessible anywhere outside of the statement block. Although they are most commonly used in subroutines, it's important to note that they can be used in other statement blocks as well. For example, you can have a my variable in a foreach loop.
An alternative to my (which is also supported in older versions of Perl) is local. The two are similar, except that a local variable is also available to any subroutines called by the subroutine where the local variable is defined.
Like many Perl functions, you can pass arguments to a subroutine. Here's an example that's similar to the start_html function that you've seen in CGI.pm. By passing arguments to the subroutine, you can have some flexibility in controlling the details of the HTML each time you call the subroutine:
$errorTitle = "Error";
$textColor = "black";
$bgColor = "white";
&HTMLTop($errorTitle, $textColor, $bgColor);
print "<P>Welcome to my web page\n";
sub HTMLTop {
local($title, $text, $bg) = @_;
print "Content-type: text/html\n\n";
print "<HTML>\n";
print "<HEAD>\n";
print "<TITLE>$title</TITLE>";
print "</HEAD>\n";
print "<BODY TEXT=$text BGCOLOR=$bg>\n";
}
The system variable @_ is a list of all the arguments passed to a subroutine. You can assign its elements to scalar variables just as you would with an ordinary array.
If you have lines of code that you need to use repeatedly, it makes sense to store those lines of code in subroutines. If you want those subroutines to be available for use in more than one Perl script, then it makes sense to store them in a file where your Perl scripts can access them as needed. Such files are called Perl library files.
The following is a two line script that uses the require function:
require "other.pl"; print "goodbye\n";
This script will look for a file named "other.pl" (where in the filesystem it looks will be discussed in a moment, but in this case, other.pl is in the same directory). If it finds the file, the statements within it are immediately executed. In this case, the file other.pl contains only one statement:
print "hello\n";
Like a subroutine call, the last expression evaluated inside a file called by require is the return value. If this return value evaluates to zero, your Perl script will abort with an error message (in this case, the print statement is successful and returns true, so there are no problems).
Here's another simple example of the require function, but this one illustrates its usefulness in building libraries of re-usable subroutines. The subroutine "hello" is contained in the file other.pl, but is called from this script:
require "other.pl"; &hello;
Here is other.pl:
sub hello {
print "howdy!";
}
1;
Note that the last line of code is the number one. Since the subroutine "hello" does not return a value until it is called, it does not return a value to the require function. To avoid having our script halt on an error, we can simply put a non-zero number at the end of the script. The number one is a non-zero value, so the condition of the require function is satisfied. This is a commonly used technique for dealing with this aspect of the require function.
When using the require function, you do not specify a directory name for the file - you only provide a filename. Perl will look for the file in the list of directories specified in the system variable @INC. For the ActiveState Windows version of Perl, the default value of @INC contains the following:
To access a file in another location, you will need to add its directory to the list in @INC. This is most efficiently done with the unshift function:
unshift(@INC,'C:\Temp');
foreach $dir(@INC){
print "$dir\n";
}
It's safer to use unshift instead of push. This is because the require function will cease searching for a script with a matching name as soon as it finds the first match. If you happen to create a script that has the same name as one already in the Perl lib directory, and you add your directory to @INC using push, Perl will see the one in the lib directory first, and use it instead of yours.
It's been my experience with some ISPs that my CGI directory is not always seen as the current working directory when my script is called by a web page. To be safe, always add the appropriate directory names to @INC, even if the files you're calling are in the same directory as the script using the require function.
I created my own library file, which I named mtt-lib.pl, that contains subroutines I use frequently in my CGI scripts. Let's take a look at it. Note that I created this file before CGI.pm existed - most of its functionality has been superseded by CGI.pm.
Perl scripts designed for use by the require function have largely been supplanted by Perl modules. One that was extremely popular for several years, and is still in widespread use, is cgi-lib.pl. This does many of the same things as CGI.pm. Also, it can process HTML form-based file uploads. Here is the cgi-lib.pl home page. A few other Perl library files can be found at the freecode.com web site.
If you're doing the Calendar of Events project, work on tasks listed in Section IV of the Calendar of Events Functional Specification (the Event Viewing Page).