Programming in C offers considerable flexibility, as you saw in the preceding lesson, but C was originally designed for system-level programming tasks. Perl, by contrast, was designed to be a powerful language for string or word processing. You can most easily see the difference by exploring a programming task.
Some friends recently returned from a couple of weeks' traveling throughout Europe. Upon their return, they commented that they were baffled by the sheer variety of currencies. They're not the first to observe this, of course, but I thought “what a great Perl program to write!” |
Translating currencies is simple once you get the formula and exchange rates. Here in the United States, currency values are usually presented relative to a single US dollar, so the French franc might be valued at 3.381, which means that every dollar you exchange is worth 3.381 francs. Exchange $20 and it will net you 20*3.381, or 67.62FF.
The Exchange program started out life by reading in the current exchange rates for five major world currencies (US dollar, Japanese yen, German deutsche mark, French franc and British pound), prompting for an amount in US dollars, and then showing what its equivalent value was in these other currencies. Much more useful, however, is the capability to translate from any one of these currencies to the other four. Further, there are sites on the Internet that show the daily currency exchange rate, so ensuring up-to-date rates is another desirable program feature.
Let's see how to carry this out with Perl and UNIX.
The basic logic flow (or algorithm if you want to be technical about it) for the Exchange program is this:
Read current exchange rates Repeat Ask user for an amount and currency Translate that into US dollars Show the equivalent value in all five currencies Until done
Perl supports subroutines to help develop clean, readable programs, so let's have a peek at the main code in the program:
#!/usr/local/bin/perl &read_exchange_rate; # read the exchange rate table into memory # now let's cycle, asking the user for input... print "Please enter the amount, appending the first letter of the name of "; print "the currency that you're using (franc, yen, deutschmark, pound) - "; print "the default value is US dollars. "; print "Amount: "; while (<STDIN>) { ($amnt,$curr) = &breakdown(chop($_)); $baseval = $amnt * (1/$rateof{ $curr}); # translate currency into USD printf("%2.2f USD, ", $baseval * $rateof{'U'}); # USA printf("%2.2f Franc, ", $baseval * $rateof{'F'}); # France printf("%2.2f DM, ", $baseval * $rateof{'D'}); # Germany printf("%2.2f Yen, and ", $baseval * $rateof{'Y'}); # Japan printf("%2.2f Pound Amount: ", $baseval * $rateof{'P'}); # England }
The first line needs to point to your Perl interpreter; an easy way to find it is to use which perl on the command line.
I've added some fancy output formatting with the print statement, but otherwise this code is quite similar to the algorithm you've already seen. Notice that the array rateof uses the first letter of the currency as an index and returns the current exchange rate relative to the US dollar (that is, $rateof{'F'} is actually 3.381).
One slick thing about Perl is that subroutines can return a list of variables, as you see demonstrated in the call to the “breakdown” subroutine, which returns both $amnt and $curr.
Two subroutines are included in this program, read_exchange_rate and breakdown. Let's consider them in reverse order.
The breakdown subroutine receives the user currency entry and splits it into two parts: the numeric value and the first letter of the currency indicator, if any (the default currency is US dollars):
sub breakdown { @line = split(" ", $_); # chop at space $amnt = $line[0]; if ($#line == 1) { $curr = $line[1]; $curr =~ tr/a-z/A-Z/; # uppercase $curr = substr($curr, 0, 1); # first char only } else { $curr = "U"; } return ($amnt, $curr); }
I won't go into too much detail here—Perl can be somewhat overwhelming when you first start working with it—but if the subroutine is given a value such as “34.5 yen” it will return $amnt = 34.5 and $curr = 'Y'.
The current exchange rate is read in from an associated data file, exchange.db, which contains the exchange rate for the five currencies (although the exchange rate for US dollars is always 1, of course!):
sub read_exchange_rate { open(EXCHRATES, "<exchange.db") || die "Can't find current exchange rates. "; while ( <EXCHRATES> ) { chop; split; $curr = @_[0]; $val = @_[1]; $rateof{$curr} = $val; } close(EXCHRATE); }
Using the notation you're already familiar with from the UNIX shell, Perl specifies whether files are being opened for reading or writing with the direction of the redirect arrow. Here on line two you can see that the exchange.db file is being opened for reading. The || die is a shorthand way of saying, if the open fails, output the error message and quit immediately.
The exchange.db data file looks like this:
P 0.594 D 1.687 F 5.659 Y 132 U 1
Here's the cool part about this program: The exchange rates can be lifted off a handy Web page automatically, to ensure that they're always current and accurate.
I accomplish that task by using the lynx text-based Web browser to grab a page off Yahoo Financials that has the exchange rates, then a few simple UNIX commands in a pipeline to strip out the information I don't want and reformat it as needed.
It's all dropped into a shell script, build-exchrate:
#!/bin/sh # Build a new exchange rate database by using the data on Yahoo # special case for the British Pound needed... lynx -dump http://quote.yahoo.com/m3?u | awk '/U.K./ { if (NF > 4) print "P "$3 } ' # now let's get the other three... lynx -dump http://quote.yahoo.com/m3?u | awk '/Can/,/SFran/ { if (NF > 4) print $1" "$2 } ' | cat -v | sed 's/M-%/Y/' | egrep '(U.K.|DMark|FFranc|Yen)' | sed 's/U.K./P/;s/DMark/D/;s/FFranc/F/;s/Yen/Y/' echo "U 1" exit 0
To learn more about the useful lynx command, use man lynx on your system. It's a good addition to your bag of UNIX tricks.
The output of this command is exactly the database file format, albeit to the screen rather than the data file:
% build-exchrate
P 0.594
D 1.687
F 5.659
Y 132
U 1
Creating the data file for the program is easy:
% build-exchrate > exchange.db
%
For those of you who are Web developers, lynx users are why you've been putting those ALT tags in your pages whenever you included any sort of images with IMG SRC. Try it and you'll see what I mean. |
Let's now try out the exchange program and see how it works!
% perl exchange.pl Please enter the amount, appending the first letter of the name of the currency that you're using (franc, yen, deutschmark, pound) - the default value is US dollars. Amount: 20 20.00 USD, 113.18 Franc, 33.74 DM, 2640.00 Yen, and 11.88 Pound Amount: 20 pounds 33.67 USD, 190.54 Franc, 56.80 DM, 4444.44 Yen, and 20.00 Pound Amount: 20 yen 0.15 USD, 0.86 Franc, 0.26 DM, 20.00 Yen, and 0.09 Pound Amount: 20 deutschmark 11.86 USD, 67.09 Franc, 20.00 DM, 1564.91 Yen, and 7.04 Pound Amount: 20 franc 3.53 USD, 20.00 Franc, 5.96 DM, 466.51 Yen, and 2.10 Pound
Finally, one last query: In the United States the dynamite RedHat Linux package (a great, inexpensive UNIX for PC-based computers) costs about $50. It's easy to use Exchange to compute the equivalent price overseas:
Amount: 50
50.00 USD, 282.95 Franc, 84.35 DM, 6600.00 Yen, and 29.70 Pound
Amount:
To quit the program, I use ^D to send an end-of-file signal.
You can get an online copy of the Exchange program and its companion build-exchrate shell script by visiting http://www.intuitive.com/tyu24/. |
The Exchange program demonstrates how you can write succinct and sophisticated programs in Perl. It also demonstrates that Perl can be a wee bit confusing if you're uninitiated. That's why your best bet for learning Perl, or any other programming language, is to spend the time to find and read a good tutorial. |
More importantly, the program demonstrates that it's the combination of tools—UNIX commands and Perl— that lets you really create some terrific applications.
There are two main ways to run Perl programs: by typing perl followed by the name of your program (as shown previously), or by specifying them directly on the command line. For the latter approach to work, you'll need to include #!/usr/local/bin/perl as the first line of your program and use chmod to make your program executable. |
Whichever way you choose to invoke your Perl program, the Perl interpreter will scan the program to see whether it all makes syntactic sense, and then actually begin executing the instructions specified.
The scan performed is rudimentary, however, and catches only the most grievous of mistakes. Add a simple -w flag, however, and the interpreter looks much more closely at the program, emitting various warnings (“-w” = “warnings”) for odd constructs and more.
Even Perl programs that work fine can generate quite a variety of warnings! In fact, perl -w is the Perl version of lint.
I'll start by making the exchange.pl program executable, to save a little bit of typing:
% chmod +x exchange.pl
%
Not much output, but no output is good news: Everything worked fine.
I'm going to delete the semicolon after the call to read_exchange_rate in the exchange.pl file so that you can see what happens when the Perl interpreter finds the mistake.
Done. (We'll call that an “edit between the lines,” okay?)
% exchange.pl
syntax error at ./exchange.pl line 7, near "print"
Execution of ./exchange.pl aborted due to compilation errors.
Hmm…line 7, eh? Let's use the -n flag to the cat command to see the first 10 lines, numbered:
% cat -n exchange.pl | head
1 #!/usr/bin/perl
2
3 &read_exchange_rate # read exchange rate into memory
4
5 # now let's cycle, asking the user for input...
6
7 print "Please enter the amount, appending the first letter of the name of
";
8 print "the currency that you're using (franc, yen, deutschmark, pound) -
";
9 print "the default value is US dollars.
";
10 print "Amount: ";
Line 7 isn't where the problem occurs (it's on line 3), but this is a great opportunity to point out that you should never entirely trust the line numbers in compiler or interpreter error messages.
Now let's invoke Perl with the -w flag to see whether it offers more advice on what's wrong with the program:
% perl -w exchange.pl
syntax error at exchange.pl line 7, near "print"
Scalar value @_[0] better written as $_[0] at exchange.pl line 43.
Scalar value @_[1] better written as $_[1] at exchange.pl line 44.
Execution of exchange.pl aborted due to compilation errors.
%
Alas, no help here, but it is showing two old-style lines I have in the read_exchange_rate subroutine.
I'm going to restore the semicolon (though I won't show that here; just use vi to add it) and run the -w flag one more time to see whether there are any additional useful suggestions:
% perl -w exchange.pl
Scalar value @_[0] better written as $_[0] at exchange.pl line 43.
Scalar value @_[1] better written as $_[1] at exchange.pl line 44.
Use of implicit split to @_ is deprecated at exchange.pl line 42.
Use of implicit split to @_ is deprecated at exchange.pl line 42.
Use of implicit split to @_ is deprecated at exchange.pl line 42.
Use of implicit split to @_ is deprecated at exchange.pl line 42.
Name "main::EXCHRATE" used only once: possible typo at exchange.pl line 47.
Use of uninitialized value at exchange.pl line 45, <EXCHRATES> chunk 6.
Use of uninitialized value at exchange.pl line 45, <EXCHRATES> chunk 6.
Please enter the amount, appending the first letter of the name of
the currency that you're using (franc, yen, deutschmark, pound) -
the default value is US dollars.
Amount:
Wow! Lots of output, most of which is telling me that there are new, fancier ways to specify things (for example, use $_[1] instead of @_[1]).
Buried in all of this output, however, is a bug in the program that the Perl interpreter found:
Name "main::EXCHRATE" used only once: possible typo at exchange.pl line 47.
A closer look at the read_exchange_rate subroutine shows what's wrong:
% cat -n exchange.pl | tail -12
37 sub read_exchange_rate {
38 open(EXCHRATES, "<exchange.db") ||
39 die "Can't find current exchange rates.
";
40
41 while ( <EXCHRATES> ) {
42 chop; split;
43 $curr = @_[0];
44 $val = @_[1];
45 $rateof{ $curr} = $val;
46 }
47 close(EXCHRATE);
48 }
Can you see the problem it has found? The open statement creates a file handle called EXCHRATES, which is then used in the while statement, but when I went to close the file handle, I forgot the trailing s and called it EXCHRATE.
An easy fix, fortunately!
Even the most carefully written Perl programs can have problems lurking. The -w flag isn't ideal, but you should become familiar with its use and learn how to distinguish important warnings from unimportant ones. |
In this case the bug identified wouldn't have broken anything or generated any incorrect results, but if I had continued to improve Exchange, not closing the file handle could have become a significant problem down the road.
Earlier I recommended that you buy a good Perl tutorial book to learn more about the language. I'm going to revise that a bit, because the standard Perl installation includes a ton of online documentation. |
If you've been trying all the examples as you've been reading the lessons, you're already familiar with the standard UNIX man page format and how to find the information you see therein. Man pages are good for summaries of how to work with individual commands, but they're much less useful for explaining large, complex programs such as the C shell, the Elm Mail System, or the Perl interpreter.
That's why the Perl documentation is broken into a staggering number of man pages:
% man -k perl | grep 1
a2p (1) - Awk to Perl translator
perl (1) - Practical Extraction and Report Language
perlLoL (1) - Manipulating Lists of Lists in Perl
perlXStut (1) - Tutorial for XSUBs
perlapio (1) - perl's IO abstraction interface.
perlbook (1) - Perl book information
perlbot (1) - Bag'o Object Tricks (the BOT)
perlbug (1) - how to submit bug reports on Perl
perlcall (1) - Perl calling conventions from C
perldata (1) - Perl data types
perldebug (1) - Perl debugging
perldelta (1) - what's new for perl5.004
perldiag (1) - various Perl diagnostics
perldoc (1) - Look up Perl documentation in pod format.
perldsc (1) - Perl Data Structures Cookbook
perlembed (1) - how to embed perl in your C program
perlfaq (1) - frequently asked questions about Perl
perlfaq1 (1) - General Questions About Perl
perlfaq2 (1) - Obtaining and Learning about Perl
perlfaq3 (1) - Programming Tools
perlfaq4 (1) - Data Manipulation
perlfaq5 (1) - Files and Formats
perlfaq6 (1) - Regexps
perlfaq7 (1) - Perl Language Issues
perlfaq8 (1) - System Interaction
perlfaq9 (1) - Networking
perlform (1) - Perl formats
perlfunc (1) - Perl builtin functions
perlguts (1) - Perl's Internal Functions
perlipc (1) - Perl interprocess communication (signals, fifos, pipes, safe subprocesses, sockets, and semaphores)
perllocale (1) - Perl locale handling (internationalization and localization)
perlmod (1) - Perl modules (packages and symbol tables)
perlmodlib (1) - constructing new Perl modules and finding existing ones
perlobj (1) - Perl objects
perlop (1) - Perl operators and precedence
perlpod (1) - plain old documentation
perlre (1) - Perl regular expressions
perlref (1) - Perl references and nested data structures
perlrun (1) - how to execute the Perl interpreter
perlsec (1) - Perl security
perlstyle (1) - Perl style guide
perlsub (1) - Perl subroutines
perlsyn (1) - Perl syntax
perltie (1) - how to hide an object class in a simple variable
perltoc (1) - perl documentation table of contents
perltoot (1) - Tom's object-oriented tutorial for perl
perltrap (1) - Perl traps for the unwary
perlvar (1) - Perl predefined variables
perlxs (1) - XS language reference manual
s2p (1) - Sed to Perl translator
POSIX (3) - Perl interface to IEEE Std 1003.1
Quite a few man pages, eh?
The good news (I think) is that the standard perl man page offers a suggested order for reading the man pages that can help overcome some of the gasping, drowning feeling you probably have right now!
For ease of access, the Perl manual has been split up into a number of sections:
perl Perl overview (this section) perldelta Perl changes since previous version perlfaq Perl frequently asked questions perldata Perl data structures perlsyn Perl syntax perlop Perl operators and precedence perlre Perl regular expressions perlrun Perl execution and options perlfunc Perl builtin functions perlvar Perl predefined variables perlsub Perl subroutines perlmod Perl modules: how they work perlmodlib Perl modules: how to write and use perlform Perl formats perllocale Perl locale support perlref Perl references perldsc Perl data structures intro perllol Perl data structures: lists of lists perltoot Perl OO tutorial perlobj Perl objects perltie Perl objects hidden behind simple variables perlbot Perl OO tricks and examples perlipc Perl interprocess communication perldebug Perl debugging perldiag Perl diagnostic messages perlsec Perl security perltrap Perl traps for the unwary perlstyle Perl style guide perlpod Perl plain old documentation perlbook Perl book information perlembed Perl ways to embed perl in your C or C++ application perlapio Perl internal IO abstraction interface perlxs Perl XS application programming interface perlxstut Perl XS tutorial perlguts Perl internal functions for those doing extensions perlcall Perl calling conventions from C (If you're intending to read these straight through for the first time, the suggested order will tend to reduce the number of forward references.)
I find the online documentation overwhelming, so don't worry if this doesn't make you want to leap online and read it all.
The smarter way to learn more about Perl is to read the online documentation. You can start at http://www.perl.org/ or jump straight to the terrific Perl 5 Desktop Reference in HTML form at http://reference.perl.com/guides/perl5.html.
Start with the FAQs and the basic Perl man page, and then graduate to a book on the subject (or even a course)—you'll be a Perl expert. |
There are useful pieces to the Perl environment other than just the -w flag to the interpreter! In this section I'll highlight some special command flags worth knowing to help you get the most out of Perl. |
An interesting variation in debugging requires another flag, the -e (execute the following command) flag. It will let you actually use the Perl interpreter interactively:
$ perl -de 1 Loading DB routines from perl5db.pl version 1 Emacs support available. Enter h or `h h' for help. main::(-e:1): 1 DB<1> print "Hi!"; Hi! DB<2> q %
The 1 was actually a command to the Perl interpreter, and it was because I specified -d for debugging that the interpreter executed the command and then stopped for input.
If you'll be using Perl to write Common Gateway Interface (CGI) scripts for a Web server (which I'll talk about again in the next hour), you'll want to explore the -T (“taint”) flag, which keeps close track of the flow of user input for security. See perlsec for more information.
Finally, no discussion of Perl can be complete without highlighting the terrific Perl developer community and its Comprehensive Perl Archive Network (CPAN). The best place to learn about it is http://www.perl.com/CPAN-local/CPAN.html.
You can also use Perl interactively to learn about the CPAN modules available, though I've never had any luck with it myself. Try entering perl -MCPAN -e shell. |
The Perl language is well worth spending some time learning. The UNIX shell offers various capabilities, but you'll undoubtedly hit the edges as you become more sophisticated; that's where Perl can really fill in the gaps. |
You can find a great list of Perl books—many with reviews included—maintained by Perl guru Tom Christiansen at http://www.perl.com/perl/critiques/index.html.