Just another Perl Hacker by Jonathan Hudson jrhudson@bigfoot.com

There's more than one way to do it ...

I've always missed having a powerful and portable scripting language on QDOS. S*BASIC of course has its adherents, but if, like me, you don't use it very often, it's easy to forget the syntax, which I find awkward and unnecessarily verbose and, after perl, its not a particularly powerful language.

I'd considered porting perl to QDOS for some time, but as ever, there were more pressing things to do, and besides, it would probably be difficult. As it happened, porting perl wasn't too difficult, initially taking about three hours ...however ironing out the final bugs resulting from the arcane QDOS file system then took a few days longer; the problems having been initially masked by the more flexible and forgiving uqlx file-system. A bug report some weeks after the first release and some testing on 'real' hardware by Thierry Godefroy (thanks Thierry) resulted in a quick solution once the problem had been identified.

The version ported to QDOS is perl 4 (4.036), this is perhaps disappointing as perl 4 is obsolete in the real world (1992 vintage), having been superseded by perl 5, which offers a richer syntax, dynamic loading of language extensions and object orientated methods. perl 4 remains a good introductory approach for QDOS for a number of reasons:

So what?

Perl was invented, if that's the right word, by Larry Wall, then a harassed Unix administrator (and well known author of tools such as patch and rn (an internet new reader)), in the late 1980s to solve a practical problem of synchronizing files on two sites a continent apart. Since then it has grown to become the lingua franca running much of the internet and most Web servers, as well as an immensely popular language for system administration on many different operating systems and a powerful text processing language. Many of the ``X2Y'' text format conversion tools (latex2html, html2ps for example) are written in perl.

Perl 5 is available for most modern operating systems, (Unix, Plan 9, Beos, Open VMS, Windows, MSDOS, MacOS, AmigaOS, OS2, OS390, QNX) and is highly portable. The QDOS perl 4 version offers a comparable feature set to other non-Unix perl 4 ports.

Here's how the author describes it, verbatim from the man page.

Perl is a language optimized for scanning arbitrary text files, extracting information from those text files, and printing reports based on that information. It's also a good language for many system management tasks. The language is intended to be practical (easy to use, efficient, complete) rather than beautiful (tiny, elegant, minimal).

Perl combines (in the author's opinion, anyway) some of the best features of C, sed, awk, and sh, so people familiar with those languages should have little difficulty with it. (Language historians will also note some vestiges of csh, Pascal, and even BASIC-PLUS.) Expression syntax corresponds quite closely to C expression syntax. Unlike most Unix utilities, Perl does not arbitrarily limit the size of your data-if you've got the memory, Perl can slurp in your whole file as a single string. Recursion is of unlimited depth. And the tables used by hashes (previously called "associative arrays") grow as necessary to prevent degraded performance. Perl uses sophisticated pattern matching techniques to scan large amounts of data very quickly. Although optimized for scanning text, Perl can also deal with binary data, and can make dbm files look like hashes.

If you have a problem that would ordinarily use sed or awk or sh, but it exceeds their capabilities or must run a little faster, and you don't want to write the silly thing in C, then Perl may be for you.

I find that people tend to either love or hate perl. The ``hater's'' hate it because the syntax is weird, there are numerous ways of expressing things, and some claim it a write only (impossible to read) language.

The ``lover's'' love it for the same reasons. The syntax is weird, but that's OK, because its meant to be weird. That there are numerous ways of doing things is great too, it just appeals to our creativity. And its dead easy to read ...as I shall demonstrate, at least to my satisfaction.

An example

Let's say you wanted a program that would change the last underscore in any file name in the current directory into a dot (maybe you wanted to use thq QDOS lynx program), for example:

From To
test_html test.html
pretty_picture_gif pretty_picture.gif
index_html index.html
AnotherFile AnotherFile
Simple little problem, for any arbitrary directory listing? How many lines of BASIC? Ten, twenty, perhaps? Subject of the next Quanta competition maybe? Well in perl, it's just one line.
while (<*>) {rename($_,$new) if ($new = $_) =~ s/(.*)_(.*)/$1\.$2/;}
And if you wanted to be able to pass in the directory as a parameter, it would be two:
chdir $dir if ($dir = shift);
while (<*>) { rename($_,$new) if ($new = $_) =~ s/(.*)_(.*)/$1\.$2/;}
which, assuming you'd saved the file as qlmv you'd run as
perl qlmv ram2_
if you use Adrian Ives 'sh' shell program, or from QDOS.
ex perl;'qlmv ram2_'
And I did say the syntax was a little weird, didn't I? Well it's no more obscure than I find S*BASIC (but then I use perl everyday, and S*BASIC almost never). The two line version does:
If a parameter has been passed it, shift puts it in the variable $dir, and we then change directory chdir to that directory. No parameter and we do nothing.
The while (<*>) does a wild card scan of the current directory, and in the loop body, each file found is held in the default variable $_ (because I was too lazy to tell it to use any other variable). This variable is copied into $new, and the substitution operator s/// is used to change the last '_' to a '.'. If the substitution is successful, then the rename is performed.
As you might have gathered, I've been gratuitously awkward in constructing that simple example. The solution presented is the somewhat cryptic solution that an experienced perl user (jrh) might use. Just to demonstrate that there is indeed more than one way to do it, here's a perl program that does the same thing, but written the way a programmer familair with more traditional languages might do it.

if ($ARGV[0])
  chdir $ARGV[0];         # $ARGV[0] holds any parameter passed in

opendir (DIR, ".") || die "Can't open the directory";
@files = readdir DIR;     # read all file names into an array (@files)
closedir DIR;

foreach $file (@files)       # now examine each file in turn
  if(($pos = rindex($file,"_")))   # get the last underscore
    $new = $file;                  # copy the file name
    substr($new, $pos, 1) = '.';   # substitute a dot
    rename ($file, $new);          # rename the file
Even without the comments, the program is fairly self explanatory. But which is the most efficient? Well difficult to say; the first example (at least on perl4qdos) requires an external program to perform the ``globbing'' (the while(<*>) construct, but as that perlglob is a minimal 'C' program, it may be as fast the second example. The second example is perhaps inefficient in that it uses rindex/substr rather than s///. So the best solution might be.
chdir $dir if ($dir = shift);

opendir (DIR, ".") || die "Can't open the directory";
@files = readdir DIR;  # read all file names into an array (@files)
closedir DIR;

foreach (@files)
   rename($_,$new) if ($new = $_) =~ s/(.*)_(.*)/$1\.$2/;

Next Example

Perl's string handling makes many tasks easy. As another example, convert all tabs in a file into spaces, maintaining the correct spacing. Run this fine example as:

perl -pi_bak test_file
The -pi options cause (-i_bak) the original file to be backed up as test_file_bak, and the -p option to print the changed lines back to the original file name. So here's the massive detab program:
 1 while s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
To avoid having to remember to say -pi_bak each time, perl 5 would let us add a comment to the perl program to tell perl to do that anyway; alas for perl4 we have to remember do give it as part of the command
#!/usr/bin/perl -pi_bak
 1 while s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
Again, a prize for anyone who does it more succinctly in S*BASIC.

We can take this a step further, imagine that you had a directory containing hundreds of articles for 'QL Today', and each of those articles contained hundreds of references to 'QL Today', only, you'd made a horrible mistake and consistently mis-spelt it as 'QL Toady' (pax Dilwyn, the old gags are still the best). perl to the rescue, a simple one-liner on the command line will sort it all out.

ex perl;'-pi -e "s/Toady/Today/g;" *_txt';
The -e option allows us to run a perl operation from the command line on an arbitrary list of files. Somewhat easier than changing it all manually.

But there's more

I hear you groan. What is this? The perl programmer's revenge for tedious pages of S*BASIC in Quanta? Well, just two more examples to go, and then some poetry.

perl is not just about simple but incredibly powerful single line programs; it can handle files and binary data equally well. Another practical example. The InfoZip program unzip is usually distributed as a self extracting archive. This is a program you can run, and it will decompress the archive and you then have a working unzip program. Otherwise you would require unzip to decompress the archive ...well you get the point (catch 22).

This fine scheme fails for QDOS, because you can't distribute executable files (without using zip or similar archiver, and hence needing an unzip) over electronic links and still preserve the vital data space that executable (EX) QDOS programs require. The self extracting archive for QDOS saves its data space at the end of its file, in the last eight bytes, in the same format used by the Linux hosted xtc68 cross-compiler.

Offset from EOF Data
-8 "XTcc"
-4 data space
i.e. a flag of four bytes "XTcc" followed by the 32 bit binary value. Of course, QDOS and EX knows nothing about this, so here's a perl program that sorts it out.
#!/usr/bin/perl -w

sub setqhead
  local($f) = shift;                    # get the parameter
  local($magic,$dspc,$res);             # local variables
  $res = -1;                            # preset result as 'failed'

  open(F,$f) || die "Can't open your file $f ".$!; # open the file
  seek(F,-8,2);                                    # and seek to end-8
  read(F,$buf,8);                                  # read the data
  ($magic,$dspc) = unpack("a4N",$buf); # unpack binary data to variables
  if($magic eq "XTcc")
    $hdr = pack("NccNN",tell(F),0,1,$dspc,0);     # pack binary strucure
    $res = syscall(3,0x46,0,0,-1,fileno(F),$hdr); # call the QDOS trap
  }                                   # to set executable and data space
    die "Bad magic\n";
  close F;
  $res;                                 # return (0 == success)
$fn = shift || "ram2_unz532xQ.exe"; # get users file name (with default)
system ($fn) unless &setqhead($fn); # run the file is setqhead succeeds
The interesting bits are the $hdr = pack ...line, where the binary structure used by the trap #3, FS.HEADS (set file header) is filled with the required values, and the next line, where standard perl syscall() (system call) is mapped onto the QDOS trap architecture.
$d0 = syscall($trapno, $d0, $d1, $d2, $d3, $a0, $a1, $a2, $a3);
An extension module qdos.pl is provided, allowing constructs like:
require "qdos.pl";
$res = &sd_bordr(STDIN,-1,4,2);
&sd_clear(STDIN, -1);
$res = &sd_elipse (STDIN,-1, (0, 0, 0.5, 40, 1));
which again might be intuitive to programmers with some QDOS experience.

The last line system ($fn) unless &setqhead($fn); is worthy of comment. unless is just a negated if, so the last line might be written using any of the following:

if(&setqhead($fn) == 0)
!&setqhead($fn) && system($fn);
system($fn) if (!&setqhead($fn));
&setqhead($fn) || system($fn);
usually, there's more than one way to do it (TMTOWTDI). Have I already mentioned that?

perl is pretty good at TCP/IP (Internet) network stuff as well, though unless you're fortunate enough to use uqlx as your QDOS system, this may only be of academic interest. If your uqlx/QDOS system happened to share a network with a Windows machine, which had a version of Windows prior to NT 4.0/SP3, then the following snippet would cause the Windows machine to BSOD (crash). Only you wouldn't want do that, would you?

#!/usr/contrib/bin/perl -w

# you'll need to get these values from your system header files
# these are for Linux or uqlx/Linux.
$AF_INET = 2;
$MSG_OOB = 1;

$port = 139;

$target = shift;

($name, $aliases, $proto) = getprotobyname('tcp');
($name, $aliases, $type, $len, $thataddr) = gethostbyname($target);
$that = pack('S n a4 x8', $AF_INET, $port, $thataddr);

socket(S, $AF_INET, $SOCK_STREAM, $proto) || die "socket: $!";
connect(S, $that) || die "connect: $!";
send(S, "Die,msdog", $MSG_OOB) || warn "send $!";
close S;
And here's a slightly more powerful version of the same program nice OO perl 5.
#!/usr/bin/perl -w
use IO::Socket;
use IO::Handle;

while(my $host = shift)
 my $s = new IO::Socket::INET(PeerAddr => $host, PeerPort => 139,
               Proto => 'tcp', Type => SOCK_STREAM) or die "Connect $!";
 $s->send("WinULoose", MSG_OOB);


A number of people, perhaps with too much spare time, have taken advantage of perl's rich and expressive syntax to write perl poetry. This is a perl program this is syntactically correct, but usually illogical nonsense. The best known perl poet is one Sharon Hopkins, whose work has been published in the Guardian and Economist (indeed it was reading one of Ms Hopkins poems in the Economist whilst on an otherwise dreary intercontinental flight that first got me interested in perl (shallow or what?)). Anyway, here's an example:
#!/usr/bin/perl -c


listen (please, please);

    open yourself, wide;
        join (you, me),
    connect (us, together),

tell me.

do something if distressed;

    @dawn, dance;
    @evening, sing;

    read (books, $poems, stories) until peaceful;
    study if able;

    write me if-you-please;

sort your feelings, reset goals, seek (friends, family, anyone);

     do*not*die (like this)
     if sin abounds;

keys (hidden), open (locks, doors), tell secrets;

     do not, I-beg-you, close them, yet.

                        accept (yourself, changes),
                        bind (grief, despair);

     require truth, goodness if-you-will, each moment;

select (always), length(of-days);

# listen (a perl poem)
# Sharon Hopkins
# rev. June 19, 1995
Yes, its legal perl (or at least it compiles).

Finding out more

The perl 4 port for qdos perl4qdos is available from my web site perl4qdos.zip , and no doubt from other QDOS sources of free software. You need a version post 14th November 1998, 18:00 GMT to be (largely) bug free. The archive contains the binary, documentation and examples. The documentation does not include any introductory perl programming material, though given the current popularity of perl, finding books at any large library or book store should not be a problem. I'd recommend a couple of books from O'Reilly Associates.
The Camel Book, officially known as Programming Perl, by Larry Wall et al, is the definitive reference work covering nearly all of Perl. It assumes you know how to program in perl. The 1st Edition covers perl 4, the 2nd, perl 5.
The Llama Book, officially known as Learning Perl by Randal Schwartz and Tom Christiansen with Foreword by Larry Wall is a good basic book for neophyte perl programmers. Again The 1st Edition covers perl 4, the 2nd, perl 5.
You can find out more from http://www.oreilly.com/catalog/ .There is a wealth of perl information on the internet, hardly surprising as the internet is kept doing by perl programs. Try http://www.perl.com/ (which, despite the domain is not for profit), or CPAN, the Comprehensive Perl Archive Network. Follow the links from http://www.perl.com/ to your nearest CPAN mirror.

While the perl 4 documentation is at best adequate, the perl 5 documentation is excellent and I would recommend at least downloading the (perl5) FAQs, as much is relevant to perl 4 as well.

Any comment about perl4qdos should be directed to the author jrhudson@bigfoot.com , or general discussion can be posted to Usenet maus.computer.ql.intl or the FIDONET equivalent for BBS users. Please post any questions to m.c.q.i, so everyone can learn from the answers.

perl -e '$_="wHFG NABGURE cREY UNPXRE,\n";y/a-zA-Z/N-ZA-Mn-za-m/;print;'

About this document ...

This document was generated using the LaTeX2HTML translator Version 98.1p1 release (March 2nd, 1998)

Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds.

The command line arguments were:
latex2html -no_subdir -split 0 -local_icons perl.tex.

The translation was initiated by Jonathan Hudson on 1998-11-16

Jonathan Hudson