Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Retunining hash values from subroutines

by Bindo (Acolyte)
on Jun 20, 2013 at 10:20 UTC ( [id://1039936]=perlquestion: print w/replies, xml ) Need Help??

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

Hi Monks, I just want to learn how to work with hashes within subroutines. For an instance say that I want to get all the users whos ids are greater than 500 from /etc/passwd file and list thier home directories in the following manner

dave --> /x02/dave

jack --> /x02/jack

Following script would only print information pertaning to one user.
my %UID_PATH; my %ARR1 = uid(); foreach my $TAB (keys %ARR1) { print "User --> $TAB, Value --> $ARR1{$TAB}\n"; } sub uid { my %UID_PATH; open (FH1, "</etc/passwd") || die "Can't Open : $!"; while (<FH1>) { my %UID_PATH; my @UID = split (/:/, $_); if ($UID[2] > 500) { my $USER = "$UID[0]"; %UID_PATH = ("$USER" => "$UID[5]"); #print "$USER --> $UID_PATH{$USER}\n"; return %UID_PATH; } } }

Please can someone help me out. I just want to to display all users with a UID greater than 500. Still main rational here is to understand hash behavior in subs. :)

Replies are listed 'Best First'.
Re: Retunining hash values from subroutines
by bart (Canon) on Jun 20, 2013 at 10:51 UTC
    There are a few things wrong with your approach.
    1. You return the hash when you match a line for a single user
      Instead, put the return statement at the end of the sub.
    2. You replace the contents of the hash for each user
      Instead, add a single entry in the hash for each user doing this:
      $UID_PATH{$USER} = $UID[5];

    For optimization you could decide to return the hash ref, \%UID_PATH instead of a flattened list, but then you'd have to fix the script to use a hashref instead ofa hash, everywhere. (In languages like PHP and Javascript, there is no difference between a hash ref (or a hash. (AKA "associative array" in PHP and "object" in Javascript)

Re: Retunining hash values from subroutines
by hbm (Hermit) on Jun 20, 2013 at 11:31 UTC

    In addition to bart's comments, you define %UID_PATH three times:

    • globally, which you don't use
    • at the start of your sub
    • within the while loop (the effect of which you will see when you move the 'return' out of the loop)

    Keep only the second.

Re: Retunining hash values from subroutines
by packetstormer (Monk) on Jun 20, 2013 at 11:33 UTC

    You could be a bit cleaner still and not declare the %UID_PATH hash as many times. Also, as stated above, the reason you were getting one line is that your return value was within the while loop, meaning the only the last value would be returned. Moving it outside and cleaning up a little as below:

    #!/usr/bin/perl use strict; my %ARR1 = uid(); foreach my $TAB (keys %ARR1) { print "User --> $TAB, Value --> $ARR1{$TAB}\n"; } sub uid { my %UID_PATH; open (FH1, "</etc/passwd") || die "Can't Open : $!"; while (<FH1>) { my @UID = split (/:/, $_); if ($UID[2] > 500) { my $USER = "$UID[0]"; $UID_PATH{$USER} = "$UID[5]"; } } return %UID_PATH; }

      Thank you so very much gentlemen. You all are great :)

      Mr Packetst thank you very much I think I almost had it before I even began this thread but I lost it because of the has format I used.

      %UID_PATH = ("$USER" => "$UID[5]")

      instead of

      $UID_PATH{$USER} = "$UID[5]";

      Even though my problem is solved I still like to know as to how I should write the hash like the fist instance with the arrow, if at all possible. Kindly note if I write it that way only one entry from the /etc/passwd is returned.

        This line: %UID_PATH = ("$USER" => "$UID[5]")
        creates a hash containing the values "$USER" => "$UID5".

        Where as this line:
        $UID_PATH{$USER} = "$UID[5]"; adds a value to the already exsiting hash.
        So, if you want to iterate through a file and perform some logic then add the result to an existing hash you use the second line. As long as the hash key is unique it will simple add to the hash leaving you with the end result of a hash with multiple values.

        Here is a really simple example.

        #!/usr/bin/perl use strict; use warnings; use Data::Dumper; # Declare your hash my %hash; # Add the values below to the hash $hash{1} = "a"; $hash{2} = "b"; $hash{3} = "c"; print "Hash after adding values\n"; print Dumper \%hash; # Reinitialise hash - removing all previously added values %hash = ('1' => 'a'); print "Hash when reinitialised\n"; print Dumper \%hash;

        %UID_PATH = ("$USER" => "$UID[5]")

        creates a new hash, meaning that anything that existed before in %UID_PATH is wiped out. This syntax is OK to give initial values to a hash, but should not be used for anything else, expecially not for feeding new key/value pairs into the hash. For that, use the other syntax that you showed:

        $UID{$USER}= $UID[5]

        (or whatever it was exactly)...

        BTW, I would recommend against using upper case letters for variable names, it is a pain in the neck to type them, although I must admit it is largely a matter of personal taste. The most common practice is to have lower case letters for variables and functions, Title Case for package names, UPPER CASE for constants (and, often files handles and dir handles).

Re: Retunining hash values from subroutines
by BillKSmith (Monsignor) on Jun 20, 2013 at 10:52 UTC
    You are almost there.
    my %UID_PATH; my %ARR1 = uid(); foreach my $TAB (keys %ARR1) { print "User --> $TAB, Value --> $ARR1{$TAB}\n"; } sub uid { my %UID_PATH; my %ARR2; #add open (FH1, "</etc/passwd") || die "Can't Open : $!"; while (<FH1>) { my %UID_PATH; my @UID = split (/:/, $_); if ($UID[2] > 500) { my $USER = "$UID[0]"; %UID_PATH = ("$USER" => "$UID[5]"); #print "$USER --> $UID_PATH{$USER}\n"; ####### return %UID_PATH; $AAR2{$USER} = $UID_PATH; #add } } return %AAR@; }
    Bill
Re: Retunining hash values from subroutines
by kcott (Archbishop) on Jun 20, 2013 at 21:21 UTC

    G'day Bindo,

    You seem to have your answer with respect to "... main rational here is to understand hash behavior in subs.". This is additional information, regarding your example code, which you may find useful.

    Here's a much more succinct way to generate the output you want:

    $ perl -Mstrict -Mwarnings -le ' while (my @pwent = getpwent()) { next if $pwent[2] <= 500; print join " --> ", @pwent[0,7]; } ' ... ken --> /Users/ken ...

    In addition to getpwent() (which iterates through all records), there's also getpwnam() (which returns a single record based on username), getpwuid() (which returns a single record based on numeric user ID) and other related functions.

    While the online documentation (Fetching user and group info) lists these functions, it provides little in the way of details (return values are not even shown). For that, you can use:

    perldoc -f getpwent

    -- Ken

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1039936]
Approved by Happy-the-monk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others meditating upon the Monastery: (4)
As of 2024-03-28 13:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found