Perl array of hashrefs problems

287 views Asked by At

I have this in my project:

sub get_src_info($) {
        my $package = shift;
        my ($key,$value,$tail) =("","","");
        my (@APT_INFO,$r);

        open APT, "apt-cache showsrc $package|";

        while (<APT>){
                chomp;
                ($key,$value,$tail) = split /:/;
                if (defined ($key) && defined ($value)) {
                        $value =~ s/^\s+|\s+$//;
                        if (defined($tail)) {
                                $value = "$value:$tail";
                        }
                        if ( $key eq "Package" or $key eq "Version" ) {
                                $r->{$key} = $value;
                        }
                }
                if(/^$/) { push @APT_INFO,$r++; }
        }
        close APT;
        return @APT_INFO;
}

I generally use use strict to check for errors. The code works with no strict "refs"; instruction, but fails to run without it, yielding error:

Can't use string ("163277181") as a HASH ref while "strict refs" in use at a.pl line 61, <APT> line 45.

line 61 is: $r->{$key} = $value;

I prefer to fix my code, rather than silence it, but can't get what's wrong/how to fix this.

Also, what's correct way to advance a reference to point to the next object? Although it works I do not feel like $r++ is correct construction in here.

thanks a lot in advance.

1

There are 1 answers

0
amon On

You use the $r variable both as a hash reference $r->{$key}, and a number $r++. References numify to an ID, so you can use them as numbers. However, you cannot use a non-reference scalar as a reference (references are not pointers). To make this clear:

my $reference = { foo => 1 };
my $numified  = 0 + $reference;  # is now 163277181
say $numified->{foo};  # does not work under strict "refs"
# ${"163277181"}{foo} is equivalent: This looks for global variable %163277181

You can work around the problems by simply creating a new reference in $r when a new block begins. You should also scope your other variables properly: do not use globals like APT, and declare your $key etc. variables inside the loop.

Also, $key can never be undef (split doesn't return undef values), and you should not use prototypes.

I guess the following code will do what you want:

use autodie; # automatic error handling, e.g. for `open`

sub get_src_info {
  my ($package) = @_;
  my $info = {};
  my @apt_info;

  open my $fh, "-|", "apt", "showsrc", $package; # circumvent the shell for safety
  while (<$fh>) {
    chomp;
    unless (length) {
      push @apt_info, $info;
      $info = {};  # put a new reference into $info
      next;
    }
    my ($key, $value) = split /:/, $_, 2;  # limit number of fragments to 2
    next unless $key eq "Package" or $key eq "Version";
    s/^\s+//, s/\s+$// for $value;  # this trims at *both* ends
    $info->{$key} = $value;
  }

  return @apt_info;
}

More on References vs. Pointers:

You cannot use pointer arithmetic in memory safe languages. Perl is such a memory safe language. The references which you can use are similar to pointers in C. They are typed as well (although dynamically), but are reference counted automatically, so that the referenced data structure is freed once it isn't needed. Proponents of garbage collection and memory safety point to less errors (e.g. double free) and increased productivity, although some algorithms may not be expressed as elegantly. Most modern languages are memory safe by default.

Even if this were C, and $r++ would point to a new address, I'd have to ask you: Shouldn't you malloc new memory? Memory allocation is implicit in Perl. The $r = {} gives us a new, empty hash reference. If you use lexical variables (with my), deallocation happens automatically as well.