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

Hi Monks, I have a question. I am having a hard time understanding how hashes work, an example of the code below demonstrates what I am trying to accomplish. I am wondering howto take multiple comma delimited files (/etc/passwd) and take the unique ids(which is already present in another file) off of each file and push pop whatever them to append to a different file that already has the same ids present. Basically I have another file with the unique ids present already and from the /etc/passwd, named passwd.(server) files i want to list every passwd.server file that these unique ids from the existing file are on. What i want to know about the code below is line by line what is it doing? Thanks.
#!/usr/bin/perl -w use strict; my %USERS; foreach my $file (<passwd.*>) { open(PASSWD,$file); my $strip=$file; $strip =~ s/passwd\.//; while(<PASSWD>) { my($login,$gcos) = (split(':',$_))[0,4]; if(exists $USERS{$login}) { push(@{$USERS{$login}},$strip); } else { $USERS{$login} = [$gcos,$strip]; } } close(PASSWD); } open(NEWFILE,">endstrx") || print "Can't open servup: $!\n"; foreach my $login (sort keys %USERS) { print NEWFILE "$login:".shift(@{$USERS{$login}}).":"; print NEWFILE join(':',@{$USERS{$login}})."\n"; } close(NEWFILE);

Replies are listed 'Best First'.
Re: hash of hashes -newbie question?
by eric256 (Parson) on Nov 05, 2003 at 16:24 UTC
    In case you don't have line numbers turned on.
    001 #!/usr/bin/perl -w 002 use strict; 003 my %USERS; 004 foreach my $file (<passwd.*>) { 005 open(PASSWD,$file); 006 my $strip=$file; 007 $strip =~ s/passwd\.//; 008 while(<PASSWD>) { 009 my($login,$gcos) = (split(':',$_))[0,4]; 010 011 if(exists $USERS{$login}) { 012 push(@{$USERS{$login}},$strip); 013 } else { 014 $USERS{$login} = [$gcos,$strip]; 015 } 016 } 017 close(PASSWD); 018 } 019 020 open(NEWFILE,">endstrx") || print "Can't open servup: $!\n"; 021 foreach my $login (sort keys %USERS) { 022 print NEWFILE "$login:".shift(@{$USERS{$login}}).":"; 023 print NEWFILE join(':',@{$USERS{$login}})."\n"; 024 } 025 close(NEWFILE); 001 - 002 = Get the script ready to run turning warnings and strict on 003 = Get the hash %USERS ready for later 004 = loop threw all files matching the glob 'passwd.*' 005 = open the file found and give it the handle PASSWD 006 - 007 = copy the file name and remove the part before the first . 008 = loop over the lines of the file 009 = split the line on :, take the 1st and 4th element and putt +ing them in $login, and $gcos respectivly 011 = check if an element already exists in the hash with that u +ser name 012 = if so then push this file name onto the list at that key +. 014 = otherwise start a list at this key with the gcos and the + filename 017 = close the file 020 = open a new file for output with teh name endstrx and the h +andle NEWFILE 021 = loop over the keys of the USER hash, sorting them alphabet +icaly 022 = print the users name, followed by the first element of the + list at that key, 023 = then join all the file names together and print them as we +ll 025 = close the file
    ___________
    Eric Hodges

    janitored by ybiC: s/pre/code/ as per Monastery convention

Re: hash of hashes -newbie question?
by Anonymous Monk on Nov 05, 2003 at 16:13 UTC

    Well, first of all, your title says "hash of hashes" but what you are working with here is a hash of arrays (actually, hash of anonymous array references).

    Rather than walk you through your example code line by line (I assume you must understand some of it and are only really worried about the hash of arrays).

    my($login,$gcos) = (split(':',$_))[0,4]; if(exists $USERS{$login}) { push(@{$USERS{$login}},$strip); } else { $USERS{$login} = [$gcos,$strip]; }

    The above is the core code that builds your structure. You obtain a login and gcos from a line in a passwd file. Next, you test %USERS to see if that login exists as a key in that hash. If it *doesn't* we assign an anonymous array reference to that key and populate it with the gcos and the current servername stripped from the filename. If the login does exists (we found it in a previous file), then we just push the current server ($strip) onto the array reference that already exists for that key:

    push( @{ $USERS{$login} }, $strip );

    Here, $USERS{$login} resolves to an array reference and we are de-referencing it: @{ arrayref } in order to push another value onto that array.

    In the final section of code, you simply loop over the sorted keys (login ids) of the hash, and print out each login followed by its gcos and the list of servers (contained in the anonymous array) with colon separators. That loop could be simplified to:

    foreach my $login (sort keys %USERS) { print NEWFILE join(':', $login, @{$USERS{$login}}), "\n"; }

    Hope it helps.