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

Hello perl-brain, I'm very confused about what seems to be a fairly straight-forward thing. My goal is to get a count of hits per minute from apache httpd access logs.

The code speaks louder than my words:

... while (<FILE>) { my $line=$_; (my $day,$month,$year,$hour,$minute) = ( $line =~ /^.*\[(\d*)\/(.*) +\/(\d*):(\d*):(\d*).*$/); if ( defined $topHash{$year}{$month}{$day}{$hour}{$minute} ) { $minuteCounter=$topHash{$year}{$month}{$day}{$hour}{$minute}; $minuteCounter++; $topHash{$year}{$month}{$day}{$hour}{$minute}=$minuteCounter; } else { $topHash{$year}{$month}{$day}{$hour}{$minute}=1; $minuteCounter=$topHash{$year}{$month}{$day}{$hour}{$minute}; } } close(FILE); foreach my $ryear ( sort keys %topHash ) { foreach my $rmonth ( sort keys %{$topHash{$ryear}} ) { foreach my $rday ( sort keys %{$topHash{$ryear}{$rmonth}} ) { foreach my $rhour ( sort keys %{$topHash{$ryear}{$rmonth}{$rd +ay}} ) { foreach my $rminute ( sort keys %{$topHash{$ryear}{$rmonth +}{$rday}{$rhour}} ) { print "\"$ryear\"\t\"$rmonth\"\t\"$rday\"\t\"$rhour\"\t +\"$rminute\"\t"; print "\"$topHash{'$ryear'}{'$rmonth'}{'$rday'}{'$rhour +'}{'$rminute'}\"\n"; } } } } }

The output looks something like this:
"2010"  "Feb"   "11"    "00"    "03"    ""

but, if I put this at the bottom of the script:
print "$topHash{'2010'}{'Feb'}{'11'}{'00'}{'03'}\n";
I get the correct result, in case of my test file:
3

What in the world is wrong with this?
print "\"$topHash{'$ryear'}{'$rmonth'}{'$rday'}{'$rhour'}{'$rminute'}\"\n";
and why oh why will it not work.

advTHANKSance!

-Eric

Replies are listed 'Best First'.
Re: hash of hash of hash of hash of hash...
by kennethk (Abbot) on Feb 25, 2010 at 22:25 UTC
    If I am reading this correctly, you are having an issue with string interpolation - see Quote and Quote like Operators. The presence of single quotes in your string is interfering with proper interpretation of your desired output. I think you will get your expected results with

    print "\"$topHash{$ryear}{$rmonth}{$rday}{$rhour}{$rminute}\"\n";

    As you code stands now, you are literally passing the keys $ryear ("\$ryear"), $rmonth ("\$rmonth"), ... and you have not defined an entry for any of those keys. This is demonstrated in the following script:

    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %hash = (1 => {1 => 1}); my $key = '1'; print "$hash{'$key'}{'$key'}\n"; print Dumper(\%hash);

    with output

    Use of uninitialized value in concatenation (.) or string at junk.pl l +ine 9. $VAR1 = { '1' => { '1' => 1 }, '$key' => {} };

    Note how the inappropriate interpolation has resulted in the autovivification of the hash key '$key'.

      That was it, thanks for the moment of clarity! I got tripped up because when explicitly stated the variables I had to single quote them.

      Thanks for your help, I really appreciate it!
      -Eric

        No, you don't. When a key does not contain any white space or does not start with a sigil, Perl will automatically stringify it. Thus

        print "$topHash{2010}{Feb}{11}{00}{03}\n";

        is syntactically equivalent to

        print "$topHash{'2010'}{'Feb'}{'11'}{'00'}{'03'}\n";.

        See Scalar value constructors in perldata, for example.

Re: hash of hash of hash of hash of hash...
by almut (Canon) on Feb 25, 2010 at 22:25 UTC

    If you put single quotes around the variables, you get literal strings as keys, such as '$ryear', not the value of the variable. Just leave the quotes off.

    Sorry, spoke too soon... (Update: unstruck original note again, as on second thought it is in fact correct.)

Re: hash of hash of hash of hash of hash...
by roboticus (Chancellor) on Feb 26, 2010 at 14:32 UTC

    elofland:

    Just a couple minor notes:

    1. Perl automatically creates hash table entries for you if you reference them, so you don't need to explicitly create them. (Search for "autovivification" for details.)
    2. Perl treats an undef as a numeric 0 when you increment the value.

    Example:

    #!/usr/bin/perl -w use warnings; use strict; my %topHash; # (1) Perl automatically creates the hash table entries as needed # (2) The undef value will be treated as zero, so the result of this # statement is that $topHash{77}{15}{Feb} is 1 ++$topHash{77}{15}{Feb}; for my $k1 (keys %topHash) { for my $k2 (keys %{$topHash{$k1}}) { for my $k3 (keys %{$topHash{$k1}{$k2}}) { print "$k1 / $k2 / $k3 : '$topHash{$k1}{$k2}{$k3}\n"; } } }

    So you can simplify the first chunk of your code to:

    ... while (<FILE>) { my $line=$_; (my $day,$month,$year,$hour,$minute) = ( $line =~ /^.*\[(\d*)\/(.*) +\/(\d*):(\d*):(\d*).*$/); ++$topHash{$year}{$month}{$day}{$hour}{$minute}; } close(FILE);

    ...roboticus