home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #16 / NN_1992_16.iso / spool / comp / lang / perl / 4941 < prev    next >
Encoding:
Text File  |  1992-07-24  |  8.2 KB  |  254 lines

  1. Path: sparky!uunet!pmafire!news.dell.com!swrinde!cs.utexas.edu!hermes.chpc.utexas.edu!news.utdallas.edu!convex!tchrist
  2. From: tchrist@convex.COM (Tom Christiansen)
  3. Newsgroups: comp.lang.perl
  4. Subject: Re: Perl meets shell
  5. Message-ID: <1992Jul24.160750.22163@news.eng.convex.com>
  6. Date: 24 Jul 92 16:07:50 GMT
  7. References: <Brs2rD.Fz1@cs.psu.edu>
  8. Sender: usenet@news.eng.convex.com (news access account)
  9. Reply-To: tchrist@convex.COM (Tom Christiansen)
  10. Organization: CONVEX Realtime Development, Colorado Springs, CO
  11. Lines: 236
  12. Originator: tchrist@pixel.convex.com
  13. Nntp-Posting-Host: pixel.convex.com
  14. X-Disclaimer: This message was written by a user at CONVEX Computer
  15.               Corp. The opinions expressed are those of the user and
  16.               not necessarily those of CONVEX.
  17.  
  18. From the keyboard of flee@cs.psu.edu (Felix Lee):
  19. :I've been slowly collecting a pile of Perl routines that I want to use
  20. :in both shell scripts and Perl scripts.  But it's rather awkward to
  21. :arrange for this to happen.
  22. :
  23. :Presumably, in Perl, you would do something like
  24. :    require 'fqdn.pl';
  25. :    $y = &fqdn($x);
  26. :
  27. :And in shell you would do
  28. :    y=`fqdn $x`
  29. :or
  30. :    cat list | fqdn
  31. :
  32. :Supporting the shell usage requires writing a wrapper that applies the
  33. :routine to @ARGV or <STDIN>.  The awkward part is this same wrapper
  34. :has to be written for every little routine in the library.
  35. :
  36. :Instead, what I have right now are two Perl programs, "perl-apply" and
  37. :"perl-map", that can be used like so:
  38. :    y=`perl-apply fqdn $x`
  39. :    cat list | perl-map fqdn
  40. :    perl-map fqdn `cat list`
  41. :
  42. :"perl-apply proc args" locates proc, loads it, calls proc with all the
  43. :args, and prints the result.
  44. :
  45. :"perl-map proc args" locates proc, loads it, calls proc on each arg
  46. :(or on each line of STDIN), and prints each result on a separate line.
  47. :
  48. :This still isn't ideal.  The shell usage is somewhat messy.
  49. :
  50. :It would be nice if the same file could be used for both purposes.
  51. :This requires a way for the file to know whether it's being run as a
  52. :script, or loaded with "require" (or "do").
  53. :
  54. :Here's one approach.  If you put something like
  55. :    #!/usr/bin/wrapper
  56. :at the top of the file, this will get ignored.  Unfortunately, the
  57. :wrapper has to be a binary executable.
  58. :
  59. :Perhaps the wrapper could be Perl itself, with a new flag.
  60. :Something like:
  61. :    #!/usr/bin/perl -Emain
  62. :The "-Emain" means that the procedure "main" should be called after
  63. :the file is loaded, after all the immediate statements are executed,
  64. :before exiting.
  65.  
  66. -Emain is interesting: I sometimes thing perl should autocall &main(@ARGV)
  67. if defined &main.
  68.  
  69. :Does this seem reasonable?  It still seems messy.
  70.  
  71. I agree that having both perl and shell be able to access the same routine
  72. in a similar fashion.  I do think you're making this a tad more difficult
  73. than it need be.  It just so happens that I've been doing this for years.
  74.  
  75. I'm going to spell out what I've done and why very carefully, so that
  76. people disagreeing with the why part can recraft their own solution.
  77.  
  78. The trick to making this work is that you do indeed have a wrapper, but
  79. it's a perl one, not a binary executable.  This program does basically
  80. these things:
  81.  
  82.     1.    require() the appropriate file.
  83.     2.    Call foo() with the appropriate args.
  84.     3.    Print out foo()'s output appropriately.
  85.     4.  Exit appropriately.
  86.  
  87.  
  88. The only catch is to find a reasonable definition for "appropriate" in 
  89. all cases above in which it occurs.
  90.  
  91.  
  92.  
  93.         STEP 1: require() the appropriate file
  94.  
  95. First, how do you map routine() to foo.pl?  I'm going to say that
  96. routine() will by definition reside in "routine.pl".   
  97.  
  98. We want one and only one copy of the wrapper (let's call it `exechook' for
  99. now) any routine that we want to call.  Well indirect through it.  One way
  100. to do this from the shell is
  101.  
  102.     $ exechook routine args
  103.  
  104. but a more desriable one is merely
  105.  
  106.     $ routine args
  107.  
  108. So we'll have exechook consult its $0 to see whether it was called as
  109. exechook and needs to look further for whom to call, or whether to derive
  110. the name of the routine to invoke right from $0.  So we'll make
  111. exechook live in /usr/local/bin (and be exectuable) and make links:
  112.  
  113.     $ROOT=/usr/local        # or just /usr
  114.     cp routine.pl $ROOT/lib/perl/
  115.     cd $ROOT/bin; ln exechook routine
  116.  
  117. Now we'll *call* "routine", but get exechook, who'll load routine.pl for us.
  118.  
  119. That makes the initial exechook code looks like this:
  120.  
  121.     $ME = 'exechook';
  122.     if ($0 =~ m,/?$ME$,) {
  123.     $routine = shift;
  124.     die "usage: [$ME] file [args ...]\n" unless @ARGV;
  125.     } else {
  126.     ($routine = $0) =~ s,.*/,,;
  127.     } 
  128.     require "$routine.pl";
  129.  
  130.  
  131.  
  132.         STEP 2: Call foo() with the appropriate args.
  133.  
  134. Felix has proposed that if the program has no arguments, that
  135. it should call the function repeatedly for each line of STDIN.
  136. That means that
  137.  
  138.     var=`fqdn`
  139.  
  140. shold be the same as
  141.  
  142.     var=`fqdn \`cat\`` 
  143.  
  144. What I don't like about this is that it makes no allowance
  145. for routines that get no arguments by their very nature.  
  146. For example, if you didn't have a whoami on your system, 
  147. and wanted one that worked this way:
  148.  
  149.     sub whoami { (getpwuid($>))[0]; } 
  150.  
  151. And made an exec hook for it, you would be very surprised to have 
  152.  
  153.     myname=`whoami`
  154.  
  155. Sit and wait for input.  So let's not do that.  We'll give it @ARGV and
  156. let it do what it wants.  People wanting `cat` behaviour can code it
  157. themselves, or use xargs or apply or whatever they're really looking for.
  158.  
  159.         STEP 3: Print out foo()'s output appropriately.
  160.  
  161. I'd like this to work both for things that return single values and those
  162. that return lists.  Each value should be printed out with a newline tagged
  163. on the end for the shell's benefit.  That means basically doing this
  164.  
  165.     print join("\n", &$routine(@ARGV)), "\n";
  166.  
  167. Although there's no need to make a scalar, so we should do this instead:
  168.  
  169.     $, = "\n";
  170.     print &$routine(@ARGV)), '';
  171.  
  172.  
  173.         STEP 4: Exit appropriately.
  174.  
  175. This is more interesting than it might initially appear.  Shell programs
  176. should exit zero if they succeed, and non-zero if they fail.  This means
  177. there's one success state and a bunch (1..255) of failure states.
  178.  
  179. Perl functions, on the other hand, generally indicate failure by returning
  180. undef (as opposed to merely the null string or 0), whereas success can
  181. come in any one of a virtually infinite variety of fun-filled flavours.
  182.  
  183. I propose that the following classes of functions should all be accomodated:
  184.  
  185.     I.     sub hello     { "Hello, world!"     } 
  186.     II.  sub whoami     { (getpwuid($>))[0]     }
  187.     III. sub pwnam     { getpwent(shift)     }
  188.     IV.  sub amisu     { $> == 0        }
  189.     V.   sub range     { shift .. shift     }
  190.  
  191. Type I functions are the easiest, since they also succeed.
  192. We just print out their return value(s) and exit 0.  
  193.  
  194. Type II functions aren't that much different.  But they can fail.
  195. So we now no that we have to check for a return of undef, and both
  196. not print anything and exit non-zero. This brings our code to 
  197.  
  198.     @return = &$routine(@ARGV);
  199.     exit 1 if @return == 0 || @return == 1 && !defined $return[0];
  200.  
  201.  
  202. Type III functions are fine now, because if the getpwent fails,
  203. we get undef back, and no printout and a failure exit code.
  204.  
  205. Type IV functions are particularly problematic.  You'd really like
  206. to be able to say
  207.  
  208.     if (&amisu) { }
  209.  
  210. in perl, and
  211.  
  212.     if amisu; then ... 
  213.  
  214.  
  215. in shell.   
  216.  
  217. That means that we must introduce a hack.  If the return value is 
  218. 1, then we aren't going to print it out, but just return success.
  219.  
  220.     print (@return, "") unless @return == 1 && $return[0] == 1;
  221.  
  222. Now, because of our decision for class IV functions, we have a problem
  223. with class V functions (well, and others).  Consider: if we call
  224.  
  225.     range 1 1
  226.  
  227. it'll return just 1, which wouldn't get printed.  But so it goes.
  228.  
  229.  
  230. So, that leaves us with this for exechook, which isn't very big:
  231.  
  232.     #!/usr/bin/perl
  233.     $ME = 'exechook';
  234.     if ($0 =~ m#/?$ME$#) {
  235.     die "usage: [$ME] file [args ...]\n" unless @ARGV;
  236.     $routine = shift;
  237.     } else {
  238.     ($routine = $0) =~ s#.*/##;
  239.     } 
  240.     require "$routine.pl";
  241.     @return = &$routine(@ARGV);
  242.     exit 1 unless defined $return[0];
  243.     $, = "\n";
  244.     print (@return, "") unless $return[0] == 1;
  245.     exit 0;
  246.  
  247.  
  248. --tom
  249. -- 
  250.     Tom Christiansen      tchrist@convex.com      convex!tchrist
  251.     I might be able to shoehorn a reference count in on top of the numeric
  252.     value by disallowing multiple references on scalars with a numeric value,
  253.      but it wouldn't be as clean.  I do occasionally worry about that. --lwall :-)
  254.