Introduction to Perl

Written by Rusty Nejdl

Background

Perl is a language which attempts to let you do whatever you want. It assumes more that you know what you are doing. If you want to see the details, okay. If you don't, that's okay too. If you want deal with errors, okay, if not, also okay. If you want to write horribly confusing and ugly code, well, that's your own choice.

Perl is not a very structured language. It does some stuff for you. For example, you do not need to declare variables, do memory management, have a "main()" function. There isn't much of a distinction between ints, doubles, char, pointers (er, references), or strings: perl is generally considered to lack strong type checking.

Perl is an interpetted language. The Perl interpetter looks at the source code, and figures out what to do from there.

It is really really easy to write bad code and requires a lot of discipline to write good code.

The Print Statement

The most basic thing you're going to want a perl script to do is tell you things--that is, usually, send output to the terminal. Here's a fully functioning one-line Perl script:

#!/usr/bin/perl

print "1 + 2 = 3\n";

Variables

This perl script does the same as the above, but uses variables instead.

#!/usr/bin/perl

$x = 1;
$y = 2;
$z = 3;

print "$x + $y = $z\n";

Any variables in quotes will be interpolated--that is, they'll be translated into the data they contain. So when you run that program, you still get:

1 + 2 = 3

The most basic of all first program scripts:

#!/usr/bin/perl

$t	= "Hello World!";
print   "$t\n";

Operator Basics

So now we know how to assign values to variables, and print them out. Next, we learn how to manipulate the data.

Some of the most basic things that we'd want to do to some numbers would be mathematical operations - addition, subtraction, multiplication, etc... perl implements them in exactly the way you'd expect it to:

$firstNum = 1;
$secondNum = 30;
$result = $firstNum + $secondNum; 
# $result now contains 31
print "$result\n"; 

The example above is simple - some addition, and some printing. Despite the fact that scalars can contain either numbers or text, there are different operators that you can use on scalars, based on what they contain. Adding two numbers is different from adding two strings.

$firstNum = 1;
$secondNum = 30;
$numResult = $firstNum + $secondNum;
# $numResult now contains 31
$strResult = $firstNum . $secondNum;
# $strResult now contains 130
print "$numResult $strResult \n";

The '+' operator is familiar - it simply finds the sum of the two arguments. The '.' operator is new - it's called the string concatenation operator, and it combines its arguments into one string.

Operators won't work if you put them in between double quotes (" "), so if you want to include them in an output statement, you should do something like this:

$firstNum = 1;
$secondNum = 30;
print $firstNum + $secondNum, " ", $firstNum . $secondNum, "\n";

Both of the above code snippets will produce

31 130

Getting Input From the User

Variables give your program flexibility. You can now change that program so it adds any two numbers you type in. The way to get input from the keyboard into a running program is with a line like this:

$x = <>;

When your program encounters a line like that, it will stop and wait for numbers (or letters, or anything else) to be typed in at the keyboard, until RETURN is hit. Then it will assign everything you just typed into $x, and the program can then play with it. So this program

#!/usr/bin/perl

print "Type in a number: ";
$x = <>;
chop($x);
print "Type in a number: ";
$y = <>;
chop($y);

$z = $x + $y;

print "$x + $y = $z\n";

will wait for two numbers, add them up, and then print the result. The one strange thing in that program is the chop command. When the value of $x comes in from the keyboard, it's stored along with the \n character from hitting RETURN. So if you typed 35 and then hit RETURN, the value of $x became

35

Chop just chops the last character off of a variable. Its most common use is for getting rid of that nasty "\n"--which, believe it or not, counts as one character. (Even more to the point: it counts as a charcter, which means "35\n" is not anything like "35". You can't add with it, you can't print it without getting an unwanted line break, and it won't do anything else you might expect. Chopping is good Perl practice.)

Subroutines

See how, in the above program, the first six lines basically do the same thing twice? Computer programs are designed to perform repetitive tasks for you; if you find yourself typing the same thing over and over again in a program, it's a good clue that you should condense your program with a subroutine.

A subroutine is like a command you define yourself. So let's make a version of the program that uses a subroutine called getnumber that gets a number from the keyboard:

#!/usr/bin/perl

$x = &getnumber;
$y = &getnumber;
$z = $x + $y;

print "$x + $y = $z\n";

# The program ends there...
# the subroutine is just tacked on at the end:

sub getnumber {
print "Type in a number: ";
$number = <>;
chop($number);
$number;
}

Everything inside the curly brackets is the subroutine. Now that section of the program will be run every time you use the command

&getnumber;

in your program. In this case, the subroutine "getnumber" returns a variable which is then assigned to $x and $y. Not all subroutines are designed to return variables. This one returns the contents of $number because of the line

$number;

So the command

$x = &getnumber;

first runs the subroutine, and then, when it sees the line $number; it exits the subroutine, and gives $x the value of $number.

But like I said, not all subroutines are designed to return a value. For instance, you could have a subroutine that just printed out a standard warning:

sub warning {
print "WARNING: The program is about to ask you to type in a number!\n";
}

So the line

&warning; 

will now print out

WARNING: The program is about to ask you to type in a number!

every time you use it.

You can stick the code for subroutines anywhere you want in your program, but the traditional place is to group them all together at the end of the program, after the main section.

While

Right now the program just executes once and then kicks you out. Many times you'll want your program to do something over and over again; you can do this with a control structure that creates a "loop". There are many types of control structures; we'll start you off with a simple one called while. It basically means: do this set of commands while this statement is true. For instance, here's a code fragment that loops over and over as long as you type in the number "15":

$x = 15;
while($x == 15) {
$x = &getnumber;
}

Two things to note:

1) $x == 15 means "$x is equal to 15". It's a comparative statement; it should never be confused with $x = 15, which assigns the value 15 to $x. Confusing == and = is one of the most common novice errors; in this case, it would create an infinite loop.

2) The first line of the code fragment sets the value of $x to 15. If you didn't do that, $x wouldn't equal 15 the first time around, so the program would never enter the loop at all, and would never ask for a number.

Here's our adding program, with a loop so you can use it over and over:

#!/usr/bin/perl

$doagain = "yes";

while($doagain eq "yes") {

$x = &getnumber;
$y = &getnumber;
$z = $x + $y;

print "$x + $y = $z\n";

print "Do it again? (yes or no) ";
$doagain = <>;
chop($doagain);

}

(I left out the subroutine to save space here, but you'd have to include it if you wanted that program to run.) Note that the comparative statement this time uses "eq" rather than "==". == compares for numerical values; eq is for variables made up of letters, numbers, and other characters (called "string" variables.)

Text Manipulation


Split

Sometimes you will have a string that you will need to separate by a certain character. For example, within UNIX the /etc/passwd file looks like this:

[www]:[1:15am]:[/home/rnejdl] > cat /etc/passwd
rnejdl:*:1001:1001:Rusty Nejdl:/home/rnejdl:/bin/tcsh
drobinson:*:1011:1011:Dan Robinson:/home/drobinson:/bin/tcsh
juston:*:1012:1012:Juston Johnson:/home/juston:/bin/tcsh
dmcfeely:*:1013:1013:David McFeely:/home/dmcfeely:/bin/tcsh
mchiles:*:1014:1014:Martha Chiles:/home/mchiles:/bin/tcsh
gskillman:*:1020:1020:Greg Skillman:/home/gskillman:/bin/tcsh
jpuri:*:1021:1021:Jasmine Puri:/home/jpuri:/bin/tcsh
lfulkers:*:1022:1022:Lou Fulkerson:/home/lfulkers:/bin/tcsh
jdevlin:*:1023:1023:Jamie Devlin:/home/jdevlin:/bin/tcsh
jkampeas:*:1024:1024:Jeni Kampeas:/home/jkampeas:/bin/tcsh
jmccahan:*:1025:1025:jmccahan:/home/jmccahan:/bin/tcsh
jtavares:*:1026:1026:John Tavares:/home/jtavares:/bin/tcsh
khostetler:*:1027:1027:Ken Hostetler:/home/khostetler:/bin/tcsh
[www]:[1:16am]:[/home/rnejdl] > 

If you read through that, you'll notice that there are 6 :'s used in each line. Here, the : is used to denote a separation between field. So, if you want to print the username and real name of each user, you would want to use the split command. If you have a variable called $line that contained 1 line of the /etc/passwd file, you could use the split command to break that into 7 parts on each : by using the following code.

my ($username,$password,$userid,$groupid,$realname,$homedir,$shell) = split (':',$line);

We will use this in a practical example later.

Concatenate

Sometimes, you will want to combine data that have already gathered. Using the "." symbol allows you to combine to variables together. In our example above, we split the line into 7 parts. We can now concatenate, or attach, some of these variables back together by doing the following:

my $userinfo = $username." ".$realname;
print "$userinfo\n";

That code combines $username, a space, and $realname into one variable called $userinfo. The next line then prints the variable with a \n following it to make sure that each variable is printed on its own line.

Pattern Matching and Regular Expressions

If you were paying attention, you noticed a huge loophole in the programs above: there's nothing to prevent you from typing in a string variable when you're supposed to be typing in a number. You can type in "dog" and "cat", and the program will try to add "dog" and "cat" (which, if you're curious, gives a result of zero.) You need some way to check to make sure that the person actually typed in numbers; then, if they didn't, you can ask them again (with a looping control structure), until they get it right.

Welcome to the concepts of pattern matching and regular expressions, two of Perl's powerful text-processing tools. Let's start with a simple pattern first: one letter. If you want to test a variable to see if it contains the (lower-case) letter "z", use this syntax:

if ($x =~ /z/) {
print "$x has a z in it!\n";
}

Let's take that apart: if is just like while, except it only checks once (that is, it won't loop around again and again.) Like while, it will execute every command inside the curly brackets if the statement inside the parentheses is true.

The statement inside the parentheses works like this: =~ makes a comparison between $x and whatever's inbetween the two slashes; in this case, if there's a z anywhere inside $x, then the statement is true.

Let's up the ante, and match only if $x begins with the letter z:

if ($x =~ /^z/) {
print "$x begins with a z!\n";
}

^z is a regular expression; the carat (^) stands for the beginning of the string. Thus, the matching statement has to find a z immediately following the beginning of the string in order to be true.

How about words that begin with z and end with e? Use the regexp

/^z.*e$/

The $ stands for the end of the string; the period stands for "any character whatsoever"; combined with the asterisk, it means "zero or more characters." Without the asterisk,

/^z.e$/

would mean "z followed by one character followed by e." There's a lot of different regular expressions. For instance,

/^z.+e$/

means "z followed by at least one character, followed by e."

/^z\w*e$/

means "z followed by zero or more word characters followed by e"--that is, "z!e" wouldn't match.

So to make sure that somebody's typing in numbers in our adding program, and not words, make the subroutine getnumber look like this:

sub getnumber {
$number = "blah";
while($number =~ /\D/){
 print "Enter a number ";
 $number = <>;
 chop($number);
}
$number;
}

"\D" is the regular expression for non-digits; if any character in $number is not 0-9, the expression won't match, and you'll get asked to enter a number again.

Note how we had to set $number to include a non-digit ($number = "blah") to get inside the loop the first time around.

Substitution

You can transform variables at will or whim using regular expressions, by use of the substitution command. This command will change the letters "dog" to "cat" if they occur in the variable $x:

$x =~ s/dog/cat/;

Actually, that will only change the last occurence (so "dogdog" would become "dogcat".) To make the change "global", add a g at the end:

$x =~ s/dog/cat/g;

This will change "dig" and "dog" (but not "doug") to "cat":

$x =~ s/d.g/cat/g;

A Little About Arrays

A single number or string can be assigned to a scalar variable, in the form

$x = 45;

If you have a bunch of variables and want to store them together, that's an array. Here's how you assign the numbers 3, 5, 7, and 9 into an array called @dog:

@dog = (3, 5, 7, 9);

Although the entire array is referred to as @dog, an individual element--let's say, the first one--is $dog[0]. So $dog[0] equals 3, and $dog[1] equals 5. (Throughout history, programmers have begun their lists with the number zero, and throughout history, this has caused nothing but grief and turmoil.)

Note that the variable $dog and the array elements $dog[0]...$dog[3] are totally unrelated. $dog could be "zebra" or 87000, and it would never affect any of the elements of @dog.

That's just the barest hint of what arrays can do. I'm only bringing them up now so you won't freak out if I use them in an example.

More Control Structures


If/Then/Elsif/Else

You can use if all by itself, in lines like

print "You're 28!\n" if ($age == 28);

That's really shorthand for

if ($age == 28) {
print "You're 28!\n";
}

which means, "if $age equals 28, then do everything inside the curly brackets." The if/then control structure can be extended with elsif and else:

if ($age == 28) {
print "You're 28!\n";
} elsif ($sex eq "f") {
print "You're a female!\n";
} elsif ($sex eq "m") {
print "You're a male!\n";
} else {
print "You're a mystery to me...";
}

Only the first true statement will match. So if ($age == 28) and ($sex eq "f"), you'll get the output

You're 28!

else is the default; its commands will be executed only if none of the preceding statements in the control structure were true.

For

If you want to do something X number of times, the traditional method is with a for loop. For instance, to print the numbers 1 through 10:

#!/usr/bin/perl

for($x=1; $x<=10; $x++) {
print "$x\n";
}

The three parts inside the for() translate as: a) set the counter, $x, to equal one; b) check to see if $x is less than or equal to ten, and if it is, execute all the commands inside the curly brackets; c) once you've executed the commands inside the curly brackets, add 1 to $x ($x++ means "add one to x") and then go back to b).

You can also nest, as in put inside of, for statements. In this next example, we build ourselves a multiplication table:

#!/usr/bin/perl

for ($i=1; $i<10; $i++)
{
for ($j=1; $j<=10; $j++)
{
	$number = $i* $j;
	print "$number ";
}
print "\n";
}

There's fancier things you can do with a for loop, but we'll leave it at that for now. Suffice it to say that the three statements inside the parentheses take the form:

  1. Opening statement (execute this command the first time through the loop)
  2. Check statement (exit the loop when this is no longer true)
  3. Command (execute this command after each time through the loop)

As with the while control structure, the check statement has to be true the first time it is encountered, or the commands inside the curly brackets won't be executed even once.

Unless

Unless is the opposite of if, and you can use it just like if:

print "Dog isn't cat!\n" unless ($dog eq $cat);

or

unless ($dog eq $cat) {
block of commands
...
}

Until

Until is the opposite of while; the loop executes until the statement becomes true. Like:

until($x == "15"){
print "You have to type in 15!\n";
$x = &getnumber;
}

Foreach

OK, remember how arrays work? I explained earlier that they're a bunch of variables stored together, like ((@array) = (1,5,7,9)); but I didn't explain what advantage you might possibly get by storing them together. Well, here's one: foreach is a cool control structure that can access the elements of an array, one by one, in order:

$seven = 7;
(@array) = (1,5,$seven,"catapult");
foreach my $element (@array) {
print "$element\n";
}

So, for instance, if your array "@movies" was loaded up with the names of the 6000 movies in your collection of videotapes, you could

foreach $title (@movies){
print "$title\n" if ($title =~ /Tonight/);
}

to find out how many of your movies have the word "Tonight" in the title. Fun stuff like that can keep a Perl programmer up all night, I guarantee. Zzzzz!

File Manipulation


Filehandles

You can write to a file as easily as you can write to the terminal. The first step (almost the only step) is to open the file with the open command. Here's an example:

open(DOG,">/home/rnejdl/dogs");

DOG is the "filehandle"--the name by which you'll refer to the open file from now on. It's customary to use all caps for filehandles. The other thing inside the parens is the full pathname of the file; it's prefixed with a > so you can write to it. (Without the > you could only read from it--we'll talk about that in a second.)

Now to write a line of text to the file, just do it like this:

print DOG "This line goes into the file and not to the screen.\n";

You can also use similar coding to write to a process, such as sendmail or traceroute.

Since failure to successfully open a file can cause your program to go batty, it's a good idea to have the program exit gracefully if it fails to open the file. To do that, use this syntax for the open command:

open(DOG,">/home/rnejdl/dogs") || die "Couldn't open DOG.\n";

Now it will either open the file, or quit the program with an explanation why. To read from a file, open it without the >:

open(DOG,"/home/rnejdl/dogs") || die "Couldn't open DOG.\n";

(If the file doesn't exist, the program will quit.) Once the file is open, there are two common ways to get the information out. You can do it one line at a time, with lines like this:

$x = ;

That copies the first line from DOG (or the next line, if you've already taken some lines out) and assigns it to $x. The syntax is just like the <> we used earlier to get input from the keyboard; sticking DOG in there just tells Perl to get the input from the open file instead.

You can also do it in a loop. This program prints out all the contents of DOG:

#!/usr/bin/perl

open(DOG,"/home/rnejdl/dogs") || die "Couldn't open DOG.\n";

while() {
print;
}

Notice how we didn't specify a variable to store each line from DOG in, and we didn't specify anything for the print command to print? This is a really important concept I should have introduced earlier: Perl features a default variable called $_. Basically, if you don't specify which variable you want to use, or if you use a command like print by itself, Perl assumes you want to use $_.

Some other common commands that can assume you're talking about $_:

s/dog/cat/g;

That's a valid line all by itself; it means "substitute all occurences of 'dog' in the variable $_ with 'cat'." Another popular type of construction is

print if (/dog/);

That means "print $_ if $_ has 'dog' in it."

$_ shows up everywhere in Perl, just to make your life easier. For instance, foreach will store its elements in $_ if you're too lazy to name a variable yourself:

foreach (@array) {
print if (/Tonight/);
}

You can assign from $_ or manipulate it just like any other variable, with commands like

$_++;
$x = $_;

You just have to do it before the next time you overwrite $_ with a command like

<DOG>

--it's a very temporary storage space.

When you're done with a file, don't forget to close it with the close command:

close(DOG);

Filename Globbing

Perl can read all the filenames in a directory (/home/scotty/bin in the following example) with this syntax:

while($x = ) {
...
}

One obvious and powerful use of this "filename globbing" is a loop like this:

while($x = ) {
open(FILE,"$x") || die "Couldn't open $x for reading.\n";
...
}

Thus, the following simple program will print all lines containing the word "dog" (along with the names of the files they came from) in the /home/scotty/bin directory:

#!/usr/bin/perl

while($x = ) {
open(FILE,"$x") || die "Couldn't open $x for reading.\n";
while(){
    if(/dog/) {
	print "$x: $_";
    }
}
}

Opendir

Here's a key point of Perl philosophy: Perl tries to never limit you. Arrays can be as big as you want, strings as long as you want, and strings can contain anything. If you try to open a file, and the file doesn't open, the program hums right along; if a line is expecting three variables back from a subroutine, as in

($one, $two, $three) = &some_routine;

and it only gets back one, well, it won't care. It'll just pad the other variables and move along.

Anyways, Perl doesn't have limits, but Unix sometimes does. A key example is the difference between filename globbing, which relies on the Unix shell's built-in functions, and Perl's own opendir function.

Try to open a directory with 3000 files using filename globbing, and your program will probably crash. But you can open a directory of 100,000 files with opendir (as long as your machine can handle it...)

Anyways, here it is:

opendir(DIR,"/tmp");
while($file = readdir(DIR)){
  ....
}

Note that opendir will try to open every file, including the enigmatic . and .. files. (I can't imagine why, but I'm sure there's a reason.) So here's a variation that will only attempt to open files whose names don't begin with dots:

opendir(DIR,"/tmp");
while($file = readdir(DIR)){
next if ($file =~ /^\./);
  ....
}

Using Unix from Within Perl


Writing to a Process

There are many instances in web design and system administration when you will want to write to a process. One of the most common is sending email in the event that something happens. For example, suppose you have a script that is running that checks to see if any connection from a specific IP are coming into your machine. As soon as that happens, you want to receive an email about it. Here's how you can do it:

open (MESSAGE, "| /usr/sbin/sendmail -t -oi ");
print MESSAGE qq{To: "System Administrator" \n};
print MESSAGE qq{From: "Nifty Perl Script" \n};
print MESSAGE qq{Subject: Unautorized connection\n\n};
print MESSAGE qq{Yo!,\n};
print MESSAGE qq{\n};
print MESSAGE qq{That dude you didn't want logging back into our machine has just done it again!\n};
close (MESSAGE);

What did that do? It wrote to a process rather than a file. That little pipe (|) on the first line tells any output to the filehandle MAIL to go to a sendmail process; when you close up the process, with the close statement--presto, you've sent yourself a mail message.

One new item not mentioned before is the qq{ and } pair. Instead of using quotes, you can define what you want to use to begin and end your print statement. If we did not, then everytime we wanted to print a " symbol, then we would have to put a \ symbol in front of it to tell perl to take the next character literally, instead of assuming that that is the end of the print statement.

This is easily one of Perl's most powerful features. For instance, if you have a Perl script that generates a list of old files (at work, say) and the Internet addresses of the co-workers who own the files, you can send a message to every single one of them:

while(....){
....
open(MAIL,"|/usr/lib/sendmail $owner");
print MAIL "Subject: Notification of Ancient Fileness\n\n";
print MAIL "You own an ancient file named $ancientfile.\n";
print MAIL "Would you like me to do anything about it?\n";
close(MAIL);
}

System Calls

A lot of the time, Unix can do something faster or easier than your Perl scripts can. Or maybe you just don't want to rewrite an entire program that Unix already provides... Perl enables you to execute a shell command from within a Perl program via the system() function: for instance, to allow a user to edit a file directly:

$|=1;
system("/usr/sbin/traceroute $ipaddress")||die;

The $|=1; line says to print the output one line at a time as the perl script receives the data. The next line runs the command traceroute to the variable $ipaddress. You can see this script in action at traceroute.cgi.

Backticks

You can also fire up a shell process with the so-called 'backticks':

@lines = `/usr/local/bin/lynx -source http://www.some-web-site.com`;

Now you have the front page of some website in an array, one line neatly stored in each array element. Imagine what you can do with that...

@lines = `/usr/local/bin/lynx -source http://www.some-web-site.com`;
foreach (@lines) {
print if (/href/i);
}

(And yes, you can write perfectly wonderful spiders, robots and web catalogs with Perl... Just, please, BE RESPONSIBLE. Read up on robot ethics before you even think about it.)

One important difference between a system() call and backticks: if you fire up a program with a line like this

`/home/scotty/bin/some_program`;

then your original program won't wait until some_program is finished before hastening on to the next line. This can be quite powerful, but it's usually unnecessary and kinda scary until you get the hang of it. So to make sure that your original program waits obediently until some_program is finished up, do this:

$result = `/home/scotty/bin/some_program`;

$result is a throaway value here; its only purpose is to force the original program to wait for some_program to exit.

Your first real program

The number guessing game:

With everything you've learned so far, this next program should not be too much of a leap for you to understand.

#!/usr/bin/perl
	
# initialize the scalar $answer to a random number from 0-100
$answer = int(rand 100);

# the number of guess attempts with $attempts (starts at 1)
$attempts = 1;

# prompt user for a guess
print "Guess a number: ";

# read in user's first guess from standard input
$guess = ;

# continue looping until the user's guess is the same as the answer
while ($guess != $answer) {

	# if the guess is too high, print a helpful message
	if ($guess > $answer) {
		print "Too high, guess again: ";

	# otherwise, the guess is too low.  print a helpful message.
	} else {
		print "Too low, guess again: ";
	}

	# read in another guess using perl's <> slick operator
	$guess = <>;

	# add one to the total number of attempts
	$attempts++;
}

# using "variable interpolation", tell the user how many attempts they took 
print "Good job!  $attempts attempts\n";

One-line Programs

No intro to Perl would be complete without having a one-liner. An one-liner is a functional Perl program where all the code is run on one line. Most of these programs are hard to read. But fun. Here is a Perl one-liner which implements our number guessing game:

perl -ne 'BEGIN {print $t = " Guess a number: "; $R = int(rand 100)} (($_ == $R) 
  && print "Good Job! $. attempts\n") && exit; print "Too ", ($R > $_ ? "small" : "big"), $t;

Checking Disk Use

The following script will go through the /etc/passwd file in UNIX to get a list of users. Then, as the program finds each user, it uses the "du" command to check disk usage for that user. It will then print out the disk usage for each user.

#!/usr/bin/perl
$passwd_file="/etc/passwd";
$du='/usr/bin/du -sk';

open (PASSWD,$passwd_file);
while ($data=)
{
        chop($data);
        ($login,$shadow,$uid,$gid,$gecos,$homedir,$shell)=split(':',$data);
        if (($uid > 101) && ($UID < 6000))
        {
                if (($homedir) ne '/nonexistent')
                {
                        $space=`$du $homedir`;
                        ($space)=split("\t",$space);
                        print "$login is using $space kb's.\n";
                }
        }
}
close (PASSWD);

Emailing users

The following script sets up an array called @emailaddresses. Each item in the array is then defined with an email address. One way you could expand this script is to read in the list of email addresses from a text file or database instead of defining it in the top of the script. The script then goes through the array using a for loop and emails each user.

This script uses many of the concepts that you have learned above. This is a simplified example, but could quickly be applied to any file containing email addresses.

#! /usr/local/bin/perl

use strict;

my @emailaddresses;
$emailaddresses[0]="rnejdl\@verio.net";
$emailaddresses[1]="jbrobinson\@verio.net";
$emailaddresses[2]="jlong\@verio.net";

my $emailcount=scalar(@emailaddresses);
for (my $i=0; $i<$emailcount; $i++)
{
        open (MESSAGE, "| /usr/sbin/sendmail -t -oi ");
        print MESSAGE qq{To: "<$email>\n};
        print MESSAGE qq{From: "Training" \n};
        print MESSAGE qq{Date: Tue, 6 Feb 2001 19:40:29 -0600\n};
        print MESSAGE qq{Subject: Win a coffee mug!\n\n};
        print MESSAGE qq{Dear Associate,\n\n};
        print MESSAGE qq{We just wanted to say thank you for being on our email list.

Sincerely,

Training
training\@training.com\n};
close (BLAH);

Perl CGI

Basics

CGI's process input differently than old Perl scripting does - and this, my friends, is the only difference you'll find between the two. Once the CGI scripts process the input, it becomes data, which is treated pretty much the same way by both CGI and Perl scripts. CGI input can be retrieved in two different ways: "get" and "post." In general, I use the "post" method because unlike the "get" method, there is no limit to the amount of data that can be sent to a web server using the "post" method.

Now, to capture any data from a page that the user submitted data on, you need to do the following:

#!/usr/bin/perl

use CGI;
my $cgi=new CGI;

my $cgivariable1 = $cgi->param('cgivariable1');
my $firstname = $cgi->param('firstname');

Now all that did was to initialize the CGI module, which is a collection of functions for use in CGI pages. Each of the last two lines then captures cgivariable1 and firstname and stores them in two variables called $cgivariable1 and $firstname respectively.

The other part of using Perl scripts to print CGI pages is to remember to print your HTTP headers correctly before you print any content by doing the following:

#!/usr/bin/perl

use CGI;
my $cgi=new CGI;

my $cgivariable1 = $cgi->param('cgivariable1');
my $firstname = $cgi->param('firstname');

print $cgi->header;
print "You submitted the following:
\n"; print "$cgivariable1
\n"; print "$firstname
\n";

We added 4 new lines to our previous script. The first line simply printed the appropriate content/type line for HTTP to use and the next 3 printed content on the website.

If you are not comfortable with CGI forms, then you might want to consider reading over the HTML/CGI Forms documentation before proceeding.

Next CGI Script - A guessing game.

Let's go ahead and make a quick program to take our guessing game earlier and put it online:

#!/usr/bin/perl
use CGI;

my $cgi = new CGI;
print $cgi->header;

my $answer=$cgi->param('answer');
if ($answer eq '') {
        $answer = int(rand 100);
}

my $attempts = $cgi->param('attempts');
if ($attempts eq '') {
        $attempts = 1;
}

my $guess = $cgi->param('GUESS');
if ($guess==$answer) {
        print "Good job!  $attempts attempts\n";
} elsif ($guess) {
        # add one to the total number of attempts
        $attempts++;
        print "<FORM METHOD=POST ACTION=./guessinggame.cgi>\n";
        print "<INPUT TYPE=HIDDEN NAME=answer VALUE=$answer>\n";
        print "<INPUT TYPE=HIDDEN NAME=attempts VALUE=$attempts>\n";
        print "Guess a number: <INPUT TYPE=TEXT SIZE=2 NAME=GUESS><BR>";
        print "<INPUT TYPE=SUBMIT>";
        print "</FORM>\n";
        if ($guess > $answer) {
# if the guess is too high, print a helpful message
                print "Too high, guess again: ";
        } else {
# otherwise, the guess is too low.  print a helpful message.
                print "Too low, guess again: ";
        }

} else {
        print "<FORM METHOD=POST ACTION=./guessinggame.cgi>\n";
        print "<INPUT TYPE=HIDDEN NAME=answer VALUE=$answer>\n";
        print "Guess a number: <INPUT TYPE=TEXT SIZE=2 NAME=GUESS><BR>";
        print "<INPUT TYPE=SUBMIT>";
        print "</FORM>\n";
}

This program adds some new features that haven't been used in previous scripts, which is HIDDEN variables. The reason is because HTML is a stateless system. That is, each time you submit or load a page, the browser and server have no clue what happened last time the page was loaded without the page itself providing clues back to the server. So, in this example, we have to make sure that we tell the script what the answer is as well as how many attempts have been tried.

This is not the only way to handle this script and there are definitely better ways of doing this, but without jumping too far ahead, I wanted to make sure that the leap to get to this level wasn't too bad.

Conclusion

PERL is a very useful language for both system administration as well as dynamic web content. Because of this flexibility, it can be sometimes daunting to a new user to find just the right way of handling a problem as there are probably about 5 or 6 ways of solving something. This document was designed to be just enough to get you started learning on your own. So, if you don't go out and practice what you've worked on, then you won't get any better at it. Just remember how far you've come in just a few hours of work.