Perl Lines of the Day from February, 1999

24 February, 1999


  local *h = $h;

I had a program with a function that expected to get an argument $h, which was a hashref. $h had a lot of members; I passed it by reference partly for efficiency. But inside the function, I used $h a lot and got tired of writing $h->{FOO_BAR} everywhere.

I added the Line of the Day as the first line of the function, and changed all the $h->{FOO_BAR} to $h{FOO_BAR} instead.

18 February, 1999

Special 1st Anniversary Line of the Day

  substr($permstrs[0], -1) =~ tr/-x/Ss/;

Some time ago, I wrote a module, Stat::lsMode which generated a string to represent file permissions in the style of the ls -l command. In it, I used the following monstrosity:

  $permstrs[0] =~ s/([-x])$/$1 eq 'x' ? 's' : 'S'/e;

What's this for? $permstrs[0] contains a string that represents the three user-permission bits of the file's permissions, typically something like r-x.

Normally, if the file is user-executable, we want the last character to be x to indicate that; otherwise, the last character is - to show that it's not executable. However, if the setuid bit is enabled on the file, we change x to s and, less commonly, - to S. The line above accomplishes this.

However, I didn't like the eval, and I kept wishing I could use tr for what was basically a character-to-character translation task. But I thought I couldn't use tr here, because what came to mind was this:

  $permstrs[0] =~ tr/-x/Ss/;

This is wrong because it turns r-x into rSs when the setuid bit is on, and it should turn it into r-s.

The Line of the Day is a correct solution, and I think it's preferable to what I had before. The lesson I learned from this is that although lvalue substr is in my bag of tricks, it's not as close to the top of the bag as it probably should be.

(See also 27 April 1998 item for the appalling code that converts in the opposite direction.)

17 February, 1999


  *{$safe->root . '::'} = \%{$package . '::'};

Suppose you have a compartment object $safe from the Safe package. Normally, code evaluated in these compartments is isolated from the main Perl variable namespace, so it can neither read nor write the usual set of variables. This is usually what you want from Safe compartments. But in my Text::Template module I wanted a convenient way to let the code in the compartment see the contents of a certain user-specified package $package. This line accomplishes that.

Here's an example:

  $Q::X = 119;
  $X    = 57;
  $val = $safe->reval("$X + 1");
  print "val=$val\n";

Normally, this program will report that val=1, because the Safe compartment does not have access either to the main package or to package Q. However, after using the Line of the Day:

  $package = 'Q';
  *{$safe->root . '::'} = \%{$package . '::'};

The program will report val=120, because the default root package of the compartment has been aliased to Q.

How does this work? There's actually quite a lot of magic here. $safe->root retrieves the name of the root of the compartment's package namespace, typically something like Safe::Root119. If you have a package name, and you append ::, you get the name of the hash that holds the symbol table for that package, in this case Safe::Root119::.

On the right-hand side, we construct the name of the symbol table for the user's package. Then we do a glob assignment to alias the compartment's symbol table to be the user's symbol table. The effect is that the symbol table in which the compartment is looking is the same symbol table as the one used for the user's package.

Usually, aliasing of symbol tables in this way has no effect, because the path through the symbol table is determined at compile time, and once the code is compiled, a run-time aliasing like this is too late. But in this case, the code that it's supposed to affect hasn't been compiled yet; it is going to be compiled and executed in the safe compartment by the $safe->reval method.

15 February, 1999


   local $/ unless wantarray;

There's been discussion in comp.lang.perl.moderated about a function that opens and reads a file and returns the contents. In list context is should return a list of lines, and in scalar context it should return the entire contents as a single string.

This isn't really interesting, except that in scalar context you want to be sure you don't end up reading the file line-by-line and then putting the lines back together. That's a waste of time.

Here's the obvious solution:

  sub read_file {
    my $file = shift;
    local *F;
    open F, $file
      or die "Couldn't open file `$file': $!; aborting";
    if (wantarray) {
      my @lines = <F>
      @lines;
    } else {
      local $/;
      my $text = <F>
      $text;
    }
  }

The local $/ line in the scalar context branch temporarily undefines $/, which puts Perl into `slurp mode' so that the subsequent <F> reads the entire file into $text.

I realized that the @lines and $text variables are unnecessary here; you might as well just return the result of <F> directly: (Purple lines show changes.)

  sub read_file {
    my $file = shift;
    local *F;
    open F, $file
      or die "Couldn't open file `$file': $!; aborting";
    if (wantarray) {
      <F>
    } else {
      local $/;
      <F>
    }
  }

And that's when I had my inspiration:

  sub read_file {
    my $file = shift;
    local *F;
    open F, $file
      or die "Couldn't open file `$file': $!; aborting";
    local $/ unless wantarray;
    <F>
  }

Yes, this actually works.


Return to: Universe of Discourse main page | What's new page | Perl Paraphernalia | Line of the Day

mjd-perl-lotd@plover.com