Äîêóìåíòàöèÿ
HTML CSS PHP PERL äðóãîå

First Lesson in Portability

 
Previous Table of Contents Next

First Lesson in Portability

Portability: It's one of the things that Perl is good at. Whether your Perl code is run on a VMS machine, Unix, a Macintosh, or under MS-DOS, there's a very high probability that the Perl code you write will work seamlessly under any architecture that Perl supports. When you need to interact with the underlying operating system, such as when you're doing file I/O, Perl tries to hide all the nitty-gritty details for you so that your code will just work.

By the Way

Some of the reasons that Perl is so portable are discussed at great length in Hour 16, "The Perl Community."


Sometimes, though, there's a limit to what Perl can hide from you.

Throughout this hour, the examples have read "do this for Windows and MS-DOS, do this for Unix," and either one has worked, depending on the architecture you're using. Trying to accommodate both Windows and Unix with your own programs means that you'll have to create two versions of each program—one for Windows and one for Unix. Developing two versions creates further problems when your program is successful and moved to an even more alien architecture like Mac OS 9.

It's not at all uncommon for a program to be written for one architecture—such as Windows NT—to find itself being run under a different architecture—such as Unix. Because Perl runs on so many different architectures, many people assume that running a Perl program under Windows NT is the same as running it under Unix. Web servers and other applications move frequently between architectures; it's just good business sense to keep your software portable.

Creating a different version of your program for each architecture that would work under every possible situation is time consuming, wasteful, and unproductive. By following a few rules, you can create programs that will work everywhere, or at least try to work everywhere, and that are easy to fix if they do not.

The following are some general rules for writing "go anywhere" code:

  1. Always have warnings turned on, and use the use strict directive. This way, you can make sure that your code will probably run with various versions of Perl and that there aren't any obvious bugs.

  2. Always check the return value from system requests—for example, use open || die, never just open. Checking the value will help you find errors when moving your application from one server to another—not just between architectures.

  3. Print good, descriptive error messages.

  4. Use Perl's built-in functions to perform tasks you might otherwise do by using system or backticks (` `).

  5. Wrap system-dependent operations (file I/O, terminal I/O, process control, and so on) in subroutines, and check to make sure those operations are supported on the current operating system.

The first two points you should already be familiar with. All throughout this book, the examples have checked the exit status of critical functions, and since Hour 8, "Functions," all larger examples have demonstrated use strict and warnings.

Point 3, having good error messages, cannot be overlooked. Of the following messages, which is the most helpful?


(no message, or wrong output)

Died at line 15.

Cannot open Foofile.txt: No such file or directory

Cannot open Foofile.txt: No such file or directory at myscript.pl line 24


Obviously, the last one is the most helpful. After you've installed the program, and a problem arises months (or years) later, the last message indicates what program failed ("myscript.pl"), what it wanted ("Foofile.txt"), why it failed ("No such file..."), and where it failed ("line 24"). This information will help you fix the problems quickly. A little bit of time spent writing a good, descriptive error message always pays off.

Point 4 simply means that you should use Perl whenever possible. To retrieve a directory listing, it's tempting just to use $dir=`dir`; but this will fail if the program is ever moved to a non-Windows system. A good solution would be to use <*>. A better solution would be to use the opendir/readdir/closedir functions whenever possible. These solutions will work no matter where your program is moved.

Telling the Difference: An Example

The last two points for writing "go anywhere" code—wrapping system-dependent things in subroutines and checking the architecture of the machine that the program is running on—bear a little more explanation and a demonstration.

As you sit typing your Perl programs, you should remember that one day your Perl program might be used on a machine other than the one you're currently using. You may develop the next Amazon.com Web site, and it may move from your PC to a large Windows NT server to a cluster of Sun Enterprise 10000 Unix servers. Or you may simply have personal CGI programs and change Web providers only to find that your new provider has a different kind of server. These situations happen all of the time and need consideration.

So how does your program know the difference between Windows NT and Unix? Simple. The Perl special variable $^O—that's a dollar sign, a caret, and the capital letter O—contains the architecture name that the program is running under. For example, under Windows and MS-DOS, it contains the string MSWin32. Under Unix, it contains the type of Unix you're running—linux, aix, solaris, and so on.

The following are some tasks that depend on what operating system you are running:

  • Finding out anything about the system's configuration

  • Working with the disk and directory structure

  • Using system services (such as email)

For this example, you're going to examine a piece of code to find the amount of available disk space on a system. This exercise might be useful if someone wants to upload a file to a server and you need to find out whether the file would fit first. A code snippet to find the free disk space in the current drive of a Windows system might look like the following:


# The last line of 'dir' reports something like:

#      10 dir(s)    67,502,080 bytes free

# Or on Win98, "MB" instead of "bytes"

my(@dir,$free);

@dir=`dir`;

$free=$dir[$#dir];

$free=~s/.*([\d,]+) \w+ free/$1/;

$free=~s/,//g;


The preceding snippet takes the last line of the directory listing—in @dir—and uses regular expressions to remove everything but the size—the digits and commas preceding bytes free. Finally, the commas are removed so that $free contains just the raw free disk space. This approach works well for Windows systems. For Unix—Linux, in particular—the following snippet works:


# Last lines of df -k . reports something like this:

# Filesystem         1024-blocks  Used    Available Capacity Mounted on

# /dev/hda1             938485        709863 180139        80%      /

# And the 4th field is the number of free 1024K disk blocks

# This format may be particular to Linux.

my(@dir, $free);

@dir=`df -k .`;

$free=(split(/\s+/, $dir[$#dir]))[3];

$free=$free*1024;


Notice the differences between this snippet and the previous one. The utility under Windows to find the disk space is dir; under Unix, it is df -k .. The last line of output of df -k . is split apart, and the fourth field is placed in $field. Output of df varies slightly between Unix systems; usually, the number of fields reported is different, or they're in a different order. Your Perl code can easily compensate by simply picking a different field.

So now you have two completely different routines to find free disk space. You can combine them and have the appropriate one run on each architecture, as follows:


if ( $^O eq 'MSWin32') {

    # The last line of 'dir' reports something like:

    #      10 dir(s)    67,502,080 bytes free

    my(@dir,$free);

    @dir=`dir`;

    $free=$dir[$#dir];

    $free=~s/.*([\d,]+) \w+ free/$1/;

    $free=~s/,//g;

} elsif ($^O eq 'linux' ) {

    # Last line of df -k . reports something like this:

    # /dev/hda1      938485 709863 180139   80%  /

    # And the 4th field is the number of free 1024K disk blocks

    my(@dir, $free);

    @dir=`df -k .`;

    $free=(split(/\s+/, $dir[$#dir]))[3];

    $free=$free*1024;

} else {

    warn "Cannot determine free space on this machine\n";

}


The sample program has now been expanded to include both the MS-DOS/Windows version and the Linux version. If it's run under any other kind of machine, a warning is printed.

The routine is almost finished. What you need to do now is isolate this routine in a subroutine so that the variables needed can be declared private and the final product can be cut and pasted into any program and used whenever needed.


# Computes free space in current directory

sub freespace {

    my(@dir, $free);

    if ( $^O eq 'MSWin32') {

        # The last line of 'dir' reports something like:

        #      10 dir(s)    67,502,080 bytes free

        @dir=`dir`;

        $free=$dir[$#dir];

        $free=~s/.*([\d,]+) bytes free/$1/;

        $free=~s/,//g;

    } elsif ($^O eq 'linux' ) {

        # Last line of df -k . reports something like this:

        # /dev/hda1      938485 709863 180139   80%  /

        # And the 4th field is the number of free 1024K disk blocks

        @dir=`df -k .`;

        $free=(split(/\s+/, $dir[$#dir]))[3];

        $free=$free*1024;

    } else {

        $free=0; # A default value

        warn "Cannot determine free space on this machine\n";

    }

    return $free;

}


Now whenever your programs need to find the amount of free disk space, you can simply call the freespace() subroutine, and the answer is returned. If you try running this subroutine on another architecture that's not listed, an error message is printed. However, adding another Unix-like OS to the function wouldn't be difficult; you can just add another elsif clause.

    Previous Table of Contents Next
    © 2000- NIV