Still More Perl
- Functions
- File Input and Output
- More CGI
Functions
- A function definition includes the function's header and a block of code that describes its behavior
- This is a function to print an HTML header
sub header { my $title = shift @_; print << END_OF_HEADER; Content-Type: text/html<html> <head> <title>$title</title> </head> <body> END_OF_HEADER; }
Function Parameters
- Function parameters are communicated through a special array @_
- It is common practice to assign the parameter array to a list of variables in the first statement of a function body
my ($x, $y, ...) = @_;
- If there are only few, or a variable number of parameters, we often use the shift operator to read the arguments
my $title = shift @_; my $title = shift; # @_ is the implied default
- Local variables should be declared using my (otherwise they are globally accessible)
Return Value
- Unlike in most other languages, function return the value of the last expression, if not specified otherwise
sub square { my $x = shift @_; $x * $x; }
- Explicitly return a value with return; this should be the preferred way
sub grade { my $marks = shift @_; if ($marks >= 90) { return "A+"; } elsif ($marks >= 85) { return "A"; } elsif ($marks >= 80) { return "A-"; } elsif ... # other cases } }
- Here is how these functions would be invoked
$result = square($value); print "Square of $value is $result\n";$grade = &grade(95); print "Final grade: $grade\n";
Functions and Libraries
- Functions can be described in a separate file, and imported into many scripts (reuse!)
- Library files often carry the extension ".lib"
- File should end with a line that indicates the library was successfully loaded
1;
- Functions can also be declared in a Perl module
Function Libraries (Example)
- Suppose we wanted to define commonly used code to create the header and footer of a web page in a library
- Store the following in the file header_footer.lib
sub Header { my $title = shift(@_); print "Content-Type: text/html\n\n"; print "<html><head><title>"; print "$title"; print "</title></head><body>\n"; }sub Footer { print "\n</body></html>"; }
1; # OK
Importing a Library
- A require statement tells the interpreter to incorporate the file specified into the current execution context
require "header_footer.lib";
- All global variables and functions in the library become visible in the execution context
- Imported functions can be invoked just as locally defined ones
Header(); print << END_OF_BODY; <h1 align="center">Order confirmation</h1> Thank you for your order. Your confirmation number is $orderId. END_OF_BODY Footer();
Files and Directories
- Operating on a file requires a file handle
- By convention, file handles are in upper-case
FILE
- Opening a file
open(FILE, "/home/4user3/~bob/orders.log"); # read open(FILE, "</home/4user3/~bob/orders.log"); # read (< is optional) open(FILE, ">/home/4user3/~bob/orders.log"); # write open(FILE, ">>/home/4user3/~bob/orders.log"); # append
- Closing a file
close(FILE);
Error Handlers
- When opening a file, you should be prepared to deal with errors (eg if you cannot read from/write to the file)
- Use an error handler to catch these exceptions
open(FILE, ">>/home/4user3/~bob/orders.log") || &errorMessage("cannot open file orders.log");sub errorMessage { my $message = shift @_; print "Content-Type: text/html\n\n"; print $message; exit; # exit from the script }
- This is a common idiom; here is how it works
- Call to open returns 0 if it fails
- In that case, the second part of the OR condition is executed
- Whether to include the "Content-Type" line depends on your script (could use a flag and test it)
Writing to a File
- Use the file handle as first parameter to print
open(FILE, ">/home/4user3/~bob/orders.log"); print FILE "Order $oid was shipped $date.\n"; # writes to FILE print "Your order was shipped today.\n";
Gaining Exclusive Access
- When you write to a shared file, it is important to gain exclusive access to it
- Perl provides the flock function for this purpose (it makes use of a shared lock file)
$LOCK = 2; $UNLOCK = 8;open(FILE, ">>/home/4user3/~bob/orders.log"); flock(FILE, $LOCK); print FILE"Order 123 shipped 02/18/2002."; flock(FILE, $UNLOCK);
Reading from a File
- You can read from a file in multiple ways
- Read file one line at a time
- Read contents of the file into an array
- Read contents into a string
- Read file one line at a time
open(FILE, "/home/4user3/~bob/orders.log"); while (<FILE>) { # process the line # each line will be stored in the variable $_ } close(FILE);
- Read contents of the file into an array
open(FILE, "/home/4user3/~bob/orders.log"); @messages = <FILE>; close(FILE);
- Sometimes you may want to read the contents into a string
undef $/; # special variable that defines end-of-line character open(FILE, "/home/4user3/~bob/orders.log"); $log = <FILE>; close(FILE);
- The first technique is generally preferrable
- Files can be large (memory-efficient)
- This technique allows processing of input as it is read
Renaming and Removing a File
- Renaming a file
$oldName = "orders.log"; $newName = "messages.log"; rename($oldName, $newName);
- Removing a file
$fileName = "orders.log"; unlink($fileName);
Checking the Status of a File
- You may want to check the status of a file (e.g. if it exists) before trying to access it
if (-option fileName) { ... }
- Options:
- -e to check if file exists
- -r to check for read permission
- -x to check for execute permission
- -w to check for write permission
- -d to check if a directory
if (-d "some/directory")
More CGI
- Decoding Form Data
- Using CGI.pm
Decoding Form Data
- In order to access form parameters, we need to decode the data sent to the CGI script; the decoding algorithm is as follows
- Read the query string from $ENV{QUERY_STRING}
- If $ENV{REQUEST_METHOD} is POST, determine length of request body and read from STDIN
- Append data to data read from query string, if present
- Split the result on the "&" character
- Split each resulting name-value pair on the "=" character
- Decode the URL-encoded characters in name and value
- Associate each name with its value(s)
Rolling Your Own
- Define a library, ParseInput.lib, to implement the algorithm
my $query;# Read data from GET if ($ENV{'REQUEST_METHOD'} eq 'GET') { $query = $ENV{'QUERY_STRING'}; }
# Read data from POST elsif ($ENV{'REQUEST_METHOD'} eq 'POST') { read(STDIN, $query, $ENV{'CONTENT_LENGTH'}); $query = $ENV{'QUERY_STRING'} . "&" . $query if ($ENV{'QUERY_STRING'}); }
# Parse the query foreach $_ (split(/&/, $query)) { tr/+/ /; # we haven't met yet (see pattern matching) s/\%(..)/pack(C, hex($1))/ge; ($_, $param) = split (/=/, $_); if ($param{$_}) { $param{$_} .= ",$param"; } else { $param{$_} = $param; } }
1;
Using Our Library
require "ParseInput.lib";# Print mime header print "Content-type: text/html\n\n";
# Check input parameters unless ($param{'user'} && $param{'email'}) { print "Must fill out user and email"; exit; }
# Process form print <<END_OF_UPDATE; <h1>Success</h1> Thanks for your input. The following update has been made: <p> <table border> <tr> <td>User: <td>$param{'user'} <tr> <td>Email: <td>$param{'email'} </table> END_OF_UPDATE
CGI.pm and its Uses
- Handling input with CGI.pm
- Get information about the environment
- Get form input
- Handle file uploads
- Generate output with CGI.pm
- Generate headers
- Generate HTML
- Handling errors
- Trap errors (alternative to die operator)
Standard and Object-Oriented Syntax
- CGI.pm upports two styles of usage:
- Standard interface
- Object-oriented interface
- CGI.pm is an object-oriented module
- But it can export its methods into main name space
- Example of object-oriented syntax
use CGI;my $q = new CGI; my $name = $q->param("name"); print $q->header("text/html"), $q->start_html("Welcome"), $q->p("Welcome back, $name!"), $q->end_html;
- Standard syntax looks like this
use CGI qw( :standard );my $name = param("name"); print header("text/html"), start_html("Welcome"), p("Welcome back, $name!"), end_html;
Getting Information About the Environment
- Makes envrionment variables availabe via method calls
- Method names derived from name of the environment variable
content_type CONTENT_TYPE path_info PATH_INFO query_string QUERY_STRING request_method REQUEST_METHOD script_name SCRIPT_NAME Accept HTTP_ACCEPT # actually uppercase
- For example, to get the additional path information, use
my $path = $q->path_info;
Accessing Form Parameters
- One of the most common reasons for using CGI.pm
- The param method allows you to access the parameters submitted to your script
- Without parameters, param returns all parameter names
foreach $name ($q->param) { print "$name"; }
- When supplied with a parameter name, it will return its value
$email = $q->param("email"); print "Email: $email";
- For parameters with multiple values, param returns
- just the first value (if called in a scalar context)
- a list of all values (if called in a list context)
$color = $q->param("color"); # just the first one @colors = $q->param("color"); # list of all colors
Modifying Parameters
- CGI.pm also lets you add/modify/delete the value of parameters
- Specifying default values
- Converting values to a common format
$q->param(sku => "102030"); $q->param(interests => "music", "dining", "sport"); $q->delete("age"); $q->delete_all;
Modifying Parameters (Example)
- One good application is to support image and regular submit buttons interchangeably
- With image buttons, the browser sends two separate variables, name.x and name.y
- For regular submit buttons, their name is sent
- You may decide to replace submit buttons by image buttons later
- The code below sets a form parameter for each parameter whose name ends in .x
foreach ($q->param) { $q->param($1 => 1) if /(.*)\.x/; # see pattern matching }