| Get your FREE home page! |
|
A sysadmin's notes on Perl 5. Dave Ferreira. Last updated 1999-09-19. why | scalars, arrays, hashes | context | regular expressions | files and locking | the CGI guestbook, in full | mistakes that cost me a weekend | books 0. Why this page existsI administer about forty Unix boxes (Solaris, a few Linux) and a handful of NT machines I try not to touch. In 1997 I got tired of writing shell scripts that fell over when a hostname had a space in it and picked up Perl. I learned it the hard way, which is to say by breaking things in production and then reading the manual to find out why. This page is the manual I wish somebody had handed me. It is not a tutorial for people who have never programmed. It is the notes of a person who was in a hurry. Everything here is Perl 5. Specifically, 5.005_03, which is what ships on our Red
Hat 6.0 boxes and what I built on the Solaris ones. If your The single most important thing on this page, so I'll say it first: #!/usr/bin/perl -w use strict; Every script. Every time. 1. Scalars, arrays, hashesPerl has three kinds of variables and the sigil in front tells you which. my $host = "web1"; # a scalar: one thing (string or number, Perl doesn't care)
my $count = 42;
my $total = $count * 2.5; # 105
my @hosts = ("web1", "web2", "db1"); # an array: an ordered list
print $hosts[0]; # "web1" -- note the $: ONE element is a scalar
print $hosts[-1]; # "db1" -- negative index counts from the end
print $#hosts; # 2 -- the LAST index, not the count
print scalar(@hosts); # 3 -- the count
push @hosts, "web3"; # add to the end
my $last = pop @hosts; # take from the end
my $first = shift @hosts; # take from the front
unshift @hosts, "lb1"; # add to the front
my %port = ( # a hash: key => value pairs, no order
http => 80,
https => 443,
ssh => 22,
);
print $port{ssh}; # 22 -- again, one element is a scalar, so $
$port{smtp} = 25; # add one
delete $port{https}; # remove one
if (exists $port{ftp}) { } # test for a key without creating it
foreach my $name (sort keys %port) {
printf "%-6s %d\n", $name, $port{$name};
}
The thing that trips everyone: The hash is the workhorse. If you find yourself scanning an array to see whether something is in it, you wanted a hash. Counting things: # how many hits from each host, out of an Apache access log
my %hits;
open(LOG, "</var/log/httpd/access_log") or die "can't open log: $!";
while (my $line = <LOG>) {
my ($host) = split ' ', $line; # first whitespace-separated field
$hits{$host}++; # autovivifies: no need to set it to 0 first
}
close(LOG);
foreach my $host (sort { $hits{$b} <=> $hits{$a} } keys %hits) {
printf "%6d %s\n", $hits{$host}, $host;
}
That sort block sorts numerically, descending, by value. Swap 2. Context, or why your array printed "3"Perl expressions behave differently depending on whether they're being asked for a single value (scalar context) or a list (list context). An array in scalar context is its length. This is a feature, but it's a feature that will surprise you about once a week for the first year. my @list = (10, 20, 30); my $n = @list; # scalar context: $n is 3 my ($x) = @list; # LIST context (the parens): $x is 10 print "have @list\n"; # interpolates as "have 10 20 30" print "have " . @list . "\n"; # the . forces scalar: "have 3" my $line = <FH>; # scalar context: one line my @all = <FH>; # list context: every remaining line, slurped into memory my $when = localtime; # scalar: "Sun Sep 19 14:02:33 1999" my @when = localtime; # list: (sec, min, hour, mday, mon, year, wday, yday, isdst) That last one is worth a whole paragraph. In list context, 3. Regular expressionsRegexes are why you learn Perl. The basics: if ($line =~ /error/) { } # matches if $line contains "error"
if ($line =~ /error/i) { } # case-insensitive
if ($line !~ /^#/) { } # does NOT start with #
$line =~ s/\s+$//; # strip trailing whitespace (in place)
(my $copy = $line) =~ s/foo/bar/g; # substitute in a copy, all occurrences
The pieces you use every day:
Pulling the fields out of an Apache common-log line (host, ident, user, date in brackets, the request in quotes, status, bytes): while (my $line = <LOG>) {
next unless $line =~ m{
^(\S+)\s+\S+\s+\S+ # host, ident, authuser
\s+\[([^\]]+)\] # [date]
\s+"(\w+)\s+(\S+)[^"]*" # "METHOD /path HTTP/1.x"
\s+(\d{3})\s+(\d+|-) # status, bytes (or -)
}x;
my ($host, $when, $method, $path, $status, $bytes) = ($1, $2, $3, $4, $5, $6);
$bytes = 0 if $bytes eq '-';
print "$status $path\n" if $status >= 400;
}
Things to notice. About greedy: Don't test 4. Files, and locking themopen(IN, "<$path") or die "can't read $path: $!"; # read open(OUT, ">$path") or die "can't write $path: $!"; # write (clobbers) open(LOG, ">>$path") or die "can't append $path: $!"; # append open(PS, "ps -ef |") or die "can't run ps: $!"; # read from a command close(IN); Always check the return of If more than one copy of your script can run at the same time and they all write to
the same file — which is every CGI script ever written — you need a lock, or
two writes will land on top of each other and you'll get half of one line and half of
another. Perl's use Fcntl qw(:flock); # gives you LOCK_SH, LOCK_EX, LOCK_UN open(FH, ">>$path") or die "can't append $path: $!"; flock(FH, LOCK_EX) or die "can't lock $path: $!"; # blocks until we have it seek(FH, 0, 2); # go to the real end: someone may have written while we waited print FH "$entry\n"; close(FH); # closing releases the lock
5. The CGI guestbook, in fullHere is the whole thing that runs the guestbook on my own (non-GeoCities) box. It's
short on purpose. It uses Taint mode ( #!/usr/bin/perl -wT
# guestbook.cgi -- append an entry if one was posted, then show the book.
use strict;
use CGI qw(:standard);
use Fcntl qw(:flock);
$ENV{PATH} = '/bin:/usr/bin'; # taint mode requires a known PATH
my $file = '/home/dave/gbook/entries.txt'; # NOT under the document root
print header(); # Content-type: text/html, blank line
print start_html('Guestbook');
my $name = param('name') || '';
my $comment = param('comment') || '';
if ($name ne '' && $comment ne '') {
for ($name, $comment) {
s/[\r\n]+/ /g; # keep each entry on one line
s/\|/-/g; # pipe is my field separator
}
open(BOOK, ">>$file") or die "can't append to $file: $!";
flock(BOOK, LOCK_EX) or die "can't lock $file: $!";
seek(BOOK, 0, 2);
print BOOK join('|', scalar(localtime), $name, $comment), "\n";
close(BOOK) or die "can't close $file: $!";
print p("Thanks, ", escapeHTML($name), ". You're in the book.");
}
open(BOOK, "<$file") or die "can't read $file: $!";
flock(BOOK, LOCK_SH) or die "can't lock $file: $!";
my @entries = <BOOK>;
close(BOOK);
print h2('The book so far');
foreach my $line (reverse @entries) { # newest first
chomp $line;
my ($when, $who, $what) = split /\|/, $line, 3;
print p(b(escapeHTML($who)), " wrote on $when:", br(), escapeHTML($what));
}
print end_html();
Notes, in the order you'll wonder about them:
6. Mistakes that cost me a weekendEach of these is real, and each is documented here so that I don't do it a third time.
7. Books
Neighbors: Brian's PC Help Page · The Y2K Watch: 100 Days to Go (a man who has read more COBOL than I've read Perl) · Mom's Guide to the Family Computer · Quake II Clan [DUST] (what the other half of the office does after 6) · SiliconValley neighborhood Visitors since 1998-11-02:
Corrections welcome and expected: [email protected].
If you tell me a code sample is wrong, include the error message and your Last updated 1999-09-19. © 1998-1999 Dave Ferreira. The code on this page is yours to
use; if it breaks something, you were warned in section 0. |