448 lines
22 KiB
HTML
448 lines
22 KiB
HTML
<!--startcut ==============================================-->
|
|
<!-- *** BEGIN HTML header *** -->
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
|
|
<HTML><HEAD>
|
|
<title>Learning Perl, part 4 LG #67</title>
|
|
</HEAD>
|
|
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
|
|
ALINK="#FF0000">
|
|
<!-- *** END HTML header *** -->
|
|
|
|
<CENTER>
|
|
<A HREF="http://www.linuxgazette.com/">
|
|
<IMG ALT="LINUX GAZETTE" SRC="../gx/lglogo.png"
|
|
WIDTH="600" HEIGHT="124" border="0"></A>
|
|
<BR>
|
|
|
|
<!-- *** BEGIN navbar *** -->
|
|
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="nielsen.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue67/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="orr.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
|
|
<!-- *** END navbar *** -->
|
|
<P>
|
|
</CENTER>
|
|
|
|
<!--endcut ============================================================-->
|
|
|
|
<H4 ALIGN="center">
|
|
"Linux Gazette...<I>making Linux just a little more fun!</I>"
|
|
</H4>
|
|
|
|
<P> <HR> <P>
|
|
<!--===================================================================-->
|
|
|
|
<center>
|
|
<H1><font color="maroon">Learning Perl, part 4</font></H1>
|
|
<H4>By <a href="mailto:ben-fuzzybear@yahoo.com">Ben Okopnik</a></H4>
|
|
</center>
|
|
<P> <HR> <P>
|
|
|
|
<!-- END header -->
|
|
|
|
|
|
|
|
|
|
<BLOCKQUOTE>
|
|
<tt>The Internet Revolution was founded on open systems; an open system
|
|
is one whose software you can look at, a box you can unwrap and play with.
|
|
It's not about secret binaries or crippleware or brother-can-you-spare-a-dime
|
|
shareware. If everyone always had hidden software, you wouldn't have 1/100th
|
|
the useful software you have right now.</tt>
|
|
<p><tt>And you wouldn't have Perl.</tt>
|
|
<BR><CITE> -- Tom Christiansen</CITE>
|
|
</BLOCKQUOTE>
|
|
|
|
<p><b>Overview</b>
|
|
<p>If you have been following this series, you now have a few tools - perhaps
|
|
you've even experimented with them - which can be used to build scripts.
|
|
So, this month we're going to take a look at actually building some, particularly
|
|
by using the "open" function which allows us to assign filehandles to files,
|
|
sockets, and pipes. "open" is a major building block in using Perl, so
|
|
we'll give it a good long look.
|
|
<br>
|
|
<p><b>Excercises</b>
|
|
<p>Last time, I mentioned writing a few scripts for practice. Let's take
|
|
a look at a few possible ways to do that.
|
|
<p>The first one was a script that would take a number as input, and print
|
|
"Hello!" that many times. It would also test the input for illegal (non-numeric)
|
|
characters. Here is a good example, sent in by David Zhuwao:
|
|
|
|
<BLOCKQUOTE>
|
|
<tt>#! /usr/bin/perl -w</tt>
|
|
<p><tt>#@author David Zhuwao</tt>
|
|
<br><tt>#@since Apr/19/'01</tt>
|
|
<p><tt>print "Enter number of times to loop: ";</tt>
|
|
<p><tt>#get input and assign it to a variable.</tt>
|
|
<br><tt>chomp ($input = <>);</tt>
|
|
<p><tt># check the input for non-numeric characters.</tt>
|
|
<br><tt>if ($input !~ m/\D/ && length($input) > 0) {</tt>
|
|
<br><tt> for ($i = 0; $i < $input; $i++) {</tt>
|
|
<br><tt> print "Hello!\n";</tt>
|
|
<br><tt> }</tt>
|
|
<br><tt>} else {</tt>
|
|
<br><tt> print "Non-numeric input.\n";</tt>
|
|
<br><tt>}</tt>
|
|
</BLOCKQUOTE>
|
|
First, to point out good coding practices: David has used the "-w"
|
|
switch so that Perl will warn him if there are any compile-time warnings
|
|
- an excellent habit. He has also used whitespace (blank lines and tabs)
|
|
effectively to make the code easy to read, as well as commenting it liberally.
|
|
Also, rather than checking for the presence of a number (which would create
|
|
a problem with input like "1A"), he is testing for non-numerical characters
|
|
and a length greater than zero - good thinking!
|
|
<p>Minor points (note that none of these are problems as such, simply
|
|
observations): in using the match operator, "m//", the "m" is unnecessary unless
|
|
the delimiter is something other than "/". As well, the Perl "for/foreach"
|
|
loop would be more compact than the C-like "for" loop, while still fulfilling
|
|
the function:
|
|
<BLOCKQUOTE>
|
|
<tt>print "Hello!\n" for 1 .. $input;</tt>
|
|
</BLOCKQUOTE>
|
|
<p>It would also render "$i" unnnecessary. Other than those minor nits
|
|
- well done, David!
|
|
<br>
|
|
<p>Here's another way:
|
|
<BLOCKQUOTE>
|
|
<tt>#!/usr/bin/perl -w</tt>
|
|
<p><tt>print "Please enter a number: ";</tt>
|
|
<br><tt>chomp ( $a = <> );</tt>
|
|
<p><tt>print "Hello!\n" x $a if $a =~ /^\d+$/;</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
Unlike David's version, mine does not print a failure message; it simply
|
|
returns you to the command prompt if the input is not numeric. Also, instead
|
|
of testing for non-numerical characters, I'm testing the string from its
|
|
beginning to its end for <i>only</i> numerical content. Either of these
|
|
techniques will work fine. Also, instead of using an explicit loop, I'm
|
|
using Perl's "x" operator, which will simply repeat the preceding print
|
|
instruction "$a" times.
|
|
<br>
|
|
<p><b>...And, One More Time...</b>
|
|
<p>Let's break down another one, the second suggestion from last month:
|
|
a script that takes an hour (0-23) as input and says "Good morning", "Dobriy
|
|
den'", "Guten Abend", or "Buenas noches" as a result (I'll cheat here and
|
|
use all English to avoid confusion.)
|
|
|
|
<BLOCKQUOTE>
|
|
<tt>#!/usr/bin/perl -w</tt>
|
|
<p><tt>$_ = <>;</tt>
|
|
<p><tt>if ( /^[0-6]$/
|
|
) { print "Good night\n"; }</tt>
|
|
<br><tt>elsif ( /^[7-9]$|^1[0-2]$/ ) { print "Good morning\n";
|
|
}</tt>
|
|
<br><tt>elsif ( /^1[3-8]$/
|
|
) { print "Good day\n";
|
|
}</tt>
|
|
<br><tt>elsif ( /^19$|^2[0-3]$/ ) { print
|
|
"Good evening\n"; }</tt>
|
|
<br><tt>else
|
|
{ print "Invalid input!\n"; }</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
On the surface, this script seems pretty basic - and, really, it is
|
|
- but it contains a few hidden considerations that I'd like to mention.
|
|
First, why do we need the "beginning of line" and "end of line" tests for
|
|
everything? Obviously, we want to avoid confusing "1" and "12" - but what
|
|
could go wrong with /1[3-8]/?
|
|
<p>What could go wrong is a mis-type. Not that it matters too much in this
|
|
case, but being paranoid about your tests is a good idea in general. :)
|
|
What happens if a user, while trying to type "14", typed "114"? Without
|
|
those "limits", it would match "11" - and we'd get a wrong answer.
|
|
<p>OK - why didn't I use numeric tests instead of matching? I mean, after
|
|
all, we're just dealing with numbers... wouldn't it be easier and more
|
|
obvious? Yes, <b>but</b>. What happens if we do a numeric test and the
|
|
user types in "joe"? We'd get an error along with our "Invalid input!":
|
|
<BLOCKQUOTE>
|
|
<tt>Argument "joe\n" isn't numeric in gt at -e line 5, <> chunk 1.</tt>
|
|
</BLOCKQUOTE>
|
|
<p>As a matter of good coding practice, we want the user to see only the
|
|
output that we generate (or expect); there should not be any errors caused
|
|
by the program itself. A regex match isn't going to be "surprised" by non-digit
|
|
input; it will simply return a 0 (no match) and pass on to the next "elsif"
|
|
or "else", which is the "catchall" clause. Anything that does not match
|
|
one of the first four tests is invalid input - and that's what we want
|
|
reported.
|
|
<br>
|
|
<p><b>Handling Files</b>
|
|
<p>An important capability in any language is that of dealing with files.
|
|
In Perl, this is relatively easy, but there are a couple of places where
|
|
you need to be careful.
|
|
<BLOCKQUOTE>
|
|
<tt># The right way</tt>
|
|
<br><tt>open FILE, "/etc/passwd" or die "Can't open /etc/password: $!\n";</tt>
|
|
</BLOCKQUOTE>
|
|
<p>Here are some wrong or questionable ways to do this:
|
|
<BLOCKQUOTE>
|
|
<tt># Doesn't test for the return result</tt>
|
|
<br><tt>open FILE, "/etc/passwd";</tt>
|
|
<p><tt># Ignores the error returned by the shell via the '$!' variable</tt>
|
|
<br><tt>open FILE, "/etc/passwd" or die "Can't open /etc/password\n";</tt>
|
|
<p><tt># Uses "logical or" to test - can be a problem due to precedence
|
|
issues</tt>
|
|
<br><tt>open FILE, "/etc/passwd" || die "Can't open /etc/password: $!\n";</tt>
|
|
</BLOCKQUOTE>
|
|
By default, files are open for reading. Other methods are specified
|
|
by adding a rather obvious "modifier" to the specified filename:
|
|
<BLOCKQUOTE>
|
|
<p><tt># Open for writing - anything written will overwrite file contents</tt>
|
|
<br><tt>open FILE, ">/etc/passwd" or die "Can't open /etc/password: $!\n";</tt>
|
|
<p><tt># Open for appending - data will be added to the end of the file</tt>
|
|
<br><tt>open FILE, ">>/etc/passwd" or die "Can't open /etc/password: $!\n";</tt>
|
|
<p><tt># Open for reading and writing</tt>
|
|
<br><tt>open FILE, "+>/etc/passwd" or die "Can't open /etc/password: $!\n";</tt>
|
|
<p><tt># Open for reading and appending</tt>
|
|
<br><tt>open FILE, "+>>/etc/passwd" or die "Can't open /etc/password: $!\n";</tt>
|
|
<p>Having created the filehandle ("FILE", in the above case), you can now
|
|
use it in the following manner:
|
|
<p><tt>while ( <FILE> ) {</tt>
|
|
<br><tt> print; # This
|
|
will loop through the file and print every line</tt>
|
|
<br><tt>}</tt>
|
|
</BLOCKQUOTE>
|
|
<p> Or you can do it this way, if you just want to print out the
|
|
contents in one shot:
|
|
<BLOCKQUOTE>
|
|
<TT>print <FILE>;</TT>
|
|
</BLOCKQUOTE>
|
|
<p> Writing to the file is just as easy:
|
|
<BLOCKQUOTE>
|
|
<tt>print FILE "This line will be written to the file.\n";</tt>
|
|
</BLOCKQUOTE>
|
|
<p>Remember that the default open method is "read". I usually like to emphasize
|
|
this by writing the statement this way:
|
|
<BLOCKQUOTE>
|
|
<tt>open FILE, "</etc/passwd" or die "Can't open /etc/password: $!\n";</tt>
|
|
</BLOCKQUOTE>
|
|
<p>Note the "<" sign in front of the filename: Perl has no problem with
|
|
this, and it makes a good visual reminder. The phrase "leaving breadcrumbs"
|
|
describes this methodology, and has to do with the idea of making what
|
|
you write as obvious as possible to anyone who may follow. Don't forget
|
|
that the person "following" might be you, a couple of years after you've
|
|
written the code...
|
|
<p>Perl automatically closes filehandles when the script exits... or, at
|
|
least, is supposed to. From what I've been told, some OSs have a problem
|
|
with this - so, it's not a bad idea (though not a necessity) to perform
|
|
an explicit "close" operation on open filehandles:
|
|
<BLOCKQUOTE>
|
|
<tt>close FILE or die "Can't close FILE: $!\n";</tt>
|
|
</BLOCKQUOTE>
|
|
<p>By the way, the effect of the "die" function should be relatively obvious:
|
|
it prints the specified string and exits the program.
|
|
<p><b>Don't</b> do this, unless you're at the last line of your script:
|
|
<BLOCKQUOTE>
|
|
<tt>close;</tt>
|
|
</BLOCKQUOTE>
|
|
<p>This closes <i>all</i> filehandles... including STDIN, STDOUT, and STDERR
|
|
(the standard streams), which leaves your program dumb, deaf, and blind.
|
|
Also, you cannot specify multiple handles in one close, so you do indeed
|
|
have to close them one at a time:
|
|
<BLOCKQUOTE>
|
|
<tt>close Fh1 or die "Can't close Fh1: $!\n";</tt>
|
|
<br><tt>close Fh2 or die "Can't close Fh2: $!\n";</tt>
|
|
<br><tt>close Fh3 or die "Can't close Fh3: $!\n";</tt>
|
|
<br><tt>close Fh4 or die "Can't close Fh4: $!\n";</tt>
|
|
</BLOCKQUOTE>
|
|
<p>You could, of course, do this:
|
|
<BLOCKQUOTE>
|
|
<tt>for ( qw/Fh1 Fh2 Fh3 Fh4/ ) { close $_ or die "Can't close $_: $!\n";
|
|
}</tt>
|
|
</BLOCKQUOTE>
|
|
<p><IMG ALT=":)" SRC="../gx/dennis/smily.gif" WIDTH="20" HEIGH="24">
|
|
That's Perl for you; There's More Than One Way To Do It...
|
|
<br>
|
|
<p><b>Using Those Handles</b>
|
|
<p>Let's say that you have two files with some financial data - loan rates
|
|
in one, the type and amount of your loans in the other - and you want to
|
|
calculate how much interest you'll be paying, and write the result out
|
|
to a file. Here is the data:
|
|
<BLOCKQUOTE>
|
|
<tt>rates.txt</tt>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<tt>House 9%</tt>
|
|
<br><tt>Car 16%</tt>
|
|
<br><tt>Boat 19%</tt>
|
|
<br><tt>Misc 21%</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
<BLOCKQUOTE>
|
|
<tt>loans.txt</tt>
|
|
<br>
|
|
<hr WIDTH="100%">
|
|
<tt>Chevy CAR 8000</tt>
|
|
<br><tt>BMW car 22000</tt>
|
|
<br><tt>Scarab BOAT 150000</tt>
|
|
<br><tt>Pearson boat 8000</tt>
|
|
<br><tt>Piano Misc 4000</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
<p>All right, let's make this happen:
|
|
|
|
<BLOCKQUOTE>
|
|
<tt>#!/usr/bin/perl -w</tt>
|
|
<p><tt>open Rates, "<rates.txt" or die "Can't open rates.txt: $!\n";</tt>
|
|
<br><tt>open Loans, "<loans.txt" or die "Can't open loans.txt: $!\n";</tt>
|
|
<br><tt>open Total, ">total.txt" or die "Can't open total.txt: $!\n";</tt>
|
|
<p><tt>while ( <Rates> ) {</tt>
|
|
<br><tt> # Get rid of the '%' signs</tt>
|
|
<br><tt> tr/%//d;</tt>
|
|
<br><tt> # Split each line into an array</tt>
|
|
<br><tt> @rates = split;</tt>
|
|
<br><tt> # Create hash with loan types as keys and percentages
|
|
as values</tt>
|
|
<br><tt> $r{lc $rates[0]} = $rates[1] / 100;</tt>
|
|
<br><tt>}</tt>
|
|
<p><tt>while ( <Loans> ) {</tt>
|
|
<br><tt> # Split the line into an array</tt>
|
|
<br><tt> @loans = split;</tt>
|
|
<br><tt> # Print the loan and the amount of interest
|
|
to the "Total" handle;</tt>
|
|
<br><tt> # calculate by multiplying the total amount
|
|
by the value returned</tt>
|
|
<br><tt> # by the hash key.</tt>
|
|
<br><tt> print Total "$loans[0]\t\t\$", $loans[2] * $r{lc
|
|
$loans[1]}, "\n";</tt>
|
|
<br><tt>}</tt>
|
|
<p><tt># Close the filehandles - not a necessity, but can't hurt</tt>
|
|
<br><tt>for ( qw/Rates Loans Total/ ) {</tt>
|
|
<br><tt> close $_ or die "Can't close $_: $!\n";</tt>
|
|
<br><tt>}</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
<br>Rather obviously, Perl is very good at this kind of thing: we've done
|
|
the job in a dozen lines of code. The comments took up most of the space.
|
|
:)
|
|
<br>
|
|
<p>Here's another example, one that came about as a result of one of my
|
|
article about procmail (<a href="../issue62/okopnik.html">"No
|
|
More Spam!"</a> in LG#62). The original "blacklist" script that was invoked
|
|
from Mutt pulled out the spammer's e-mail address via "formail", then parsed
|
|
the result down to the actual "user@host" address with a one-line Perl
|
|
script. It took the entire spam mail as piped input. Martin Bock, however,
|
|
suggested doing the whole thing with Perl; after exchanging a bit of e-mail
|
|
with him, I came up with the following script based on his idea:
|
|
<BLOCKQUOTE>
|
|
<tt>#!/usr/bin/perl -wln</tt>
|
|
<br><tt># The '-n' switch makes the script read the input one line at a
|
|
time--</tt>
|
|
<br><tt># the entire script is executed for each line;</tt>
|
|
<br><tt># the '-l' enables line processing, which appends carriage returns
|
|
to</tt>
|
|
<br><tt># the lines that are printed out.</tt>
|
|
<p><tt># If the line matches the expression, then...</tt>
|
|
<br><tt>if ( s/^From: .*?(\w\S+@\S+\w).*/$1/ ) {</tt>
|
|
<br><tt> # Open the "blacklist" with the "OUT" filehandle
|
|
in append mode</tt>
|
|
<br><tt> open OUT, ">>$ENV{HOME}/.mutt/blacklist" or
|
|
die "Aargh: $!\n";</tt>
|
|
<br><tt> # Print $_ to that filehandle</tt>
|
|
<br><tt> print OUT;</tt>
|
|
<br><tt> # Close</tt>
|
|
<br><tt> close OUT or die "Aargh: $!\n";</tt>
|
|
<br><tt> # Exit the loop</tt>
|
|
<br><tt> last;</tt>
|
|
<br><tt>}</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
<br>The substitution operator in the first line is not perfect - I can
|
|
write some rather twisted e-mail addresses which it would not parse correctly
|
|
- but it works well with variations like
|
|
<BLOCKQUOTE>
|
|
<tt>one-two@three-four.net</tt>
|
|
<br><tt><one-two@three-four.net></tt>
|
|
<br><tt>joe.blow.from.whatever@whoever.that-might-be.com (Joe Blow)</tt>
|
|
<br><tt>Joe Blow <joe.blow.from.whatever@whoever.that-might-be.com></tt>
|
|
<br><tt>[ The artist formerly known as squiggle ] <prince@loco.net></tt>
|
|
<br><tt>(Joe) joe-blow.wild@hell.and.gone.com ["Wildman"]</tt>
|
|
</BLOCKQUOTE>
|
|
<p>To "decode" what the regular expression in it says, consult the "perlre"
|
|
manpage. It's not <i>that</i> complex.
|
|
<IMG ALT=":)" SRC="../gx/dennis/smily.gif" WIDTH="20" HEIGH="24">
|
|
Hint: look for the word
|
|
"greed" to understand that ".*?", and look for the word "capture" to understand
|
|
the "(...) / $1" construct. Both of them are very important concepts, and
|
|
both have been mentioned in this series.
|
|
<p>Here's a somewhat more compact (and that much less readable) version
|
|
of the above; note that the mechanism here is somewhat different:
|
|
<BLOCKQUOTE>
|
|
<tt>#!/usr/bin/perl -wln</tt>
|
|
<br><tt>BEGIN { open OUT, ">>$ENV{HOME}/.mutt/blacklist" or die "Aargh:
|
|
$!\n"; }</tt>
|
|
<br><tt>if ( s/^From: .*?(\w\S+@\S+\w).*/$1/ ) { print OUT; close OUT;
|
|
last; }</tt>
|
|
</BLOCKQUOTE>
|
|
|
|
<br>The BEGIN block on the first line of the script runs only once during
|
|
execution, despite the fact that the script loops multiple times; it's
|
|
very similar to the same construct in Awk.
|
|
<br>
|
|
<p><b>Next Time</b>
|
|
<p>Next month, we'll be looking at a few nifty ways to save ourselves work
|
|
by using <b>modules</b>: useful code that other people have written from
|
|
the Comprehensive Perl Archive Network (<a href="http://cpan.org">CPAN</a>).
|
|
We'll also take a look at how Perl can be used to implement CGI, the Common
|
|
Gateway Interface - the mechanisms that "hew the wood and draw the water"
|
|
behind the scenes of the Web. Until then, here are a few things to play
|
|
with:
|
|
<p>Write a script that opens "/etc/services" and counts how many ports
|
|
are listed as supporting UDP operation, and how many support TCP. Write
|
|
the service names into files called "udp.txt" and "tcp.txt", and print
|
|
the totals to the screen.
|
|
<p>Open two files and exchange their contents.
|
|
<p>Read "/var/log/messages" and print out any line that contains the word
|
|
"fail", "terminated/terminating", or " no " in it. Make it
|
|
<br>case-insensitive.
|
|
<br>
|
|
<p>Until then -
|
|
<p>perl -we 'print "See you next month!"'
|
|
<br>
|
|
<p>Ben Okopnik
|
|
<br>perl -we'print reverse split//,"rekcah lreP rehtona tsuJ"'
|
|
<br>
|
|
<tt>References:</tt>
|
|
<p><tt>Relevant Perl man pages (available on any pro-Perl-y configured</tt>
|
|
<br><tt>system):</tt>
|
|
<p><tt>perl - overview
|
|
perlfaq - Perl FAQ</tt>
|
|
<br><tt>perltoc - doc TOC
|
|
perldata - data structures</tt>
|
|
<br><tt>perlsyn - syntax
|
|
perlop - operators/precedence</tt>
|
|
<br><tt>perlrun - execution
|
|
perlfunc - builtin functions</tt>
|
|
<br><tt>perltrap - traps for the unwary perlstyle - style guide</tt>
|
|
<p><tt>"perldoc", "perldoc -q" and "perldoc -f"</tt>
|
|
<br>
|
|
|
|
|
|
|
|
|
|
<!-- *** BEGIN bio *** -->
|
|
<SPACER TYPE="vertical" SIZE="30">
|
|
<P>
|
|
<H4><IMG ALIGN=BOTTOM ALT="" SRC="../gx/note.gif">Ben Okopnik</H4>
|
|
<CITE>A cyberjack-of-all-trades, Ben wanders the world in his 38' sailboat,
|
|
building networks and hacking on hardware and software whenever he runs out of
|
|
cruising money. He's been playing and working with computers since the Elder
|
|
Days (anybody remember the Elf II?), and isn't about to stop any time soon.</CITE>
|
|
|
|
|
|
<!-- *** END bio *** -->
|
|
|
|
<!-- *** BEGIN copyright *** -->
|
|
<P> <hr> <!-- P -->
|
|
<H5 ALIGN=center>
|
|
|
|
Copyright © 2001, Ben Okopnik.<BR>
|
|
Copying license <A HREF="../copying.html">http://www.linuxgazette.com/copying.html</A><BR>
|
|
Published in Issue 67 of <i>Linux Gazette</i>, June 2001</H5>
|
|
<!-- *** END copyright *** -->
|
|
|
|
<!--startcut ==========================================================-->
|
|
<HR><P>
|
|
<CENTER>
|
|
<!-- *** BEGIN navbar *** -->
|
|
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="nielsen.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue67/okopnik.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../faq/index.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="orr.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
|
|
<!-- *** END navbar *** -->
|
|
</CENTER>
|
|
</BODY></HTML>
|
|
<!--endcut ============================================================-->
|