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

use strict; use warnings; use experimental 'smartmatch'; use Data::Dump 'dump'; my @ipaddresses = (''); my $targetfile="/etc/hosts"; my $ipentry=""; #my @domains = (''); my @domains; my %hashEntries; open(my $descripteur,'<:encoding(UTF-8)', $targetfile) or die "$target +file not found :{"; while(my $ligne = <$descripteur>) # tant que nous ne somme +s pas arrivés à la fin du fichier { chomp $ligne; # + on enlève le caractère '\n' de fin de ligne if($ligne =~ /^([0-9]{1,3}\.){3}[0-9]{1,3}/) { ($ipentry,@domains)=split('\s{1,}|\t',$ligne); + # on "splitte" à un ou plusieurs espaces ou à une + tabulation if(!($ipentry ~~ @ipaddresses)) { print "IP trouvée --> $ipentry \n"; $hashEntries{$ipentry} = [ "@domains" ]; push(@ipaddresses,$ipentry); } else { my @temp=$hashEntries{$ipentry}; print "[Add] Nombre d'éléments: @temp\n"; push(@temp,[@domains]); $hashEntries{$ipentry} = [@temp]; + # contre intuitif... un "add" ou " ++=" ou quelque chose qui pourrait noter qu'on ajoute à ce qui existe +déjà... print "[After] Nombre d'éléments: @temp\n"; } } } print dump(%hashEntries)."\n"; foreach my $key (keys %hashEntries) { my @temp=$hashEntries{$key}; #print "Elements: ".@temp."\t"; print "[After no dump] Test 2 $key ->\t"; foreach my $element (@temp) { if($element ~~ /ARRAY/) { print "Array détecté...\t"; foreach my $autreelement (@$element) { print "[@$autreelement]\n"; } } print "[@$element]\n"; } } close($descripteur);
The issue is related to the fact that when I try to print out the content of the hash I got strange behaviour... it displays the datas in the hash plus the references (pointers) to something I consider as already processed...

After no dump Test 2 127.0.0.6 -> Array détecté... www.examendecembre.uaa12.test <-- HORRAY I can do stuff on it
test.com test2.com test3.com <-- HORRAY I can do stuff on it
ARRAY(0x560b271bdff0) ARRAY(0x560b271e18f8) <-- ???????????????????????????

The meaning of this script is to edit the /etc/hosts in order to let, for each IPv4 address, only one line with all domain names related to this IP instead of having a real mess in /etc/hosts.
For example:
127.0.0.3 onehost
...
127.0.0.6 onehost
...
127.0.0.5 oneotherhost
...
127.0.0.6 manyotherhosts host1 host2 host3
Would become...
127.0.0.6 onehost manyotherhosts host1 host2 host3
...once this script was launched.

Questions: how to

  1. Detect the element is an ARRAY... in perl
  2. Acknowledge the number of elements in each hash
  3. Avoid having the two ARRAY references within a hash element

Replies are listed 'Best First'.
Re: [Perl 5.26][Linux LEAP] Array/List/Hash misunderstanding
by hippo (Archbishop) on Feb 14, 2023 at 17:02 UTC
Re: [Perl 5.26][Linux LEAP] Array/List/Hash misunderstanding
by jwkrahn (Abbot) on Feb 14, 2023 at 21:32 UTC

    Just a small nit to pick with this line of code:

    ($ipentry,@domains)=split('\s{1,}|\t',$ligne); + # on "splitte" à un ou plusieurs espaces ou à une + tabulation

    Your regular expression /\s{1,}|\t/, which can also be expressed as /[ \t\r\n\f]{1,}|\t/, says to match one or more whitespace characters (which includes the TAB character) or if that doesn't match then match a TAB character.

    Your comment says that you want this regular expression: / {1,}|\t/ (or / +|\t/)

    Naked blocks are fun! -- Randal L. Schwartz, Perl hacker
Re: [Perl 5.26][Linux LEAP] Array/List/Hash misunderstanding
by kcott (Archbishop) on Feb 15, 2023 at 04:06 UTC

    G'day soundlord,

    Here's a few tips for this, and all your other Perl scripts, on your current O/S with your stated Perl version. Change

    use strict; use warnings;

    to

    use 5.026; use warnings; use autodie; use utf8;

    use 5.026; gives you all the features of your Perl version and use strict; automatically. There's many you can use here, and in other scripts; one that stands out in your posted code is, instead of adding "\n" to the end of your print statements, you can use say: 'say "XYZ";' is the same as 'print "XYZ\n";'.

    use warnings; -- no change; use it always.

    The autodie pragma will handle your I/O exceptions for you. '... or die "$targetfile not found :{";' is a poor error message: you're guessing at "not found" but it could be, for instance, a permission problem ($! is typically used to get the reason open() failed). Just code "open my $descripteur, '<:encoding(UTF-8)', $targetfile;" and leave the rest up to autodie.

    One of my $work machines is openSUSE Leap 15.2 with Perl v5.26.1 -- I assume the same, or very close, to what you have. I too can get away with using UTF-8 characters (e.g. é) in my source code without "use utf8;"; someone else using your code may not. Play it safe and use the utf8 pragma.

    If your first line is

    #!/usr/bin/env perl

    you can run your script as "./script.pl", instead of "perl script.pl". See perlrun for more about that.

    — Ken

Re: [Perl 5.26][Linux LEAP] Array/List/Hash misunderstanding
by soundlord (Novice) on Feb 14, 2023 at 16:18 UTC
    I solved the issue like this:
    foreach my $key (keys %hashEntries) { my @temp=$hashEntries{$key}; #print "Elements: ".@temp."\t"; print "[After no dump] Test 2 $key ->\t"; foreach my $element (@temp) { if($element ~~ /ARRAY/) { print "Array détecté...\t"; foreach my $autreelement (@$element) { print "[@$autreelement]\n"; $lesdomaines.="@$autreelement"; $lesdomaines.="\t"; } } else { print "[@$element]\n"; $lesdomaines.="@$element"; $lesdomaines.="\t"; } } print $key."\t".$lesdomaines."\n"; $lesdomaines=""; }
    output here

      This is not a good solution. This is quite a poor solution. You shouldn't be checking if the value is an array reference. It should always be an array reference.

      use List::Util qw( uniq ); use Sort::Natural qw( natsort ); my %data; while ( <$fh> ) { chomp; my ( $ip, @hosts ) = split; push @{ $data{ $ip } }, @hosts; } for my $ip ( natsort keys( %data ) ) { my @hosts = natsort uniq @{ $data{ $ip } }; say join "\t", $ip, @hosts; }

      Actually, I'd use a hash of hashes since it will handle duplicates. The code would look something like this:

      use Sort::Natural qw( natsort ); my %data; while ( <$fh> ) { chomp; my ( $ip, @hosts ) = split; ++$data{ $ip }{ $_ } for @hosts; } for my $ip ( natsort keys( %data ) ) { my @hosts = natsort keys %{ $data{ $ip } }; say join "\t", $ip, @hosts; }

      It needs to handle blank lines and comments, but you get the idea.

      You don't need to use natsort, but it provides a better sort order than sort. In fact, you don't have to use any sorting at all, but it's nicer.

Re: [Perl 5.26][Linux LEAP] Array/List/Hash misunderstanding
by alexander_lunev (Pilgrim) on Feb 15, 2023 at 06:42 UTC

    Hello!

    I'm assuming that you call 'element' a value of $hash{$key} (because in a canonical way of speaking 'element' is a pair of key and value in a hash).

    1. Detect the element is an ARRAY... in perl

    foreach my $element (values %hash) { if (ref $element eq ref []) { } # OR if (ref $element eq 'ARRAY') { } }

    2. Acknowledge the number of elements in each hash

    my $number_of_elements = scalar keys %hash;

    3. Avoid having the two ARRAY references within a hash element

    There can be only one reference in a hash value. Did you mean "avoid pushing ARRAY into hash element, if it's already an ARRAY ref"?

    my %hash; foreach my $pair (qw/1_one 1_two 2_one 2_two/) { my ($key, $val) = split('_', $pair); if (not exists $hash{$key}) { $hash{$key} = []; } push @{ $hash{$key} }, $val; }
Re: [Perl 5.26][Linux LEAP] Array/List/Hash misunderstanding
by soundlord (Novice) on Feb 14, 2023 at 16:08 UTC

    My bad :{
    I forgotten the "else part" of the "if" so... as a real beginner, the hashes are outputed :{
    The part 3 is answered so...