home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume13 / perl / sample < prev    next >
Encoding:
Internet Message Format  |  1988-01-30  |  10.3 KB

  1. Subject:  v13i013:  Forwarded posting of perl code
  2. Newsgroups: comp.sources.unix
  3. Sender: sources
  4. Approved: rsalz@uunet.UU.NET
  5.  
  6. Submitted-by: Larry Wall <lwall@jpl-devvax.jpl.nasa.gov>
  7. Posting-number: Volume 13, Issue 13
  8. Archive-name: perl/sample
  9.  
  10. [  This article originally appeared on comp.sources.d, and explains a bit
  11.    more about perl, as well as a pretty good piece of a sample perl
  12.    program.  I hope someone will translate the UUCP/Usenet scripts
  13.    (uucp+nuz.tulz in Volume 7) into Perl, and send them along
  14.    to be posted. --r$ ]
  15.  
  16. As to what it is, here's the hype paragraph from the manual page:
  17.  
  18.      Perl is a interpreted language optimized for scanning arbi-
  19.      trary text files, extracting information from those text
  20.      files, and printing reports based on that information.  It's
  21.      also a good language for many system management tasks.  The
  22.      language is intended to be practical (easy to use, effi-
  23.      cient, complete) rather than beautiful (tiny, elegant,
  24.      minimal).    It combines (in the author's opinion, anyway)
  25.      some of the best features of C, sed, awk, and sh, so people
  26.      familiar with those languages should have little difficulty
  27.      with it.  (Language historians will also note some vestiges
  28.      of csh, Pascal, and even BASIC-PLUS.) Expression syntax
  29.      corresponds quite closely to C expression syntax.    If you
  30.      have a problem that would ordinarily use sed or awk or sh,
  31.      but it exceeds their capabilities or must run a little fas-
  32.      ter, and you don't want to write the silly thing in C, then
  33.      perl may be for you.  There are also translators to turn
  34.      your sed and awk scripts into perl scripts.
  35.  
  36. That's all I wanted to put in the manual page, but I could tell you a little
  37. more.  First of all, why I wrote it: I wanted to set up a distributed
  38. configuration control system based on the news system, and I had to be
  39. able to print reports based on scanning a bunch of articles.  Awk and sed
  40. didn't permit me to navigate around the news system like I wanted to do
  41. (following embedded references to other articles).  The shells can navigate,
  42. but you can't do anything efficiently when you have to start up a new
  43. process every time you turn around.  I could have done it in C, of course,
  44. but text processing in C is an ungainly proposition at best.  On top of which,
  45. C didn't have the picture-style report formats I wanted.  And I didn't want
  46. to do a make every time I tweaked the program.
  47.  
  48. Somewhat later I realized that many systems programming problems deal with
  49. text--the situation arises frequently that you want to take the output of
  50. various status programs (either directly via a pipe or indirectly from a log
  51. file) and massage the data to show you just what you want to know, or pick
  52. out various bits of information to drive some other operation.  In the first
  53. category is a set of LAN-wide status reporting scripts that deliver a report
  54. to me each morning concerning anomalous conditions on any of the machines I'm
  55. responsible for.  In the second category are programs like gsh and gcp, which
  56. are just like rsh and rcp except that they work globally on some set of machines
  57. defined in a system file.  In fact, I'll show you some of those programs to
  58. give you a taste of perl:
  59.  
  60. Here's gsh:
  61. --------------------------------------------------------------------------------
  62. #!/bin/perl
  63.  
  64. $SIG{'QUIT'} = 'quit';            # install signal handler for SIGQUIT
  65.  
  66. while ($ARGV[0] =~ /^-/) {        # parse switches
  67.     $ARGV[0] =~ /^-h/ && ($showhost++,$silent++,shift,next);
  68.     $ARGV[0] =~ /^-s/ && ($silent++,shift,next);
  69.     $ARGV[0] =~ /^-d/ && ($dodist++,shift,next);
  70.     $ARGV[0] =~ /^-n/ && ($n=' -n',shift,next);
  71.     $ARGV[0] =~ /^-l/ && ($l=' -l ' . $ARGV[1],shift,shift,next);
  72.     last;
  73. }
  74.  
  75. $systype = shift;            # get name representing set of hosts
  76.  
  77. while ($ARGV[0] =~ /^-/) {        # we allow switches afterwards too
  78.     $ARGV[0] =~ /^-h/ && ($showhost++,$silent++,shift,next);
  79.     $ARGV[0] =~ /^-s/ && ($silent++,shift,next);
  80.     $ARGV[0] =~ /^-d/ && ($dodist++,shift,next);
  81.     $ARGV[0] =~ /^-n/ && ($n=' -n',shift,next);
  82.     $ARGV[0] =~ /^-l/ && ($l=' -l ' . $ARGV[1],shift,shift,next);
  83.     last;
  84. }
  85.  
  86. if ($dodist) {                # distribute input over all rshes?
  87.     `cat >/tmp/gsh$$`;            #  get input into a handy place
  88.     $dist = " </tmp/gsh$$";        #  each rsh takes input from there
  89. }
  90.  
  91. $cmd = join(' ',@ARGV);            # remaining args constitute the command
  92. $cmd =~ s/'/'"'"'/g;            # quote any embedded single quotes
  93.  
  94. open(ghosts, '/etc/ghosts') || die 'No /etc/ghosts file';
  95.                     # /etc/ghosts drives the rest
  96.  
  97. $one_of_these = ":$systype:";        # prepare to expand "macros"
  98. if ($systype =~ s/\+/[+]/g) {        # we hope to end up with list of
  99.     $one_of_these =~ s/\+/:/g;        #  colon separated attributes
  100. }
  101.  
  102. line: while (<ghosts>) {        # for each line of ghosts
  103.  
  104.     s/[ \t]*\n//;            # trim leading whitespace
  105.     if (!$_ || /^#/) {            # skip blank line or comment
  106.     next line;
  107.     }
  108.  
  109.     if (/^([a-zA-Z_0-9]+)=(.+)/) {    # a macro line?
  110.     $name = $1; $repl = $2;
  111.     $repl =~ s/\+/:/g;
  112.     $one_of_these =~ s/:$name:/:$repl:/;    # do expansion in "wanted" list
  113.     next line;
  114.     }
  115.  
  116.     # we have a normal line
  117.  
  118.     @attr = split;            # a list of attributes to match against
  119.                     #   which we put into an array
  120.     $host = $attr[0];            # the first attribute is the host name
  121.     if ($showhost) {
  122.     $showhost = "$host:\t";
  123.     }
  124.  
  125.     attr: while ($attr = pop(attr)) {            # iterate over gh array
  126.     if (index($one_of_these,":$attr:") >=0) {    # is host wanted?
  127.         unless ($silent) { print "rsh $host$l$n '$cmd'\n"; }
  128.         $SIG{'INT'} = 'DEFAULT';
  129.         if (open(pipe,"rsh $host$l$n '$cmd'$dist |")) {    # start rsh
  130.         $SIG{'INT'} = 'cont';
  131.         while (<pipe>) { print $showhost,$_; }        # show results
  132.         close(pipe);
  133.         } else {
  134.         $SIG{'INT'} = 'cont';
  135.         print "(Can't execute rsh.)\n";
  136.         }
  137.         last attr;                # don't select host twice
  138.     }
  139.     }
  140. }
  141.  
  142. unlink "/tmp/gsh$$" if $dodist;
  143.  
  144. # here are a couple of subroutines that serve as signal handlers
  145.  
  146. sub cont {
  147.     print "\rContinuing...\n";
  148. }
  149.  
  150. sub quit {
  151.     $| = 1;
  152.     print "\r";
  153.     $SIG{'INT'} = '';
  154.     kill 2, $$;
  155. }
  156. --------------------------------------------------------------------------------
  157.  
  158. Gsh (and gcp) runs off the /etc/ghosts file, which looks like this:
  159. --------------------------------------------------------------------------------
  160. # This first section gives alternate sets defined in terms of the sets given
  161. # by the second section.
  162.  
  163. all=sun+mc+vax
  164. baseline=sun+mc
  165. sun=sun2+sun3
  166. vax=750+8600
  167. passwd=devvax+chief+mc
  168.  
  169. # This second section defines the basic sets.  Each host should have a line
  170. # that specifies which sets it is a member of.  Extra sets should be separated
  171. # by white space.  (The first section isn't strictly necessary, since all sets
  172. # could be defined in the second section, but then it wouldn't be so readable.)
  173.  
  174. devvax    8600    src
  175. cdb0    sun3        sysdts
  176. cdb1    sun3        sysdts
  177. cdb2    sun3        sysdts
  178. chief    sun3    src
  179. tis0    sun3
  180. manny    sun3        sysdts
  181. moe    sun3        sysdts
  182. jack    sun3        sysdts
  183. disney    sun3
  184. huey    sun3        nd
  185. dewey    sun3        nd
  186. louie    sun3        nd
  187. bizet    sun2    src    sysdts
  188. gif0    mc    src
  189. mc0    mc
  190. dtv0    mc
  191. --------------------------------------------------------------------------------
  192.  
  193. Enough of gsh.  How about you want to remove files with find, but don't want
  194. to exec rm on every file?  I just did this today in some of my news directories.
  195.  
  196.     find . -mtime +14 -print | perl -n -e 'chop;unlink;'
  197.  
  198. I could have done the equivalent by running the find from within a perl script.
  199. Note that the open statement opens up a pipe.
  200.  
  201.     #!/bin/perl
  202.     open(goners,"find . -mtime +14 -print|");
  203.     while (<goners>) {
  204.         chop;
  205.         unlink;
  206.     }
  207.  
  208. How about transforming that into a tool that will remove anything older than
  209. a specified number of days in a specified directory?
  210.  
  211.     #!/bin/perl
  212.  
  213.     die "Usage: euthanasia directory days" unless $#ARGV == 1;
  214.  
  215.     ($dir, $days) = @ARGV;        # assign array to list of variables
  216.  
  217.     die "Can't find directory $dir" unless chdir $dir;
  218.  
  219.     open(goners,"find . -mtime +$days -print|") || die "Can't run find";
  220.     while (<goners>) {
  221.         chop;
  222.         unlink;
  223.     }
  224.  
  225. I mentioned my anomaly reporting system earlier.  Here is the script that scans
  226. a particular system for filesystems that are almost full.  Note the use of
  227. the C preprocessor to isolate Masscomp specific code.
  228. --------------------------------------------------------------------------------
  229. #!/bin/perl -P
  230.  
  231. (chdir '/usr/adm/private/memories') || die "Can't cd.";
  232. `df >newdf`;
  233. open(Df, 'olddf');
  234.  
  235. while (<Df>) {
  236.     ($fs,$kbytes,$used,$avail,$capacity,$mounted_on) = split;
  237.     next if $fs =~ /:/;
  238.     $oldused{$fs} = $used;
  239. }
  240.  
  241. open(Df, 'newdf') || die "scan_df: can't open newdf";
  242.  
  243. while (<Df>) {
  244.     ($fs,$kbytes,$used,$avail,$capacity,$mounted_on) = split;
  245.     next if $fs =~ /:/;
  246.     $oldused = $oldused{$fs};
  247.     next if ($oldused == $used && $capacity < 99);    # inactive filesystem
  248.     if ($capacity >= 90) {
  249. #if defined(mc300) || defined(mc500) || defined(mc700)
  250.     $_ = substr($_,0,13) . '        ' . substr($_,13,1000);
  251.     $kbytes /= 2;        # translate blocks to K
  252.     $used /= 2;
  253.     $oldused /= 2;
  254.     $avail /= 2;
  255. #endif
  256.     $diff = int($used - $oldused);
  257.     if ($avail < $diff * 2) {
  258.         $mounted_on .= ' *';
  259.     }
  260.     next if $diff < 50 && $mounted_on eq '/';
  261.     $fs =~ s|/dev/||;
  262.     if ($diff >= 0) {
  263.         $diff = '(+' . $diff . ')';
  264.     }
  265.     else {
  266.         $diff = '(' . $diff . ')';
  267.     }
  268.     printf "%-8s%8d%8d %-8s%8d%7s    %s\n",
  269.         $fs,$kbytes,$used,$diff,$avail,$capacity,$mounted_on;
  270.     }
  271. }
  272.  
  273. rename('newdf','olddf');
  274. -------------------------------------------------------------------------------
  275.  
  276. Well, that's enough examples for now.  In terms of speed, perl almost always
  277. beats awk and usually beats sed.  It's a superset of both awk and sed in
  278. terms of capabilities.  (That certainly makes the awk-to-perl and sed-to-perl
  279. translators work more easily--in fact, some of the features of perl are there
  280. simply to ease the translation process.  I wasn't going to add a "goto" except
  281. that the sed-to-perl translator needed one.  There's a way to make arrays
  282. have either origin 0 like C, or origin 1 like awk.  Etc.)
  283.  
  284. As for reliability, perl has been in heavy use for over a year and a half.
  285. Some of the design of perl facilitates adding new keywords without blowing
  286. existing scripts out of the water.  Furthermore, perl has a regression test
  287. suite so that I know immediately if I've destroyed a previously available
  288. capability.  So you needn't worry too much about the next version of perl
  289. blowing your old scripts out of the water.
  290.  
  291. Well, enough for now.
  292.  
  293. Larry Wall
  294. lwall@jpl-devvax.jpl.nasa.gov
  295.  
  296.