Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Cross platform compatability Part 1: Shells and Files

by jepri (Parson)
on Jan 21, 2001 at 12:39 UTC ( #53314=perlmeditation: print w/replies, xml ) Need Help??

This is a fairly difficult topic to broach, because a lot of people here are on Unix style systems and are likely to blow off the whinging windows user. This is unfortunate, because there are a lot more OSs out there than windows and Unix. You want your code to run cleanly on all of them, right? After all you're programming Perl, not Perl/Linux, right? I'm appealing to your pride here.

There isn't much you have to be aware of, but you may feel a little discomforted by having to give up some amazingly cool shell tricks. I think there are replacements for almost all of them.

Don't make the shell do work that Perl can

The favourite for most Unix coders is to use shell tricks to loop over the files in a directory, like this
while (<>) { do_stuff_to $_; } or for (@ARGV) { do_stuff_to $_; }
and insist the the user run:
perl parsedir.pl /home/me/*

The shell (bash, sh, ksh, etc.) expands the * into the list of filenames and hands it to the your program. Unfortunatly DOS/Windows doesn't. You can't rely on the shell working correctly under *nix anyway. A system manager may decide to restrict scripts to running on some primitive shell for memory or security reasons. Use

while (<*>) { do_stuff_to $_; } or better opendir DH,"."; @dir=readdir DH; closedir DH; foreach (@dir) { Do_your_thing $_; }
See here for a nice example.

Don't include specific filesystem symbols

A quick way to do temporary files is:
open (FH, ">/temp/tempfile"); print FH $temporary_data;

except that it breaks DOS machines.

DOS/Windows coders can get their revenge by using
open FH "C:\temp";

You shouldn't be using hard-coded parameters in your program anyway. It will bite you later when you realise you need the temporary file in a different place, and have to go trolling through your code to find all the places you hard-coded it. This is easily solved by placing:

my $tempdir = "/temp/"; my $tempfile = "tempfile"; and then open FH "$tempdir$tempfile";

Always finish your directory names with the directory symbol (forwards or backwards slash or whatever).

Don't use external utilities

Perl has a perfectly good regexp engine. It's probably a lot better than grep. If you're running perl, don't shell out to grep:
`ls -lR | grep myfile`;
is an abomination.

The ls -lR is a problem, because it does a directory recursion much more easily than File::Find. If you're doing a serious script though, you should be File::Find. There's even a wrapper for it now, so it's easier to use.

I hope these few examples have given you a bit of direction when it comes to making your scripts cross-platform. I'm not saying your three-line file parser should be platform independent, but if you're submitting a one-page program to the monastery it would be nice if the windows users could cut'n'paste it too.

____________________
Jeremy
I didn't believe in evil until I dated it.

Replies are listed 'Best First'.
Re: Cross platform compatability Part 1: Shells and Files
by quidity (Pilgrim) on Jan 21, 2001 at 19:21 UTC

    except that it breaks DOS machines.
    DOS/Windows coders can get their revenge by using

    open FH "C:\temp";

    That would be revenge indeed, creating a program which will run nowhere, not even on windows. On seeing this code perl turns it into:

    open FH 'C:{TAB}emp';

    Which is unlikely to be the file you wanted to open. Always, always, always, use '/' as a directory seperator, it works all over the place. In fact, your first versuion:

    open (FH, ">/temp/tempfile"); print FH $temporary_data;

    will work perfectly well, assuming 'C:\temp\tempfile' exists, but you didn't check the sucess of the open call so you'll never know.

      Always, always, always, use '/' as a directory seperator, it works all over the place.

      Unless, of course, you're on a Mac (where the directory separator is ':'), or some other operating system with a different directory separator. :)

        Unless, of course, you're on a Mac (where the directory separator is ':'), or some other operating system with a different directory separator. :)

        If you're really worried about the directory separator, do this:

        $dir_separator='/'; open BLARG, "$dir_name$dir_separator$file_name";

        I can't really explain why, but sticking directory separators in a file name makes me cringe. I guess it's because the separator is not a part of the file name.

        There, I guess I did explain why.

      Excellent - I see you spotted my deliberate mistake :) If you put single quotes around it though, it works fine:
      open FH, '>c:\temp\tempfile';

      the extra comma and greater-than sign help too.

      The main point still stands - hard coding filenames through your code is not a good idea. I don't know whether it is such a great idea to "Always, always, always, use '/' as a directory separator" or not. I feel it's fine to use whichever directory separator is appropriate for your filing system, just don't hard-code it.

      I'm not checking for errors or the like, because I was pitching the article at perl programmers who are competent, but who may not have programmed on more than one platform. As such I would be expecting them to be doing all sorts of neat stuff, but perhaps overlooking basic portability issues because they've never needed to address them before.

      ____________________
      Jeremy
      I didn't believe in evil until I dated it.

(tye)Re: Cross platform compatability Part 1: Shells and Files
by tye (Sage) on Jan 21, 2001 at 21:07 UTC

    I have to strongly disagree with your first point. You just need to fix your copy of Perl to expand wildcards. It sucks that ActiveState doesn't make this easy, but there are instructions in one of the FAQs. Actually, I just stuff a @ARGV=map{glob($_)}@ARGV if $^O=~/^MSWin/; at the front of scripts that take a list of file names.

    It would really suck if people started following your specific advice of hardcoding <*> so that scripts could never be run on a subset of files or files from more than one directory.

    See File::Spec for more advice on making scripts portable.

            - tye (but my friends call me "Tye")
Re: Cross platform compatability Part 1: Shells and Files
by merlyn (Sage) on Jan 21, 2001 at 21:58 UTC
    I hope you're looking at perldoc perlport and staying non-redundant with that if you plan to make this a series. And I'm sure the author of perlport will welcome patches so that others besides non-monks can benefit from your observations.

    -- Randal L. Schwartz, Perl hacker

Re: Cross platform compatability Part 1: Shells and Files
by deprecated (Priest) on Jan 21, 2001 at 22:04 UTC
    It irritates me, jepri, that you make a big deal of being cross platform compatible and offer alternatives for standard unix perlisms (with a few errors), and yet

    • you dont offer any way to actually detect which operating system we're running on and should be friendly enough to support
    • you dont cover any other operating system. its a little known fact that there are in fact more than two major operating systems out there. I'm curious to know what MacPerl does whether it converts file/path/ to file:path: or it actually needs colons.

    I am actually currently involved in making cross-platform code and would be interested to know more about making things more windows and mac-friendly.

    one last note:

    open (FH, ">/temp/tempfile"); print FH $temporary_data;
    I dont think I've ever worked on a *nix system that had a /temp directory. The Unix System Administration Handbook (3rd ed) doesnt mention it, either. You're looking for /tmp.

    dep.

    --
    i am not cool enough to have a signature.

      I'm curious to know what MacPerl does whether it converts file/path/ to file:path: or it actually needs colons.

      MacPerl does not convert 'file/path/' to 'file:path:'; one must actually use colons in the paths.

      Even trickier, however, is the way of specifying relative paths, by prepending the directory separator. On a Mac, ':mammal:dog' means the file dog in the directory mammal in the current working directory, whereas on Unix '/mammal/dog' means the file dog in the directory mammal in the root directory.

      On the other hand, the path 'animal:mammal:dog' on a Mac means the file dog in the directory mammal in the root directory of the volume animal. On Unix 'animal/mammal/dog' means the file 'dog' in the directory 'mammal' in the directory 'animal' in the current working directory.

      Here's a script that demonstrates this behavior in MacPerl:

      use Cwd; print cwd(), "\n"; -e 'foo' or mkdir('foo', 0777) or die "Can't mkdir 'foo': $!\n"; open(FH, '>foo/bar') or warn "Can't open 'foo/bar': $!\n"; # creates a file named 'foo/bar' in cwd open(FH, '>foo:bar') or warn "Can't open 'foo:bar': $!\n"; # fails, unless you actually have a volume named 'foo' # in that case, creates a file named 'bar' on the volume 'foo' open(FH, '>:foo:bar') or warn "Can't open ':foo:bar': $!\n"; # creates a file named 'bar' in the directory 'foo' in the cwd

      Getting cross-platform file paths is not an easy problem. That's why the File::Spec module was created. (Except that all the documentation for the module is in File::Spec::Unix. You'll have to read it locally.)

Re: Cross platform compatability Part 1: Shells and Files
by ichimunki (Priest) on Jan 21, 2001 at 20:14 UTC
    I've been under the impression that any decent port of Perl worked with / as a directory delimiter, no matter what the OS considered it to be.

    If you want to be hardcore, you could store all pathnames using File::Basename and extract a filesystem specific string for FS calls.

      Yes, you are quite correct. However my point wasn't to start up a new module war, just to point out how with a few smallish changes that are pretty much good programming practise anyway, that code can made cross-platform. I felt that saying someone should go and learn Yet Another Module would be a bit too much (does anyone else dread the task of reading through the docs for all modules related to a task, then trialling them all to see which ones work well?)

      Update: chipmunk pointed out that '/' is not a directory separator for every good port - the Mac does things differently.

      ____________________
      Jeremy
      I didn't believe in evil until I dated it.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://53314]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (6)
As of 2021-11-27 06:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?