Troubleshooters.Com Presents

Linux Productivity Magazine

Volume 2 Issue 11, November 2003

Perl

Copyright (C) 2003 by Steve Litt. All rights reserved. Materials from guest authors copyrighted by them and licensed for perpetual use to Linux Productivity Magazine. All rights reserved to the copyright holder, except for items specifically marked otherwise (certain free software source code, GNU/GPL, etc.). All material herein provided "As-Is". User assumes all risk and responsibility for any outcome.


See also Troubleshooting Techniques of the Successful Technologist
and Rapid Learning: Secret Weapon of the Successful Technologist
by Steve Litt

[ Troubleshooters.Com | Back Issues |Troubleshooting Professional Magazine ]


 
Perl is humble. It doesn't try to tell the programmer how to program. It lets the programmer decide what rules today, and what sucks. It doesn't have any theoretical axes to grind. -- Larry Wall (from Perl, the first postmodern computer language)

CONTENTS

Editor's Desk

By Steve Litt
Use of Linux doesn't require genius anymore than use of Windows requires stupidity. These widespread perceptions are false.

That being said, the Windows Culture encourages stupidity. It's how they foster the dependence necessary to maintain a monopoly. It's how they differentiate themselves from UNIX type operating systems, which are easy to configure and customize with no more than a text editor.

Fact is, people computed just fine before Windows. Legal secretaries used menus and commands to do their work in DOS, and occasionally, when tech support was too slow in arriving, they fixed operating systems. The old question, "can my grandmother use it", is misleading. Grandma has been brainwashed, by those who find it profitable to so brainwash, that she cannot perform any task which isn't fully prompted with clicks and drags. Grandma has no idea of her capabilities.

The Linux culture encourages intelligence. Users are encouraged to choose the best desktop environment, the best browser, the best word processor, the best email client, from among many. Users are encouraged to configure these programs to suit their work habits and needs. If Windows is a culture of dependence, Linux is a declaration of independence. Use Linux however you want. And spread the word.

So Linux grows.

Young or old, smart or dumb, male or female, the person long exposed to the Linux culture soon finds that he or she has the brainpower to take the next step -- writing computer programs to make his or her work easier. Many fine languages abound on the Linux platform: C, C++, Java, Python, Ruby, and Perl. Each has its advantages and disadvantages.

This month's Linux Productivity Magazine discusses one of those languages, Perl. The technical guts of Perl aren't discussed, because Troubleshooters.Com features the excellent Litt's Perls of Wisdom site, which explains most of what you need to know about Perl syntax and usage. Instead, this month's LPM issue discusses the thinking that goes into choosing Perl as a language, and the thinking that goes into writing a Perl program.

So kick back, relax, and enjoy. If you use Linux or free software, this is your magazine.
Steve Litt is the author of Samba Unleashed.   Steve can be reached at his email address.

Help Publicize Linux Productivity Magazine

By Steve Litt
Loyal readers, I need your help.

For months I've publicized Linux Productivity Magazine, expanding it from a new magazine to a mainstay read by thousands. There's a limit to what I can do alone, but if you take one minute to help, the possibilities are boundless.

If you like this magazine, please report it to one of the Linux magazines. Tell them the URL, why you like it, and ask them to link to it.

I report it to them, but they don't take it very seriously when an author blows his own horn. When a hundred readers report the magazine, they'll sit up and take notice.

Reporting is simple enough. Just click on one of these links, and report the magazine. It will take less than 5 minutes.

News Mag
Submission URL or Email address
Comments
Slashdot
http://slashdot.org/submit.pl
Just fill in the short form.
LinuxToday
http://linuxtoday.com/contribute.php3
Just fill in the short form.
Linux Weekly News
lwn@lwn.net
Just tell them the URL, why you like it.
NewsForge
webmaster@linux.com
Just tell them the URL, why you like it.
VarLinux
http://www.varlinux.org/vlox/html/modules/news/submit.php
Just fill in the short form.
LinuxInsider.Com,
Newsfactor Network
http://www.newsfactor.com/perl/contact_form.pl?to=contact
Just tell them the URL, why you like it.
The Linux Knowledge Portal
webmaster@linux-knowledge-portal.org
Just tell them the URL, why you like it.
OS News
http://www.osnews.com/submit.php
Just tell them the URL, why you like it.
DesktopLinux
http://www.desktoplinux.com/cgi-bin/news_post.cgi
Only for LPM issues involving the Linux desktop, not for programming or server issues.

If you really like this magazine, please take 5 minutes to help bring it to a wider audience. Submit it to one of the preceding sites.
Steve Litt is the author of Samba Unleashed.   Steve can be reached at his email address.

Comparative Religion

By Steve Litt
Perl is the language I use most often. I like it very much, and unless there's a strong reason to the contrary, I use it exclusively. In this article I'll tell you why, and also give you information with which you can choose your own default language.

There's an old saying: "jack of all trades, master of none". Like most programmers, I've had many 4 language days -- days when I coded in 4 different languages. It's often necessary. However, the more you focus on a single language, the better you get at that language, and the more you can accomplish in less time. For that reason, I suggest you pick a "default language" -- that is, a language which you use for everything unless there's a very strong reason to use something else.

My default language is Perl. It's installed on almost all UNIX, Linux and BSD machines, so I really can "write once, run anywhere". It's available no cost or low cost (like $39.00) for Windows from ActiveState, and also Open Source from Siemens as the SiePerl distro. Due to its built in regular expressions, weak typing, and numerous shortcuts, it develops lightning fast. The debugging phase takes longer than I'd like, but I can live with that. Unlike many languages bundled with Linux, Perl expertise actually leads to paid work.

These are my opinions.

Programming language discussions are like religious discussions -- lively, forceful and opinionated. This article discusses C, C++, Java, Python, Ruby, TCL, QT, gTk, bash and Perl. In my opinion these are all excellent languages. I've programmed in bad languages in the past, so I know a dog when I see one, and none of these is a dog.

I've programmed hundreds of thousand lines of C, C++ and Perl, tens of thousands of lines of Java and Python, and thousands of lines of bash shellscirpts. I have a passing knowledge of Ruby because it's similar to C++, Java, and Python. I know little of gTK and QT, and have only a passing familiarity with TCL.

The following is a brief comparison of the languages mentioned:

Language
Description
Advantages
Disadvantages
Availability
C
A slim, trim, machine level compiler.
  • Creates fast runtime programs. 
  • Complete access to hardware, memory and I/O. 
  • Strict type checking helps prevent errors.
  • The language has few high level constructs, so development is relatively slow. 
  • Heavy use of memory pointers, and necessity of the programmer managing the memory, often leads to intermittents and bugs. 
  • Strict type checking slows development.
Installed by default on all UNIX/Linux/BSD machines. Can be purchased for Windows.
C++
C + OOP
  • All advantages of C, although not quite as fast runtime. 
  • Classes and objects make programs more scalable. 
  • Strict type checking helps prevent errors. 
  • Full object encapsulation makes for rock solid programs, except for pointer problems and buffer overflow.
  • The same disadvantages as C. 
  • Strict type checking slows development.
Available for most UNIX/Linux/BSD machines, and installed by default on Linux machines. Can be purchased for Windows.
Java
A machine independent language compiling to language specific bytecodes. Built from the ground up to be object oriented.
  • Scalable to huge projects due to innate OOP. 
  • Built-in memory garbage collection and lack of pointers minimize bugs. 
  • The debugging phase of Java programs is incredably fast. 
  • Strict type checking helps prevent errors. 
  • Full object encapsulation makes for rock solid programs.
  • Huge market for Java programmers.
  • Runtime performance can be very slow. 
  • Even simple programs require OOP, meaning simple programs aren't as simple as you'd like. 
  • Strict type checking slows development.
Available on all major platforms.
Python
A widely ported interpreter well integrated with OOP.
  • Well integrated OOP makes Python scalable to fairly large projects.
  • Built-in memory garbage collection and lack of pointers minimize bugs. 
  • Loose type checking makes for faster development.
  • Object encapsulation is available if you know how (__variable), so you can write pretty solid programs.
  • Python's indent sensitive nesting makes it the most readable language on the planet.
  • Loose type checking encourages subtle bugs.
  • Runtime performance cannot match C or C++.
  • There's almost no market for Python programmers.
Available on UNIX, Linux, BSD and Windows. Packaged with most Linux distributions, but often not installed.
Ruby
An interpreter built from the bottom up with OOP in mind. Somewhat similar to Perl and Python.
  • Well integrated OOP makes Ruby scalable to large projects.
  • Built-in memory garbage collection and lack of pointers minimize bugs. 
  • Loose type checking makes for faster development.
  •  Object encapsulation makes for solid programs.
  • Runtime performance cannot match C or C++.
  • There is zero market for Ruby programmers.
Available, but seldom installed, on Linux. On other platforms one can compile Ruby from scratch and install.
TCL
An interpreter whose syntax and use is very different from C or the other languages discussed here. Those who know how can write substantial programs with just a few lines of TCL, but it's not very scalable.
  • Substantial programs can be made with just a few TCL statements.
  • No OOP, and not very scalable.
  • TCL is seldom used, and there's little market for TCL programmers.
Bundled with, and usually installed on Linux. Available for other platforms, including Windows.
QT
A compiled language designed to create graphical applications.
  • Creates fast and efficient graphical applications.
  • There are a few QT jobs out there.
  • Complicated to use.
Bundled with Linux, available via compilation on most other platforms.
gTk
A compiled language designed to create graphical applications.
  • Creates fast and efficient graphical applications.
  • There are a few gTk jobs out there.
  • Complicated to use.
Bundled with Linux, available via compilation on most other platforms.
bash A command interpreter that can be used as a language.
  • Outstandingly modularity is achieved with separate executables, piping and redirection.
  • Quality control is easy if you assemble the program from tested, known good executables.
  • Non-looping performance is as fast as the optimized programs it calls.
  • Available from very early in bootup, so bash scripts can be used to control bootup.
  • Looping performance is unacceptably slow.
  • The syntax is maddeningly quirky.
  • No OOP.
  • Little support for complex data constructs
Installed on all Linux boxes. Bash, or something very similar, is installed on almost all UNIX and BSD boxes. Can only be achieved on Windows with mating products such as
Perl

NOTE: This evaluation is for Perl 5.
An interpreter optimized to do big things with few lines. OOP has been tacked on after the fact.
  • Availability of OOP makes Perl scalable to moderate sized projects.
  • Built-in memory garbage collection and lack of pointers minimize bugs. 
  • Loose type checking makes for faster development.
  • Used ubiquitously throughout the UNIX/Linux/BSD world. 
  • A Perl program is likely to run on any target machine.
  • Substantial market for Perl skills.
  • Very fast for an interpreter.
  • The CPAN Perl module repository contains tools to simplify almost anything you need to do.
  • Lack of encapsulation and private variables can lead to bugs in larger projects.
  • The "many ways to perform a task" philosophy of Perl leads to unreadable code and difficult debugging, and a lack of "best practices".
  • Lack of "best practices" leads to some mighty bad Perl code out there.
Installed by default on all Linux, BSD and Unix machines. Available for Windows and all other platforms.

One chooses a language to meet the need at hand. That being said, it's best to choose a default language to use in on those occasions when you don't have strong reasons to do use a different language. The default language I've chosen is Perl.

Perl's not perfect. It's not even the language I like the best. I prefer Python. But I choose Perl over Python for the following reasons:
  1. Perl's most likely to be installed on the majority of machines.
  2. Perl's CPAN library is one of the most complete, and can be used to accomplish almost anything in Perl.
  3. There's actually paid work to be done in Perl.
Many people like Ruby because it's OOP from the ground up, and it's a powerful language. However, the same three reasons I choose Perl over Python apply doubly to Ruby.

The choice of Perl over Python and Ruby is a somewhat difficult case to make. Choosing Perl over C, C++ and Java is much more supportable.

C and C++ require the programmer to delete allocated memory, and they depend on memory pointers. These two factors almost guarantee bugs in any C or C++ program. I mean bugs that survive the debugging stage of development and are seen by the user. In many cases, these bugs are exploitable by crackers as buffer overrun errors. Also, C and C++ provide little in the way of higher level constructs, so the programmer is forced to build each app from scratch. Perl, especially fortified with the CPAN repository, enables very rapid application development.

Then there's Java. Java's a wonderful language, with Perl's advantages of memory management and lack of pointers. Java is such an organized language that it almost debugs itself. The debugging phase of Java is MUCH quicker than that of Perl. But oh, the coding phase! Java is huge. There's tons to be learned. And it's OOP only, which means that even in problem domains not resembling objects, one must still use objects. Hello World requires OOP code. And then there's the matter of Java's performance. It's getting better, but it's still slow.

gTk and QT are just too complicated for me to spend time learning. Bash is great as long as the program stays under 100 lines. TCL is just too different -- I'm used to the Pascal derived languages -- C, C++, Java, Perl, Python, Ruby, VB, etc. Give me Lisp, Prolog, or yes, TCL, and I'm a newbie again.

How to Choose Your Default Language

What are your priorities? Are you writing mostly device drivers? If so, you need C. Are quick development and freedom from bugs your priority? Stay away from C and C++. Is your main priority getting a job? I've got one word for you -- Java! Or C# if you swing that way. Do you want the most readable language ever created, and get great OOP, quick development and freedom from bugs to boot? Python! Do you want an OOP from the ground up language? Java or Ruby. Do you want the most ubiquitous interpreter in the UNIX world, with quick development and freedom from memory bugs? Perl.

I'd caution you that it can be heartbreaking to work with a language nobody else uses. I have a buddy who works wonders with a language called REBOL. He talks about it continuously and shows off his work on a regular basis. It does no good -- REBOL costs money so we all use C, Java, Perl, Python and Ruby. He has no community from which to garner further knowledge. My friend walks this world alone. There's something to be said for working in a common language, even if it's not ideal.
Steve Litt is the author of the Universal Troubleshooting Process courseware.   Steve can be reached at his email address.

Perl as an Admin's Language

By Steve Litt
The first step in the evolution from user to power user is the use of scripts to accomplish specific tasks. Most of us start with shell scripts. In DOS and Windows we used "batch files". In UNIX and its derivatives and workalikes we use shellscripts. Shellscripts are great because, when they accomplish simple things, they're simple to write. But as the task at hand gets more complex, shellscripts become ever more cumbersome.

The next step is to use an interpreter to accomplish a task. Perl, Python, and Ruby are examples. Perl is wonderful because it's installed and running on virtually all UNIX and UNIX workalike systems (i.e. Linux).

Perl as a Glue Language

A glue language is a language used to "glue" various other executables together. The ultimate glue language, of course, is bash (shellscripts in general).

Perl can be used to call other executables, just like a shellscript. To call another executable, use the backtick operator to make the call another program and obtain its output, and use a manipulation of the $? variable to obtain the child process's return value (but only if it returns in the range of -127 through 128). The following code snippet shows this:

my @output = `./child.pl`;
foreach my $line (@output) { chomp($line); print "$line\n"; }
my $childexit = ($? <= 32768 ? $? >> 8 : 0 - ((65536 - $?) >> 8));
print "\nReturn value=>$childexit<\n";


Shellscripts can do that quite easily, but Perl brings other assets to the table:
Shellscripts evaluate loops via the test program, which instantiates very slowly. Hence shellscript loops are incredibly slow. Perl loops are fast. Perl has a much cleaner syntax than bash (almost anything has a cleaner syntax than bash). Perl has full logic, OOP, and interfaces without number.

So if you need to run several processes, and you need logic to decide which processes and how to run them, glue it all together with Perl.

Perl as a File Conversion Language

One way admins use Perl is as a file conversion program. Its simple and efficient file I/O, loops and regular expressions make Perl a natural for this usage. If the perl program is used as a filter, meaning it takes its input from stdin and places its output in stdout, just a few lines of Perl can effect complex file transformations in less than 10 lines of code. While it's true that AWK can be used the same way, few people know AWK, and Perl can do anything AWK can do.

Perl as a Reporting Language

There are two kinds of reports -- cutesy reports with lots of graphics, whitespace and fonts, and reports showing the facts. The first type typically requires lots of programming with tools like Crystal Reports or whatever corporationally correct report generator they're using this month. The latter type requires a few minutes of Perl programming.

Perl's built in regular expressions remove the drudgery, errors and debugging from text parsing. If you've ever tried to parse text with straight C you know what I mean. Beyond that, Perl makes it easy to develop data structures that match the input and output of the program, so you can do it all in memory, typically in a single pass. Perl manages memory for you, so you needn't worry about allocating and freeing memory, and you needn't worry about the effect of local variables on the stack. Stacks, linked lists, binary trees, node trees, arrays, hashes (name->value pairs), and many other data constructs are easily implemented. Oftentimes objects speed your work. For instance, you could create an object to represent the piece of paper, lay out the output there, and when it's full call its print function.

Perl has complete database access via the DBI::DBD interface, so any common DBMS is readable.

Perl for Web Apps

Perl is used extensively to create web apps. At one time it was the most common web programming language, although within the last few years ASP, .NET, PHP, Zope and others have overtaken it. But it's still excellent for a quick and dirty web app, and if you take the time to devise and/or download some webapp specific Perl modules, Perl can be a highly productive web app environment. Perl has all the tools:
Let's say your newly purchased in-house accounting system requires 18 mouseclicks and 4 field entries on 8 screens to change a certain variable that should be changeable by itself. The users are revolting. The outsourcers who created the app with cheap offshore talent will get to it "real soon now".

You tell the boss you can take the pressure off, and he says go for it. You take a couple hours to write a program that manages a single screen interfacing with the correct table, using Perl's DBI::DBD interface. You take another few hours testing it, show it to the boss, and by the end of the day every user knows the URL to save 10 minutes work by using this interface.

The users love you -- you save them time and frustration. The boss loves you. You saved him a lot of trouble. The outsourcers love you. You saved them from an embarrassing situation and bought them lots of time.

In smaller organizations, quickie web apps often morph into full fledged apps over time. The ability to make a prototype in a day often makes the difference between acceptance and rejection of an idea. From there on, you're given time to develop something really nice. It wouldn't be the first time a fulltime job resulted from a one day app.

Summary

Computer administrators have been using Perl for adminstration since the dawn of time. Perl was written by Larry Wall specifically for computer administration. Perl gives administrators 20 inch biceps. But it can be used for more than just administration. Read on...

Steve Litt is the author of "Troubleshooting Techniques of the Successful Technologist".  Steve can be reached at his email address.

Adding Functionalities with Perl

By Steve Litt
Sometimes users require a brand new functionality on their computers. Perl is usually the fastest way to give them the exact functionality they need.

Here at Troubleshooters.Com, I needed a keyboard friendly menu system that would work on a console as well as in a graphical environment. In the DOS world, I had written a couple such programs in C, and it was difficult to say the least. So this time I used Perl. I gave myself 2 days to do it.

The result was UMENU. You can download it from the Troubleshooters.Com site. I use UMENU over 100 times a day. It's perfect.

When I switched to Linux, I needed an outline processor that would run on Linux. The outliners running on Linux at the time were either underdeveloped, stalled in development, or too cumbersome. I adapted the Vim editor to serve as an outliner.

Vim couldn't do the job alone. Inter-outline linking required a method of traversing trees of outlines and writing a tag file. Modularity required several environment variables be set before running Vim. Conversions were needed to convert the outline to HTML, LyX, and other formats. To get all this done quickly, I used Perl. A few days later I had a great outliner called VimOutliner, and over the years others downloaded it and it became a project.

An interesting thing happened. The current VimOutliner developers code mostly in Vim language, with some python, ruby and bash thrown in for good measure. Very little Perl survives. But it was my knowledge and skill with Perl that allowed VimOutliner to be born, instead of spending eternity as a "might work some day" project like most of the other Linux outliners existing in 2001. In a matter of 2 weeks VimOutliner went from a wish to a fully functional outliner, leaving the others in its dust and garnering a stable of high power developers. Perl was VimOutliner's jumpstart.

Perl is an EXCELLENT application development language. Let me repeat that:

Perl is an EXCELLENT application development language. For quick prototypes, it has few peers. I was once hired to create a Perl web app to interface to multiple routers and switches across the nation over the Internet via the SMNP protocol. Now the interesting thing was, this company had a crew of several developers working on a Java version of the same functionality, but they needed it quicker. So I came in and coded it up in a week, using Perl. For my client, it was money well spent. I discovered many opportunities and pitfalls, passed them on to the Java developers, and saved them lots of time. On the expense side alone, it was probably cheaper to hire me to do the Perl app than not to. And of course, my quick prototype allowed them to start collecting revenue several weeks earlier.

Many times, quick prototype impress to the extent that they morph into full blown apps. Perl is an excellent language for full blown apps. It has OOP, including inheritance. While the OOP lacks the encapsulation necessary for huge apps (>100,000 lines), it's perfect for large apps created by 1 to 5 developers. Perl's memory management and lack of pointers mean that finished apps are remarkably bug free, and those bugs that make it into the finished products are usually neither subtle, hard to reproduce, nor hard to attribute to a root cause.

Whatever you're doing, it's probably been done before. In Perl. Therefore, it's likely you'll find the tools you need on CPAN. If so, you can cut development time severalfold.

Steve Litt is the author of the Universal Troubleshooting Process courseware.   Steve can be reached athis email address.

Designing Perl Programs

By Steve Litt
Design is everything. It makes the difference between a working prototype tonight, or a tangled mess next week. It makes the difference between a scalable and maintainable app, or an albatross nobody (including you) wants to touch. This is true in any computer language or development environment. There's no substitute for design. The question is, what design method should you use?

Functional Decomposition for Simple Apps

The first question to ask is whether this will be a simple app. If the app is simple -- the kind of thing that will take a few hours -- the fastest design method is good old functional decomposition, AKA structured programming, modular programming, top down programming, 80's programming, etc. For added design speed, use an outline processor. VimOutliner is an excellent outline processor.

Once you've broken down the task at hand into subroutines calling other subroutines etc, ask yourself 2 questions:
  1. Does any of the subroutines need to keep lots of state information?
  2. Does the problem domain include entities requiring a lot of information?
If either is true to a great enough extent that a pure structured program would be difficult or convoluted, add objects to help whip it into shape. The overall program still isn't OOP, but you have a few objects to keep state and modularity. With any luck you'll have a finished and tested app today or tomorrow.

Bigger Apps Require More Consideration

If the app can't be written in a day, the functional decomposition shortcut will likely cost you time, and possibly require a rewrite. Spend a little more time at the drawing board. Obviously, your first question should be "what do I want the program to do?". A simple sentence should do. The very next question you should ask yourself is this:

What data constructs would cleanly represent what I need to do?

Determine the data

For instance, if you're modeling a system of physical objects, like astral bodies or a shot cannonball, you want to represent each body with an object. But if you tried to represent an office automation program the same way, your design would take a long time, and the resulting program would be suboptimal. With an office automation program, you might start with lists of entities -- employees, customers, vendors. Those become database tables. Then perhaps if the office automation program includes accounting, a table of accounts and a table of transactions.

Perhaps your input or output data is hierarchical -- an outline, or a directory tree, or an org chart. In that case your data might best be represented by a tree of nodes, or a self-referential database table.

Don't forget that the program's configuration and parameters are data -- usually best represented by an object.

Once you've decided the data structure best suited to represent what you need to do, determine the exact data needed, and its relation to other data. An outline processor is a convenient way to organize your thoughts, and place the data elements into the correct objects, database tables, or points on a hierarchy.

Each data element should have a set() method or function, a get() method or function, and a has() method or function. The has() method or function must always be called prior to get() so that you never operate on a null data item.

Think about the ways the data will be stored between program runs or sessions, as well as how it will be stored internally during program execution. Will the data be stored in a relational database, a configuration file, an outline, or some other way? In the program, will you use the data stored on disk, or will you cache it in memory for faster execution? Keep in mind that in a single program different data could be stored in different ways.

Determine the rules

Your next design step is probably to determine the rules. Each piece of data has rules. Certainly one rule is to determine what you'll do if the data is missing. Will it be considered blank, zero, or an error? If an error, will you abort the program immediately, or continue in an intelligent way?

Each piece of data is constructed from inputs. What are the rules for each piece of data construction? Is there a formula? Are there different formulae for different situations? Are there defaults that can be overridden?

To the extent possible, try to keep the rules in data rather than the program's code. This makes the program much more configurable and maintainable.

There are many ways to keep rules in data. With Perl, you can use the eval() function to treat a string as program code. Thus, you could place theis string in data:

my $string = '$tax = (isFood($product) | isMedicine($product) ? 0.06 * price : 0);';
And anywhere  in the program you could use it like this:
my $tax;
my $product = 19;
my $price = 1.00;
eval $string;
print "Price for product $product is $price, tax is $tax.\n";
In the preceding, you'd also need to define functions isFood($)and $isMedicine($). This way, you could change the tax formula by changing the data. If the government started taxing medicine, you could eliminate the$isMedicine()from the string. If the tax rate went up to 7%, you could change the 0.06to 0.07.

You could also keep subroutine names in data, and for different situations call different subroutines, once again with an $eval() statement. That way you call different subroutines (or different objects or whatever) depending on a data lookup, which itself depends on a criteria. The result is that you implement complex logic without if statements -- a great simplifier.

Sometimes simply keeping constants in data is enough. Consider a program to calculate tax rate by zip code.

Determine all the rules, and find ways to keep as much of those rules as possible in data, rather than in program code.

Define the Data

By now you know the needed data structure, the rules, and the way you map those rules into data. Now lay out the data, organizing it carefully by tables, objects, categories, entities, configuration, and however else you can think of. Once again, an outline processor provides you with a quick way to arrange and rearrange your data layout.

Once your data is laid out, to the extent appropriate, map the data into objects.

Code the Data and Object Structure

Now that your data and associated objects/classes are defined, place them in code. Decide on naming conventions, or use the company's standard naming conventions, and use a high power editor like Vim to convert your design to code. Remember, every data item in an object should have set(),get(), and has() methods.

Code Object Methods

Next, code any other methods that are required to implement rules and calculations. Hopefully, most of the rules and calculations are contained in stored data, but to the extent that they're not, code them in methods. Code error detection and handling in the data methods.

Code the App

Finally, write the code that ties all the data together. Perhaps it's a single controller object. Perhaps it's structured code. Whatever it is, code the app. You may need to change the code for the data -- that's OK. The fact that you designed the data so carefully should prevent dead ends and rewrites.

Summary

Perl is a rapid application development environment, meaning it won't slow you down on simple apps. Simple apps can be rapidly designed using functional decomposition, so that's what you should use.

More complex apps require more time consuming design methods. There are many design methods, but for Perl probably a data-first design method is the most practical and productive.

Be sure to do adequate design. The time saved by skimping on design time is invariably consumed in the coding phase, then consumed doubly in the debugging phase, and then you are rewarded with a cumbersome, hard to maintain app. Design well.

Steve Litt is the author of Rapid Learning: Secret Weapon of the Successful Technologist. He can be reached at his email address.

Mercenary Perl

By Steve Litt
I have good news and bad news. Let's start with the bad news...

The Bad News

These are not good times for programmers (developers, whatever you want to call them). This world has countries where you can live quite comfortably for $8,000.00 per year, and those countries house many programmers. These countries have little labor protection -- no OSHA, no stress leave, no maternity leave, no workers comp. If such workers develop ulcers due to 90 hour workweeks and slavedriver bosses, they're discarded like tin cans. Naturally, it's much cheaper to employ these cheap, abuseable and dispensable foreign workers to write code.

We're in a recession right now, so like any other recession, programmers are out of work. But with the new trend toward offshoring, whole development shops are emptying out and turning out the lights. These jobs will likely never come back.

The "hot" languages right now are Java, VB, and C#. Plenty of job ads for these skills -- much more than for Perl.

But this type of big-project work will soon be done outside the United States, so these "hot" languages will be as cold as Python or Ruby.

Nor is offshoring limited to programming. It started with programming because programmers commanded such high wages, especially in the late 1990's. But make no mistake about it, administration can be offshored much more easily than programming, and so can tech support.

The days of big money and great benefits working with computers at large corporations are over, no matter what you do or what language you use.

The Good News

Big business isn't the only employer. In good times, it isn't even the main employer. Startups employ many technologists, and startups are less equipped to deal with the large outsourcers capable of handling an offshore coding crew. Startups are likely to employ one or two well rounded, hotshot computer guys. Computer guys who are equally at home coding up an app as they are upgrading a server operating system. Guys like you.

This recession will end. I think if the Democrats take the white house in 2005, and I think that's a real possibility, the recession will end very soon. If the current crew maintains control, it might take longer. But this recession will end -- the joblessness will end -- hiring will resume. And when it does, small companies will be hiring well rounded hotshots like you.

When you get into one of these small companies, you'll be their sole source of tech knowledge. If you whip out a needed app in a day, they'll sing your praises, whether or not you use corporationally correct technology. In such an environment, Perl is your friend. As stated several times in this magazine, Perl lets you prototype an app in days, and then perfect it over time. Perl is a glue language that can bring together distinct apps from different vendors. Perl can be used to expose legacy apps through web interfaces.

To the extent that you're a ninja in your chosen language, you'll be able to work miracles and make good money. That's why it's vital to get lots of practice in a very productive language. Given the job market and what Perl can do, for me Perl is that language.

The Perl Mercenary Goes Anywhere

Like all good mercenaries, the Perl mercenary chooses sides based on money. He or she might work Perl for Linux one day, and Perl for Windows the next. Most Linux and UNIX machines come loaded with Perl. Windows machines normally don't come loaded with Perl, but it's easy enough to get. ActiveState (URL in URL's section) is the Cadillac of the industry, providing Perl and Python for Windows, as well as a host of other tools facilitating high productivity programming. You can also obtain the Siemens Perl distribution for Windows (URL in URL's section).

My experience has been that, when adjusted for directory separators and path separators, and not using OS dependencies, Perl programs written on Linux run identically on Windows, and vice versa.
Steve Litt is the author of the Universal Troubleshooting Process courseware.   Steve can be reached athis email address.

Where to Find Perl Information

By Steve Litt
Perl's so ubiquitous that there's no shortage of Perl information.

General Purpose Perl Books

I like two general purpose Perl books, Core Perl and Perl 5 for Dummies. I occasionally also use Programming Perl.

Perl 5 for Dummies

You have a job interview in 2 days, and one of the skills they ask for is Perl. Your Perl isn't too good. What do you do?

Read Perl 5 for Dummies, ISBN 0-7645-0044-9, by Paul Hoffman. It defines enough Perl terms that you'll sound authoritative at your interview.

This book is organized easy tasks to hard tasks, so it's an excellent learning tool. It covers all the bases: text, math, arrays, hashes,  branching and looping, CGI, file IO, regex, and OOP. This book takes you beyond beginner skills into the intermediate range. It might be called "For Dummies", but if you master this material you'll be a competant Perl programmer.

Hoffman's immaculate organization makes this book an outstanding reference volume. You can find almost anything in the book by glancing at the table of contents. There are many Perl books out there -- most more advanced than Perl 5 for Dummies, but Hoffman's book provides such lightning fast lookup that I carry it everywhere I go.

Perl 5 for Dummies is great for Perl syntax. With nothing but that book, you can write substantial Perl programs. But Perl is a "many ways to perform a task" type of language, and some of those ways are easier than others. To become a truly productive programmer, you must read a more advanced book...

Core Perl

Core Perl, ISBN 0-13-035181-4, by Reuven M. Lerner, starts where Perl 5 for Dummies left off. Like the For Dummies book, its organization is crystal clear, intuitive, and obvious. But Core Perl is much more advanced.

Many advanced books read like man pages. No examples -- really bright people don't need examples. What -- you can't read BNF notation? Then you shouldn't program!

Core Perl is not one of those elitist books. Examples abound. Everything's understandable, even without a computer science PH.D or an IQ over 200. It's plain English and plain code.

Yet everything's there. If it comes with the basic Perl distribution, it's covered. Completely.

Perl 5 for Dummies is organized easiest to hardest, because it's primarily a learning tool. Core Perl starts with syntax, then subroutines, strings and regex, modules, objects, tying, file IO, networking, relational databases, database apps, maintenance and security, CGI and web apps, mod_perl, and mason. The book is over 500 pages, and by page 140 you've completed all Perl syntax, features and subroutines. The majority of the book focuses on the tough stuff.

Armed with this book, you can walk into almost any Perl situation and write the right app.

Programming Perl

I view Programming Perl, ISBN 1-56592-149-6 as a backup for Core Perl. If I can't find it in Core Perl, I'll find it in Programming Perl.

Programming Perl is short on organization, long on authority, and written by industry leaders. This book is written by Perl inventor Larry Wall,  Tom Christianson and Randall Schwartz -- a who's who of Perl society. The equivalent Linux book would be written by Linus Torvalds and Alan Cox. If you need a piece of information, it's in here. If you need to know the best way to do something, it's in here. The only problem is it will be hard to find.

This book is best kept as a reference backup for Core Perl, and as rainy day reading so that one day you'll be a true Perl master.

Specialized Perl Books

Mastering Perl/Tk

None of us likes programming GUI. We'd rather concern ourselves with the problem domain. But sometimes we need to program GUI. With Perl, the easiest way to go GUI is by writing HTML. But HTTP is stateless, and sometimes that makes things too difficult.

One of the best ways to GUIize Perl is with the Tk widget set. Mastering Perl/Tk, ISBN 1-56592-716-8, by Steve Lidie and Nancy Walsh, is the perfect book to fulfil its title -- mastering Perl/Tk. All the information, and I mean ALL the information, is in here. The organization is perfect, both for learning and for lookup. Its 23 granular chapters are named to make it obvious where to look for what you need. The organization is crystal clear, and the contained information is everything you need. If you need to GUIize Perl, you need this book.

Web References

Steve Litt's Perls of Wisdom

When you're coding up an app under a crushing deadline and minute scrutiny, this is the website you want for Perl information. Billed as a website giving you "The 10% you need -- for 90% of your work", it gives you the syntax you need to quickly code loops, branches, subroutines, references, OOP, regex, CGI, arrays, hashes, and more. Unlike man pages and packaged Perl documentation, examples are plentiful. The organization is clean and optimized for quick lookup.

The regex page for Litt's Perls of Wisdom has a worldwide reputation for being one of the best ever explanations of regular expressions, and it's read by Perl programmers and non-Perl programmers alike. The page on references and subroutines walks you through all the syntax do's and don'ts, with examples.

If you program Perl, this web page is indispensable.

CPAN

Perl's a great language, but what makes it truly great is the huge number of premanufactured modules available for specific tasks. All these modules are gathered at the CPAN (Comprehensive Perl Archive Network) website. There are exquisite lookup facilities, both by category drilldown and by word search. Most of the modules have excellent documentation so you can see what they do, and how to do it, before downloading and installing them.

If you're faced with a programming task, and you don't immediately know how to do it, pay a visit to CPAN. It's likely someone has already done it, and your solution is just a download away.

Steve Litt is the author of Rapid Learning: Secret Weapon of the Successful Technologist. He can be reached at his email address.

Rapid Learning Perl

By Steve Litt
20 years ago you could spend 2 years learning a language, and still have plenty of time to profit from it. Now that time is more like 2 weeks, or, if you have an upcoming interview where one of the requirements is Perl, 2 days. How do you do it? Here's how:
When Rapid Learning Perl, use Steve Litt's Perls of Wisdom, and optionally, use the excellent book Perl 5 for Dummies.

Hello World

Create the following file, called test.pl:

print "Hello World\n";

Now run that file with the following command:
perl ./test.pl
The file will run and produce the following output:

[slitt@mydesk slitt]$ perl ./test.pl
Hello World
[slitt@mydesk slitt]$

The next step is to make the file itself executable, so you needn't manually invoke perl on the command line. To do that, add a "shebang" line before the print statement:

#!/usr/bin/perl -w
print "Hello World\n";

The line in red is called a "shebang" line, because it starts with a comment character (#), then an exclamation point, which is often called a "bang", and then the command through which to feed the remainder of the program. That program is perl, which resides in /usr/binon most systems. The -w tells the Perl interpreter to issue warnings. Perl without warnings is iffy at best -- the program appears to run smoothly but bugs produce erroneous output -- often subtle errors..

Next, set the program executable with the following command:
chmod a+x ./test.pl
Finally, run the program with the following command:
./test.pl
The program's output is identical -- all you've done is eliminated the need for manually invoking perl on the command line.

Finally, you need to make Perl a little stricter than normal. Normal, default Perl is a loosy-goosey language allowing the programmer to do almost any wild and crazy thing. Perl gladly accepts the most bizarre programming requests, and do what it thinks is best with them. It's the wild wild west.

You don't want that. You want a language with rules, and an interpreter that will stop and make you correct it if those rules are violated. To do that, insert the use strict syntax, as shown in the following code:

#!/usr/bin/perl -w
use strict;
print "Hello World\n";

Always insert the use strict line right after the shebang line. Doing so elimates a lot of hard to find bugs.

Congratulations

Congratulations! You've created a Perl program, run it from the Perl interpreter, run it from the command line, and set it to reject illegal syntax. Next stop, scalar variables...

Scalar Variables

Perl has three types of variables -- scalars, arrays (lists) and hashes. A scalar variable is a variable that isn't an array or a hash. Scalar variables always begin with a dollar sign. Scalar variables can hold one of three types of values:
  1. Numeric value
  2. String value
  3. Reference
We'll postpone references to later in this tutorial.

Modify your test.pl so it looks like this:

#!/usr/bin/perl -w
use strict;
my $var = "Hello World\n";
print $var;


The output is exactly like the output in the Hello World article, because your only change is that you put the string into a scalar variable, called $var, before printing it.

Notice the word my preceding the variable. The word my declares$var to be local to the subroutine or block within which it resides. In this case that block is the entire program, but usually it will be in a block delineated by braces { and }. Always use my. First, global variables are the kiss of death in any programming language, including Perl. Second, the use strict declaration will flag as an error variables declared without the my, unless special procedures are followed.

Next, add the following 2 lines:

#!/usr/bin/perl -w
use strict;
my $var = "Hello World\n";
print $var;
$var = 32;
print "Number is ", $var * 10, "\n";


In the preceding code you reassigned a number to $var, and used it as a number in the next print statement. In C you must declare a variable to be either a number or a character pointer, or one of many other distinct types. Perl allows you to change the type of a variable simply by assigning a value of a different type to it.

This is both good and bad. It's good because you needn't go through the convoluted logic C requires just to perform an obvious operation on different types of variables. It's bad because C's strict typechecking is one more way to prevent errors and bugs. On the whole I'd say that for most modern programming, C's typechecking is counterproductive, and Perl's non-typechecking is an advantage, as long as you're careful.

The preceding program produces the following output:

[slitt@mydesk slitt]$ ./test.pl
Hello World
Number is 320
[slitt@mydesk slitt]$

As you can see, $var was treated as a numeric, so it could be multiplied by 10 in the print statement. Note also that the print statement can print a string and a number, without any type conversion or cast, simply by putting commas between the elements to be printed. Simpler than printf or cout, isn't it.

Congratulations

Congratulations! You've used scalar variables to contain both numbers and strings, printed the results, and even multipled a numeric variable by 10. Let's discuss a little more about manipulation of variables...

Numeric and String Manipulation

Numeric manipulation is arithmetic. I won't give you Perl's order of precidence -- I don't know it myself. I use parentheses liberally, ensuring the intended calculation. Generally speaking, if you understand C or Java arithmetic, you understand Perl's:

Operator
Functionality
Example
$var contains
=
Assignment
$var = 8;
8
+
Addition
$var = 8 + 3;
11
-
Subtraction
$var = 8 - 3;
5
*
Multiplication
$var = 8 * 3;
24
/
Fractional Division
(5/2 is 2.50, not 2)
$var = 8 / 3;
2.66666666666667
%
Modulus
$var = 8 % 3
2

Whole number division
$var = (8-(8%3))/3
2

Perl's numeric relational operators are similar to C's:

Operator
Functionality
Example
>
Numeric greater than
if($var > 8)
>=
Numeric greater than or equal
if($var >= 8)
<
Numeric less than
if($var < 8)
<=
Numeric less than or equal if($var <= 8)
==
Numeric equality
if($var == 8)

String Manipulation

Use the dot operator to concatinate 2 strings:
$var = "George" . " " . "W." . " " . "Bush";
In the preceding, $var now contains "George W. Bush".

You often need the length of a string. Use the length() function to get that.

The opposite of concatination is truncation. Personally, I do most truncation with regular expressions, which are discussed later in this tutorial. However, you can also use the substr(). substr() is faster than regular expressions, so if you're truncating in a tight loop, you might want to usesubstr(). Its syntax is as follows:
substr EXPR,OFFSET,LENGTH,REPLACEMENT
LENGTH and REPLACEMENT are optional. Personally, I seldom use REPLACEMENT because it's clearer to replace text with a concatenation after truncation. Here are some ways you can use substr() to truncate from the right and from the left of a string. In the following examples, imagine that:
$string = "Perl Developer":

Task
Statement
Value of $var
Remove 3 characters from the front of the string
$var = substr($string, 3);
"l Programmer"
Remove all but the 3 final characters from the string
$var = substr($string", -3); "mer"
Remove the 3 final characters from the string $var = substr($string, 0, - 3);
"Perl Program"
Remove all but the first 3 chaaracters from the string
$var = substr($string, 0, 3);
"Per"

To remove specific text, you can use the index()and rindex() functions to find the offset of the start of a substring, and then plug that number into the OFFSET or LENGTH arguments of substr(). Information on length(), substr(), index() and rindex()are contained in their respective man pages.

Perl's string relational operators are distinct from its numeric relational operators:

Operator
Functionality
Example
gt
String greater than
if($var gt "cccc")
ge
String greater than or equal
if($var ge "cccc")
lt
String less than
if($var lt "cccc")
le
String less than or equal if($var le "cccc")
eq
String equality
if($var eq "cccc")

Congratulations


Congratulations! You've learned how to manipulate strings, perform arithmetic, and compare both numbers and strings. Now it's time to create loops.

Looping

Computers are dumb -- all they can do is perform repeated calculations very fast. That repetition involves loops. Here's the simplest loop:

#!/usr/bin/perl -w
use strict;
for(my $ss = 1; $ss <= 10; $ss++)
{
print "$ss\n";
}

The preceding program prints the numbers 1 through 10. This for loop is very similar to what you'd find in C. Notice the word my within the parentheses of the for loop. That makes $ss local to the for loop. That's good, unless you want to use the number outside the loop, in which case you'd declare $ss above the loop.

Another Perl looping mechanism is the while loop. The following program prints 1 through 10 in exactly the same format:

#!/usr/bin/perl -w
use strict;
my $ss = 1;
while($ss <= 10)
{
print "$ss\n";
$ss++;
}

In the preceding, the variable is incremented within the loop's block, instead of within the loop itself. Note also that $ss is declared above the loop, so it can be used below the loop.

Loops can be terminated with the last statement. The following code adds a last statement, and also uses the final value of the loop in a print statement below the loop:

#!/usr/bin/perl -w
use strict;
my $ss = 1;
while($ss <= 10)
{
print "$ss\n";
if($ss >= 3)
{
last;
}
$ss++;
}
print "Final value is ", $ss, ".\n";

Obviously, in the preceding code there's no need for the ($ss <= 10) because the block of the loop terminates at 3. However, often times the conditional within the while statement and the conditional triggering the last statement are not related, so that which of the two terminates the loop will be known only at runtime. One example would be a loop reading a file that terminates either on a line containing the word "terminate" or at end of file.

Finally, a special loop construct reads each element of an array and  acts on it. Here's an example:

#!/usr/bin/perl -w
use strict;
my @praises = ("fast", "stable", "easy");
foreach my $praise (@praises)
{
print "Perl is ", $praise, "\n";
}

In the  preceding, @praises is an array (a list in Perlspeak) initiialized to have elements "fast", "stable" and "easy". You might wonder why no parentheses are required around my $praise (@praises) when parentheses are required with the for and while constructs. Welcome to the world of Perl -- like a human language, it has exceptions. When run, the preceding program prints out the following:

[slitt@mydesk slitt]$ ./test.pl
Perl is fast
Perl is stable
Perl is easy
[slitt@mydesk slitt]$

Congratulations

Congratulations! You've now used three types of loops, and learned how to terminate a loop either by exceeding the conditional, or by encountering alast statement within the block of the loop. Next stop, if/elsif/else.

If/elsif/else

Every computer language has facilities by which you can do different things depending on different conditions. In Perl, you do it with if/elsif/else conditionals. Here's the basis:

#!/usr/bin/perl -w
use strict;
for(my $number = 1; $number <= 6; $number++)
{
print $number, " :";
if($number > 5)
{
print "The number is greater than 5\n";
}
elsif($number > 3)
{
print "The number is greater than 3 but less than 6\n";
}
elsif($number == 3)
{
print "The number is equal to 3\n";
}
else
{
print "The number is less than 3\n";
}
}

The preceding program loops through 1 through 6, and for each, prints the number, and then prints whether it's greater than 5, between 5 and 3, equal to 3, or less than 3, based on the if/elsif/else statement. The following is the output of the program:
x
[slitt@mydesk slitt]$ ./test.pl
1 :The number is less than 3
2 :The number is less than 3
3 :The number is equal to 3
4 :The number is greater than 3 but less than 6
5 :The number is greater than 3 but less than 6
6 :The number is greater than 5
[slitt@mydesk slitt]$

Be careful -- order counts. The first condition to be true causes the program to skip over the rest of the elsif and else statements. Thus, if we had tested for greater than 3 before testing for greater than 5, everything bigger than 3 would have printed as bigger than 3 but less than 6 -- clearly an error. Note also that you can have as many or as few elsif clauses as you need. Sometimes you need only if and else, and sometimes you don't even need the else. There is never more than one else.

In order to reduce double negatives and the like, you can use the unless clause, which looks something like this:
unless($number > 1000) 
{
print "Number less than 1000\n";
}
Another branching method is short circuit logic. For instance:
writeLogFile() || writeErrorFile() || die "cannot write files";
The preceding calls writeLogFile(). If that returns non-zero, nothing to the right of it is called. But if it returns 0, then writeErrorFile()is called. If that returns non zero, nothing to the right is called. However, if it returns zero, then the program dies with the message "cannot write files". When used right this can be a readable and compact way of programming.

Less Common Constructs

In keeping with Perl's "many ways to do it", there are other branching constructs. You might not want to code this way if you're not a "many ways to do it" type of guy. BUT, any Perl programmer must be familiar enough with these constructs that he or she knows what they do when he or she encounters them in somebody else's code.
last if x == 0;
print "Not interested\n" unless $price < 1500;
The preceding are if and unless statements respectively, but they're written with the action first and the test second. The main advantage is that parentheses and braces are not required, making this a much more readable form for if and unless statements with a single line of code for the action. Do not use these constructs if there are several statements to be executed, as it would be terribly confusing to someone reading your code.

Then there's the <=> operator. This operator, which operates on 2 numeric arguments, returns -1 if the number to its left is smaller than the one to its right, 0 if they're equal, and 1 if the one to the left is greater than the one to its right. Check out this program:

#!/usr/bin/perl -w
use strict;
for(my $number = 1; $number <= 6; $number++)
{
print $number, " :";
print "<=>", $number <=> 3, "\n";
}

In the precding each number was checked against 3 to see if it was smaller, equal, or bigger, and the result was printed on the line. Thus this single operator is a tri-state detector. The <=> operator is used extensively as a callback routine to custom sorts.

[slitt@mydesk slitt]$ ./test.pl
1 :<=>-1
2 :<=>-1
3 :<=>0
4 :<=>1
5 :<=>1
6 :<=>1
[slitt@mydesk slitt]$

There is a text equivalent of the <=> operator, the cmp operator.

Congratulations

Congratulations! You've learned how to use if/elsif/else constructs to implement conditional execution. You've also learned to implement conditional execution with unless statements, short circuit logic, action-first if and unless statements, and the <=> tri-state operator. Now it's time for arrays.

Arrays

Most computer programs use arrays. Arrays are used to hold many items, identified by number (that number being the subscript). In Perl, arrays are called "lists". Unlike most other computer languages, a single Perl array can hold elements consisting of very different types. However, from a data processing standpoint there's seldom a reason to do that -- arrays usually represent several like entities.

Array variables, when referring to an entire array, begin with an at sign (@). Thus, @books would be an array, but $books is a scalar. But, $books[2] refers to the third element of the @books array. Sound confusing? You bet it is! Here's a summary:

@books
An array variable (list variable)
$books[3]
The fourth element of the @books array
$books
A scalar not related to @books

You can declare an array like this:
my @books;
You can declare and initialize an array like this:
my @books = ("Cujo", "Carrie", "Christine");
Look carefully at the preceding. Contrary to every instinct of an experienced computer programmer, the elements are inside parentheses, not square brackets. As if that's not confusing enough, when directly initializing a reference to an array, square brackets are used for that purpose. References are discussed later in this article.

The easiest way to iterate through an array is to use the foreach construct. Here's a program that does that:

#!/usr/bin/perl -w
use strict;
my @books = ("Cujo", "Carrie", "Christine");
foreach my $book (@books)
{
print $book, "\n";
}

In the preceding, note that $book is a scalar that, for each iteration, is assigned the next element of @books.

You can also do it in a more C-like way:

#!/usr/bin/perl -w
use strict;
my @books = ("Cujo", "Carrie", "Christine");
my $subscript = 0;
print "Subscript of last element is ", $#books, "\n";
while($subscript <= $#books)
{
print $subscript, ": ", $books[$subscript], "\n";
$subscript++;
}

In the preceding note that $#books is a scalar that takes the value of the subscript of the last element in the @books array.

NOTE

Do not confuse length() with $#arrayname. The former returns the length of a string, and is meaningless when applied to an array. The latter returns the subscript of the last element of an array, and is meaningless when applied to a string.

Unlike C strings, Perl strings are not an array of characters, so there's no relationship between arrays and strings. There are, however, functions to convert between strings and arrays.

join and split convert between strings and arrays

Let's say you want to convert between strings and arrays. You can use the join() function to convert an array to a string, and the split() function to convert a string to an array. See the following:

#!/usr/bin/perl -w
use strict;
my @books = ("Cujo", "Carrie", "Christine");
my $saying = "To be or not to be, that is the question";
my $bookstring = join(", ", @books);
print $bookstring, "\n";
my @sayingarray = split(/ /, $saying);
foreach my $element (@sayingarray)
{
print $element, "\n";
}

The preceding program converts array @books into scalar string $bookstring, and converts string $saying into array @sayingstring, and then prints them both. The following is the output of the program:

[slitt@mydesk slitt]$ ./test.pl
Cujo, Carrie, Christine
To
be
or
not
to
be,
that
is
the
question
[slitt@mydesk slitt]$

Accessing Array Elements

The simplest way to access an element of an array is through its subscript. For instance:

#!/usr/bin/perl -w
use strict;
my @books = ("Cujo", "Carrie", "Christine");
print $books[1], "\n"

Element 1, which is the second element, is "Carrie", as shown in the following output:


[slitt@mydesk slitt]$ ./test.pl
Carrie
[slitt@mydesk slitt]$

To add or change an element, just assign to it:

#!/usr/bin/perl -w
use strict;
my @books = ("Cujo", "Carrie", "Christine");
$books[10] = "Firestarter";
$books[2] = "The Stand";
my $ss = 0;
foreach my $book (@books)
{
print $ss, ": ", $book, "\n" if defined($book);
$ss++;
}
print "The last element has subscript ", $#books, "\n";

Note that we assigned a value to $book[10], leaving elements 3 through 9 undefined. The output is interesting:

[slitt@mydesk slitt]$ ./test.pl
0: Cujo
1: Carrie
2: The Stand
10: Firestarter
The last element has subscript 10
[slitt@mydesk slitt]$

Stacks and FIFOs

Four functions, push(), pop(), shift() and unshift() enable you to easily implement various stacks, fifo's, and other constructs. Here are some brief descriptions:

Function
Action graphic
Description
push @DESTARRAY, @SRCARRAY

0
1
2
3
4
MEM
   v
*
*
*
*
*
   v

Places the elements of the @SRCARRAY at  @DESTARRAY's end ( starting immediately after the current final used subscript)
pop @ARRAY
0
1
2
3
4
MEM
  
*
*
*
*
*
   ^
_/
Returns and removes the array's highest subscripted element.
shift @ARRAY
MEM
0
1
2
3
4
 ^
  \_

<-

<-

<-

<-

<-
Returns and removes the array's element 0, and then shifts all other elements down one.
unshift @DESTARRAY, @SRCARRAY
MEM
0
1
2
3
4
 `->
->
->
->
->
->
Shifts all elements in @DESTARRAY up (higher subscript) by the number of elements in @SRCARRAY, and then copies the elements of @SRCARRAY to the (now) unused lower elements of @DESTARRAY.

push() and pop() implement a stack, while push() and shift() implement a FIFO (First in first out). The shift() function can grab function arguments off the function argument array, one member at a time, starting with arg 0 ($_[0]).

Splice

The splice()function is beyond the scope of this tutorial, but it can replace sections of an array. If you need this functionality, look up splice() in the perlfunc man page.

Congratulations

Congratulations! You have now created arrays, looped through them with foreach, joined them into a string with join(), created them from a string with split(), accessed array elements by subscript, and manipulated arrays with  functions, push(), pop(), shift() and unshift().

File I/O

There are several ways to access files. You can use the oldstyle capital letter barewords, a scalar variable, a filehandle using the FileHandle module, or a file glob. For simplicity and versitility, this article discusses only the scalar variable method.

File Output

The following program writes an array to a file:

#!/usr/bin/perl -w
use strict;
my $outputfile;
open($outputfile, ">outfile.txt") || die "Could not open for output\n";
my @books = ("Cujo", "Carrie", "Christine");
foreach my $book (@books)
{
print $outputfile $book, "\n";
}
close($outputfile);

Notice that there is NOT a comma between the file variable and the string to print. This is vitally important, as you'll get an unfathomable error if you try to place a comma there. By using a scalar as the file variable, you can pass the file into and out of functions and subroutines to your heart's content.

File Input

There are two styles of file input:
  1. Reading a line at a time in a loop
  2. Immediately reading the entire file into an array
Assume that you have a file called infile.txt whose three lines are "Cujo", "Carrie", and "Christine". The following program reads each line of the file and prints it, much like any other programming language:

#!/usr/bin/perl -w
use strict;
my $inputfile;
open($inputfile, "<infile.txt") || die "Could not open for input\n";
while(<$inputfile>)
{
my $line = $_;
chomp($line);
print $line, "\n";
}
close($inputfile);

Notice the chomp() function. That function removes the trailing newline from the line, so that you can process the line as a string.

Compare the preceding to the following program, which opens the file, quickly stuffs it into an array, and then uses that array to print the lines:

#!/usr/bin/perl -w
use strict;
my $inputfile;
open($inputfile, "<infile.txt") || die "Could not open for input\n";
my @lines = <$inputfile>;
close($inputfile);
foreach my $line (@lines)
{
chomp($line);
print $line, "\n";
}

The line at a time method has the advantage of less memory usage. Using a line at a time, you could efficiently read a gigabyte file. Trying to place the contents of the gigabyte file into an array would bring most computers to their knees.

The whole file technique has the advantage of being more of a snapshot. Notice that you can close the file immediately after access, rather than waiting for the process to complete. This is helpful if the process is time consuming, and other programs are waiting to access the file. Also, with the file in an array, you can reprocess it without rereading it, and you can access lines at random.

Congratulations

Congratulations! You've written and read files. If you need to use the other methods, get a good Perl book and use them.

Hashes

In Perl, a hash is a bunch of key->value pairs. Lookup is by key. A hash can easily represent an entity, or a single database row (record). For instance:


#!/usr/bin/perl -w
use strict;
my %person = (
'fname'=>'John',
'lname'=>'Smith',
'job'=>'Perl Developer'
);
print "First name: ", $person{'fname'}, "\n";
print "Last name: ", $person{'lname'}, "\n";
print "Job: ", $person{'job'}, "\n";

The preceding code outputs the following:

[slitt@mydesk slitt]$ ./test.pl
First name: John
Last name: Smith
Job: Perl Developer
[slitt@mydesk slitt]$

Notice the following about the preceding program and output:
There might be occasions when you do not know the keys. This could happen, for instance, if you parse an XML file into a hash. In such cases, you could use the keys() function to derive all the keys, and then iterate through the keys:


#!/usr/bin/perl -w
use strict;
my %person = (
'fname'=>'Zev',
'lname'=>'Smith',
'job'=>'Perl Developer'
);
my @keys = keys %person;
foreach my $key (@keys)
{
print $key, ": ", $person{$key}, "\n";
}

The preceding code outputs the following:

[slitt@mydesk slitt]$ ./test.pl
lname: Smith
fname: Zev
job: Perl Developer
[slitt@mydesk slitt]$

Notice the fields are neither in the order entered, nor in alpha order. They are not ordered. A hash is not ordered. If you want it ordered, you need to sort it by keys or by values. To sort it by keys, use the sort() function:
my @keys = sort keys %person;
To sort it in reverse order, use the reverse() function or
my @keys = reverse sort keys %person;
Another way to get the reverse sort is with the block syntax of sort():
my @keys = sort {$b cmp $a} keys %person;
Speaking of the block syntax of sort, it's a necessity if you want to sort by values instead of keys:
my @keys = sort {$person{$a} cmp $person{$b}} keys %person;
In the preceding line, $a represents element ss in the array, while $b represents element ss+1. Everything in the block is considered the sort's comparison subroutine, be called back for each pair of array elements. In this case, instead of comparing the elements, we compare the hash values associated with the elements. Pretty cool, huh?

Adding and Deleting Hash Elements

In the following program, we start with a hash whose keys are alpha representations 'one', 'two' and 'three', with corresponding values 1, 2 and 3. We then add an element 'four'=>4, and use the delete() function to delete the element whose key is 'two'. The result prints out 1, 3 and 4 -- just what you'd expect:

#!/usr/bin/perl -w
use strict;
my %numbers = (
'one'=>1,
'two'=>2,
'three'=>3
);

$numbers{'four'} = 4;
delete($numbers{'two'});

foreach my $key (sort {$numbers{$a} <=> $numbers{$b}} keys %numbers)
{
print $key, ": ", $numbers{$key}, "\n";
}


[slitt@mydesk slitt]$ ./test.pl
one: 1
three: 3
four: 4
[slitt@mydesk slitt]$

Detecting Existence of Hash Elements

To see whether a key exists, use the following syntax:
if(exists($hashname{$keystring}) {do_something();}
To see whether an existing (or nonexistent) key's value is defined, use this:
if(defined($hashname{$keystring}) {do_something();}

Constructing Complex Data With Hashes

Arrays hold scalars. References are scalars. That means that you could have an array of references to hashes. If all the referenced hashes have the same keys (column names in DBMS speak), then the array of references to hashes is exactly like a database table minus any indexes. You could create an index for a column by creating a hash whose key is the column value for each record and whose value is the array subscript.

Additionally, a hash element's value can be a reference, meaning you could have a hash of hash references, or a hash of array references. In practice, there's no end to the data constructs you can create with references, scalars, arrays and hashes.

Congratulations

Congratulations! You've created a hash, added and deleted elements, accessed elements by name, accessed all elements with the keys() function, sorted and reverse sorted by key and by value, and learned about the construction of complex data.

References

References are the coolest thing since sliced bread. They're like pointers in C, but without the danger. A reference is simply a scalar that refers to another variable -- typically an array or a hash. This is important because it gives the means for multidimensional arrays and hierarchical hashes. It also yields much more flexibility in  passing information into and out of subroutines and objects. The following chart shows how to reference and dereference different variable types, and how to instantiate them and how to instantiate references to them.

Variable Instantiating
the variable
Instantiating a
reference to it
Referencing it Dereferencing it Accessing an element
$scalar $scalar = "steve";
$ref = \"steve";
$ref = \$scalar $$ref or
${$ref}
N/A
@list @list = ("steve", "fred");
$ref = ["steve", "fred"];
$ref = \@list @{$ref} ${$ref}[3]
$ref->[3]
%hash %hash = ("name" => "steve",
   "job" => "Troubleshooter");
$hash = {"name" => "steve",
   "job" => "Troubleshooter"};
$ref = \%hash %{$ref} ${$ref}{"president"}
$ref->{"president"}
FILE

$ref = \*FILE {$ref} or scalar <$ref>

In the preceding, note the rich variety of dereferencing techniques. You can place the proper symbol in front of the brace enclosed reference variable. In the case of references to scalars you can simply place an additional dollar sign. Perhaps most useful of all, you can dereference an element of an array reference or a hash reference with the -> operator. This makes coding much easier, as you can operate directly on the reference rather than dereferencing it first -- a real time saver when you have hash elements that are themselves references to other hashes containing references. The following code shows just how handy it can be:

#!/usr/bin/perl -w
use strict;

package Me;

sub new
{
my($type) = $_[0];
my($self) = {};
$self->{'name'} = 'Bill Brown';

### Make a reference to an empty array of jobs
$self->{'jobs'} = [];

### Now make each element of array referenced by
### $self->{'jobs'} a REFERENCE to a hash!
$self->{'jobs'}->[0]={'ystart'=>'1998','yend'=>'1999','desc'=>'Bus driver'};
$self->{'jobs'}->[1]={'ystart'=>'1999','yend'=>'1999','desc'=>'Bus mechanic'};
$self->{'jobs'}->[2]={'ystart'=>'1999','yend'=>'2001','desc'=>'Software Developer'};


bless($self, $type);
return($self);
}

### showResume is coded to show off the -> operator. In real
### life you'd probably use a foreach loop, but the following
### while(1) loop better demonstrates nested -> operators.
sub showResume
{
my($self)=$_[0];
print "Resume of " . $self->{'name'} . "\n\n";
print "Start\tEnd\tDescription\n";
my $ss = 0;

# Loop through array referenced by $self->{'jobs'},
# and for each subscript, print the value corresponding
# to the hash key. In other words, print every field of
# every record of the jobs array
while (1)
{
last unless defined $self->{'jobs'}->[$ss];
print "$self->{'jobs'}->[$ss]->{'ystart'}\t";
print "$self->{'jobs'}->[$ss]->{'yend'}\t";
print "$self->{'jobs'}->[$ss]->{'desc'}\n";

$ss++;
}
}

package Main;

my $me = Me->new();
$me->showResume();
print "\nFirst job was $me->{'jobs'}->[0]->{'desc'}.\n";

In the preceding, the Me type object has an element called jobs that is a reference to an array of jobs. Each element in that array is itself a reference to a hash whose elements contain various pieces of information like start date, end date and description. Look how neatly the showResume()method is written. Now imagine the while loop in that method without benefit of the-> operator. Not a pretty picture.

I usually pass references to arrays and hashes into subroutines, and pass references to them out. After all, how often do you pass an array or hash you don't want modified?

Subroutines

Subroutines are how we split big tasks into small ones. It's how we get work done in an easy to understand manner. You declare a subroutine like this:

sub printHello()
{
### code
### return value if any

The preceding had no arguments. Here's how you declare a subroutine with arguments:


sub biggest($$)
{
$a = shift; ### arg0
$b = shift; ### arg1
### code
### return value if any
}

Here's a subroutine that returns the biggest of two numbers:

sub biggest($$)
{
$a = shift;
$b = shift;
if($a >= $b)
{
return $a;
}
else
{
return $b;
}
}

Calling the subroutine is easy:

my $wwii = 1941;
my $gulfWar = 1990;
my $bigger = biggest($wwii, $gulfwar);
### $bigger now contains 1990.

Often a subroutine's arguments are references to a hash or array. Typically you'll operate on them directly with the -> operator. If, however, for some reason you want to operate on a copy, leaving the original alone, you can dereference the reference, creating a copy:

Calling the subroutine Constructing the subroutine
%globals;
# ...
# set globals
# ...
# now print globals
&printGlobals(\%globals);

sub printGlobals
   {
   # copy of argument precludes extra-scope change
   my(%globals) = %{$_[0]};
   print "Current Dir: $globals{'currentdir'}\n";
   print "Program Dir: $globals{'programdir'}\n";
   print "Version    : $globals{'programver'}\n";
   print "Accesslevel: $globals{'accesslevel'}\n";
   }

Objects

That leaves us with objects. Perl didn't support objects natively, so when Perl became OOP they were incorporated as a sort of reference to hash. Here's an example:


#!/usr/bin/perl -w
use strict;

package Person;

sub new($$)
{
my($type) = shift; #arg0
my($self) = {};
$self->{'name'} = shift; #arg1
bless($self, $type);
return($self);
}

sub setAge($$)
{
my $self = shift; #arg0
$self->{'age'} = $_[1];
}

sub getAge($)
{
my $self = shift; #arg0
return $self->{'age'};
}

sub getName($)
{
my $self = shift; #arg0
return $self->{'name'};
}

package Main;
use Person;

my $me = Person->new('Steve Litt');
$me->setAge(16); ### Yeah, right
print $me->getName(), " is ", $me->getAge, " years old\n";

Let's analyze the preceding. The Person class's definition starts with the package Person statement, and ends with either the next package statement or with the end of the file. The class (object, whatever) is entirely defined by subroutines. Data elements of the class are created in the subroutine body by setting elements of the $selfhash reference (it's a reference to a hash). What turns the whole thing into an object or class or whatever you want to call it is the bless statement, which relates the $self hash reference to the type declared in the main routine (Person->new('SteveLitt')).

When you use the $me->setAge(16) syntax, it's the same as using $me as the first argument to Person::setAge(). In other words, it's equivalent to Person::setAge($me,16). That's always important to remember, especially when objects get tricky, like when you use callback routines.

Summary

That's it. You've learned the basics of Scalar Variables, Numeric and String Manipulation, Looping, If/elsif/else, Arrays, File I/O, Hashes, References, Subroutines, and Objects. Armed with this information and a book, you can write a fairly substantial Perl program. Go get em, tiger!

Steve Litt is the author of Rapid Learning: Secret Weapon of the Successful Technologist. He can be reached at his email address.

Life After Windows: Free Software RAD

Life After Windows is a regular Linux Productivity Magazine column, by Steve Litt, bringing you observations and tips subsequent to Troubleshooters.Com's Windows to Linux conversion.
By Steve Litt
RAD. It stands for Rapid Application Development. Most RAD is promises and snake oil, but it does exist in this world. Since the early 1990's, you can buy the Clarion application development environment, and create a 5 table app in a day. In a week you can work miracles. I've never seen such a productive computer language, before or since. Not Python, not Perl, not Delphi, not Powerbuilder, nothing.

But Clarion isn't available on Linux.

For non-database apps, Python and Perl come fairly close. With their regular expressions, object orientation and loose typing, you can bang out an app fast. But not as fast as you might want. So I've been looking for faster coding methodologies using Free Software on Linux.

One huge success is my Node.pm file. It's basically a knockoff of DOM (Document Object Model) specially adapted to outlines, but capable in any hierarchical situation. You instantiate a parser object to parse an outline into a node tree, then you code two callback routines -- one to execute on first entry to a node, and the other (optional) to execute on final exit from the node. Then you instantiate a Walker object, with the top node on the tree and the callback routines as arguments, and that Walker object does all the work. You can have several successive walker objects using several different callback routines to accomplish almost any transformation on the hierarchy.

Several years ago I constructed an EMDL to UMENU parser. EMDL stands for Easy Menu Definition Language, and it's implemented as an outline. That makes menu authoring fast for the touch typist. But UMENU is not hierarchical -- each menu has its own config file, so that UMENU systems are constructed level by level, not recursively. Think of the hassles of converting a recursive outline to a level oriented series of files. I created the EMDL to UMENU parser, but it was almost unmaintainable, ununderstandable, not able to be modifed to output other menu types, and it took about 15 seconds to process a 200 item menu system.

Then I made a new EMDL to UMENU parser using Node.pm. It took a couple days, and the logic is crystal clear. Implementing outputs for Icewm menus or web menus or almost any other type of menu is fairly easy. The new parser runs in 2 seconds instead of 15. On any job requiring manipulation of hierarchies, Node.pm can boost productivity by a factor of 10.

Node.pm isn't the only RAD technique on the block.

Noel Henson, the maintainer of the VimOutliner project, uses the /rdb 4gl. /rdb is basically a series of UNIX filters that manipulate data. Data is piped through selects and joins and edit screens. The beauty is that each of these small executables is ultimately modular, with their input defined by stdin and their output going to stdout. At either of those places you can "pick off" the data for troubleshooting. Noel says using /rdb he can whip out a 5 table app in a day.

In an Infoworld article called "Test before you leap into development", author  Jon Udell describes a methodology where the tests are created first, and serve as a definition for the desired application. One of the things Udell stressed was testing of individual modules, which implies a need for modularity. I thought of Noel Henson's statement that building a /rdb app was like putting together Legos (R), and decided to try coding an app like that. My first attempt was very simple and got done in a day. My second was tougher and more thought provoking...

I have a program to keep track of the state of various peoples' domain names via whois information. Unfortunately, whois information is not given out in data format, but instead in a rather freeform text that varies with top level domain and registrar. My old domain tracking program worked well enough when everyone used network solutions and had only .com domains, but as choices in registrars and top level domains increased, more parsers were needed.

I decided to rewrite the domain tracking program using the piped small executable philosophy. I had it somewhat working in a day, and almost completely working in 2. Here is the design:

First design of domain program

In the preceding, all but the cat program were written in Perl. The splitter took the file full of whois records, split them into single records, and using Perl's pipes (open($pipe,'|'.$command)) piped each data set out to the piped pair of the registrar, match and toplevel parser, and the parser router. The parser router, based on the toplevel domain and the registrar, assembled the proper pipeline of parsers to parse the data, then piped to that pipeline via a Perl pipe.

This design and coding method enabled me to assemble and test a unit at a time, with a high degree of confidence in each component. Components were tested repeatedly with proper input files until confidence was achieved. The last step was to use my UMENU program to assemble these components into a user friendly app, so that the user would not need to construct pipelined commands on the command line. The UMENU assembly took a remarkable 15 minutes. Armed with the UMENU app as documentation, I then added a web interface (by adding a single executable that converts straight text to a web page and then constructing a web page with links too call the various pipelines).

So in about 2 days I had a complete app that correctly parsed .net, .com and .biz from two different registrars. There was only one problem...

This app took 19 seconds to run, as opposed to the 1.5 seconds its predecessor took. I don't mind paying for quick development in runtime efficiency, but a tenfold speed decrease is a little excessive. Some bottleneck analysis revealed that the problem was executable startup and data passage throughout the system -- there was no one bottleneck so there was no magic bullet. The only way to speed it up was to condense several executables into one. Specifically, I placed the registrar, match and toplevel parser, as well as the parser router, within the domain splitter. This was pretty much a matter of copying the code into the .pm file that housed system wide subroutines, and then calling those from the domain splitter. Additionally, I had to debug through constant regression tests.

Second, I condensed the registrar and registrant parsers into one. This  eliminated some reusable code because one registrar parser worked for both registrars. Once again, debugging and regression testing got the app performing exactly as the completely modular one had. The new design ran in 9.5 seconds and looked something like this:
Second design diagram

Piped executables is definitely a route to a high quality 1 or 2 day app, and serves as a prototype for faster apps, as well as a baseline for regression testing when developing the higher performance version. Whether runtime speed and scalability issues can be adequately addressed by this design and coding methodology is something I'll need to research more.
Steve Litt is the author of the course on the Universal Troubleshooting Process.  He can be reached at his email address.

Letters to the Editor

All letters become the property of the publisher (Steve Litt), and may be edited for clarity or brevity. We especially welcome additions, clarifications, corrections or flames from vendors whose products have been reviewed in this magazine. We reserve the right to not publish letters we deem in bad taste (bad language, obscenity, hate, lewd, violence, etc.).
Submit letters to the editor to Steve Litt's email address, and be sure the subject reads "Letter to the Editor". We regret that we cannot return your letter, so please make a copy of it for future reference.

How to Submit an Article

We anticipate two to five articles per issue, with issues coming out monthly. We look for articles that pertain to the Linux or Open Source. This can be done as an essay, with humor, with a case study, or some other literary device. A Troubleshooting poem would be nice. Submissions may mention a specific product, but must be useful without the purchase of that product. Content must greatly overpower advertising. Submissions should be between 250 and 2000 words long.

Any article submitted to Linux Productivity Magazine must be licensed with the Open Publication License, which you can view at http://opencontent.org/openpub/. At your option you may elect the option to prohibit substantive modifications. However, in order to publish your article in Linux Productivity Magazine, you must decline the option to prohibit commercial use, because Linux Productivity Magazine is a commercial publication.

Obviously, you must be the copyright holder and must be legally able to so license the article. We do not currently pay for articles.

Troubleshooters.Com reserves the right to edit any submission for clarity or brevity, within the scope of the Open Publication License. If you elect to prohibit substantive modifications, we may elect to place editors notes outside of your material, or reject the submission, or send it back for modification. Any published article will include a two sentence description of the author, a hypertext link to his or her email, and a phone number if desired. Upon request, we will include a hypertext link, at the end of the magazine issue, to the author's website, providing that website meets the Troubleshooters.Com criteria for links and that the author's website first links to Troubleshooters.Com. Authors: please understand we can't place hyperlinks inside articles. If we did, only the first article would be read, and we can't place every article first.

Submissions should be emailed to Steve Litt's email address, with subject line Article Submission. The first paragraph of your message should read as follows (unless other arrangements are previously made in writing):

Copyright (c) 2003 by <your name>. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, version  Draft v1.0, 8 June 1999 (Available at http://www.troubleshooters.com/openpub04.txt/ (wordwrapped for readability at http://www.troubleshooters.com/openpub04_wrapped.txt). The latest version is presently available at  http://www.opencontent.org/openpub/).

Open Publication License Option A [ is | is not] elected, so this document [may | may not] be modified. Option B is not elected, so this material may be published for commercial purposes.

After that paragraph, write the title, text of the article, and a two sentence description of the author.

Why not Draft v1.0, 8 June 1999 OR LATER

The Open Publication License recommends using the word "or later" to describe the version of the license. That is unacceptable for Troubleshooting Professional Magazine because we do not know the provisions of that newer version, so it makes no sense to commit to it. We all hope later versions will be better, but there's always a chance that leadership will change. We cannot take the chance that the disclaimer of warranty will be dropped in a later version. 

Trademarks

All trademarks are the property of their respective owners. Troubleshooters.Com(R) is a registered trademark of Steve Litt.

URLs Mentioned in this Issue

_