PerlHeathen has asked for the wisdom of the Perl Monks concerning the following question:

Hello, I've got a subroutine that produces a nested hash, as below:
sub build_quota_list { my %users; open QUOTA, $quotareport; while (<QUOTA>) { if (m/ \+[ \-]/) { my ($username, $over, $used, $soft_block, $hard_block, $junk) +=split " ", $_, 6; $users{$username}{uname}=$username; $users{$username}{used}=$used; $users{$username}{soft_block}=$soft_block; $users{$username}{hard_block}=$hard_block; } } close QUOTA; return %users; }
I call it like this: my %users=build_quota_list; It seems to work fine; I've verified with the debugger that my nested hash contains what I expect it to contain. However, when I try to pass that selfsame has into a subroutine, all hell breaks loose.
sub find_deletion_candidates { my %users=@_; use User::pwent; foreach(keys %users) { my %user=$_; my $pw=getpwnam($user{uname}); my $homedir=$pw->dir; } # do bunch o' stuff I haven't coded yet return %users; } %users=find_deletion_candidates(%users);

It's really wierd. With the debugger (ptkdb), I can verify that @_ looks like I'd expect, my nice nested hash. But after I do my %users=@_, %users doesn't contain what I expect. What am I doing wrong?

Thanks a ton,

Aaron

Replies are listed 'Best First'.
Re: hash parameter question
by Celada (Monk) on Dec 12, 2005 at 22:47 UTC

    I can't reproduce this problem (%users seems to have the right value in the function) but there is a problem with the following:

    foreach(keys %users) { my %user=$_; [...] }

    $_ isn't a hash so it doesn't make sense to assign it to a hash variable. $_ is one of the hash keys returned by keys. What you want is this:

    foreach my $username (keys %users) { #my $pw=getpwnam($users{$username}{uname}); # actually it's simpler, you already have the name... my $pw=getpwnam($username); my $homedir=$pw->dir; }

    I also suggest that your build_quota_list return a reference to the hash it builds and find_deletion_candidates take this reference as input. Otherwise you are making a copy of the whole contents every time you pass it around.

Re: hash parameter question
by GrandFather (Saint) on Dec 12, 2005 at 22:51 UTC

    The following code works as expected:

    use warnings; use strict; use Data::Dumper; my %HoH = ( first => {1 => 'First 1', 2 => 'First 2'}, second => {1 => 'second ', 2 => 'second 2'}); print "Original hash: " . Dumper (\%HoH); my %newHoH = %{inAndOut (%HoH)}; print "\nnew hash: " . Dumper (\%newHoH); sub inAndOut { my (%hash) = @_; print "\nin sub: " . Dumper (\%hash); return {%hash}; }

    but the equivelent of your code:

    use warnings; use strict; use Data::Dumper; my %HoH = ( first => {1 => 'First 1', 2 => 'First 2'}, second => {1 => 'second ', 2 => 'second 2'}); my %bogusHoH = %{bogusInAndOut (%HoH)}; print "\nbogus hash: " . Dumper (\%bogusHoH); sub bogusInAndOut { my (%hash) = @_; return %hash; }

    generates warning:

    Can't use string ("2/8") as a HASH ref while "strict refs" in use at C +:\Documents and Settings\Peter.WINDOMAIN\My Documents\PerlMonks\nonam +e.pl line 9.

    You would do well to use strict; use warnings; in your code to catch this sort of thing earlier for yourself.


    DWIM is Perl's answer to Gödel
Re: hash parameter question
by chas (Priest) on Dec 12, 2005 at 22:57 UTC
    I'm not familiar with User::pwent. However, your find_deletion_candidates sub doesn't make much sense to me. The keys of %users are usernames , so what are %user and $user{uname}?
    (Update: I guess you got several answers while I was looking at your code...)

      That's exactly what's wrong
      my %user=$_;
      should be
      my %user=%$_;
      because $_ contains a *reference* to the nested hash. The fact that a scalar is being assigned to a hash is a sure sign something is wonky.

        Nope. $_ contains a string. A key from %users; which is presumably a username. Your suggestion will therefore not work.

        Had PerlHeathen written:

        foreach(values %users) { my %user=$_; my $pw=getpwnam($user{uname}); my $homedir=$pw->dir; }

        (note the use of values) then $_ would be a reference to a hash and then your solution would make the code work.

        For example:

        foreach(values %users) { my %user=%$_; my $pw=getpwnam($user{uname}); my $homedir=$pw->dir; }

        It is likely that this is what PerlHeathen was trying to write in the first place.

        jarich

Re: hash parameter question
by tphyahoo (Vicar) on Dec 12, 2005 at 22:48 UTC
    If by "nested hash" you mean a hash inside a hash, or really any kind of complicated data structure, it is a lot easier to pass the argument as a hash reference. ie,
    sub find_deletion_candidates { my $users=shift; my %users = %{$users} #etc }
    and earlier on you had
    my $users = { %some_complicated_hash }; find_deletion_candidates($users);
    Good luck!

    UPDATE: Changed "you have to" to "it is a lot easier to" after chromatic's reply.

      ...you have to pass the argument as a hash reference

      Untrue:

      use Test::More tests => 2; my %nested = ( first => { one => 'two' }, second => { three => 'four' +} ); sub get_element { my ($level_one, $level_two, %nested) = @_; return $nested{ $level_one }{ $level_two }; } is( get_element( 'first', 'one', %nested ), 'two', 'first element +access' ); is( get_element( 'second', 'three', %nested ), 'four', 'second element + access' );
        Well, chromatic is right, but his answer bothered me anyway. After thinking about it for a bit I believe I put my finger on why. I tend to parse arguments using the my $arg1 = shift;my $arg2 = shift; idiom. I know, the OP didn't do that, but bear with me.

        Parsing the arguments like chromatic did will likely lead to headache if you are passing anything other than scalars. And if your sub has more than 1 argument, there is a good chance you are. Passing arguments as scalar refs just makes my code simpler to think about. That's why I almost always do it that way, and why I gave the advice I did. Examples of headaches and non-DWIM behavior are in code below, which produces errors on both subs:

        use strict; use warnings; my %nested = ( first => { one => 'two' }, second => { three => 'four' +} ); pass_the_hash_1(%nested, 'second argument'); pass_the_hash_2(%nested, 'second argument'); sub pass_the_hash_1 { my %first_argument = shift; my $second_argment = shift; print "$first_argument{first}{one} should be two\n"; } sub pass_the_hash_2 { my %first_argument = shift; my $second_argment = shift; print "$second_argment should be second argument\n"; }