1
#! /usr/bin/perl 
use strict;
use warnings;
use File::stat;

my $file_name = 0;
my $info = 0;
my $ret_mode = 0;
my $size;
my $last_mod;
my @array_file;
my $index = 0;
my @array_mode;
my @array_size;
my @array_last_mod;
foreach(@ARGV){
  $file_name = $_;
  $info = stat($file_name);
  $size = $info->size;
  $last_mod = scalar(localtime($info->mtime));
  $ret_mode  = $info->mode;
  $ret_mode = $ret_mode & 0777;
  $array_file[$index] = ($file_name);
  $array_mode[$index] = ($ret_mode);
  $array_size[$index] = ($size);
  $array_last_mod[$index] = ($last_mod);
  $ret_mode = 0;
  $index++;
 }
 my @array_arrays = (@array_file, @array_mode, @array_size, @array_last_mod);
 my $array_ref = \@array_arrays;
 my $i = 0;
 for(@$array_ref){
  print "@$array_ref[$i]\n";
  $i++;
 }

I have created an array of arrays and I want to print the filename,mmode,size and last access time from the array of arrays created. Its not printing any values with,

 for(@$array_ref){
  print "@$array_ref[$i]\n";
  $i++;
 }
1

2 Answers 2

5
my @array_arrays = (@array_file, @array_mode, @array_size, @array_last_mod);

This statement does not create an array of arrays. Instead, It flattens the various arrays into one big flat list and then assigns that to @array_arrays. You want to assign array references. Obtain those with the reference operator \:

my @array_arrays = (\@array_file, \@array_mode, \@array_size, \@array_last_mod);

or with the shortcut

my @array_arrays = \(@array_file, @array_mode, @array_size, @array_last_mod);

Even then, your last foreach-loop is wrong. You probably meant

for my $i (0 .. $#{ $array_arrays[0] }) {
  for my $aref (@array_arrays) {
    print $aref->[$i], "\n";
  }
}

or something similar.


Your code style could be improved.

  • Please don't declare all your variables at the top. Declare them in the tightest scope possible. Try to declare them at the point of initialization, e.g.

    for my $file_name (@ARGV) {
      my $info = stat($file_name);
      my size = $info->size;
      ...
    }
    
  • Don't prefix your array names with array_. The @ sigil and/or subscripting with the [...] operator makes it clear that these are arrays.

  • $ret_mode & 0777 – The result should be $ret_mode itself: 0777 is 0b111111111. I.e. this removes all but the last 9 bits – you wouldn't care if there were more to the left.

  • $last_mod = scalar(localtime($info->mtime)); – due to the scalar assignment, localtime is already executed in scalar context. No need to make this explicit.

  • my $index = 0; ... for (...) { $array[$index] = ...; $index++ }. Please not. Just use push: for (...) { push @array, ... }. Don't maintain indices yourself unless you have to.

  • $ret_mode = 0; Why? you assign a new value during the next iteration anyway. Note that you should declare this variables inside the loop (see my point about tight scopes), which would create a new variable in each iteration, making this even more useless.

  • my $array_ref = \@array_arrays; .. @$array_ref[$i]. Isn't this a bit backwards? $array_arrays[$i] would work just as well. Note that in your defeferencing, @ is probably the wrong sigil. You meant $$array_ref[$i].

Sign up to request clarification or add additional context in comments.

Comments

3

Let's try a little something different.

First off, there's a nice syntax using the -> for referenced arrays and hashes. Here I am going to make a array of people. I'll make a hash of %person that contains all that person's information:

my %person;
my $person{NAME} = "Bob";
my $person{JOB}  = "Programmer";
my $person{PHONE} = "555-1234";

Now, I'll put it into an array:

my @array
my $array[0] = \%person;

I could reference the person in the array this way:

print ${$array[0]}{NAME} . "\n";   #Prints Bob
print ${$array[0]}{JOB} . "\n";    #Prints Porgrammer

But, Perl gives me a nice clean way to do this:

print $array[0]->{NAME} . "\n";    #Prints Bob
print $array[0]->{JOB}  . "\n";    #Prints Progammer

In fact, I could skip the hash all together. Here I am adding Jill to my array:

$array[1]->{NAME} = "Jill";
$array[1]->{JOB}  = "DBA";
$array[1]->{PHONE} = "555-5555";

You can see this is a much simpler way to use references. It's easier to see what is going on and takes fewer lines of code.

You can refer to an array of an array like this:

$myarray[1]->[3] = 42;

Or have a hash which stores an array. In this day and age, who has only a single phone number?:

$person[1]->{PHONE}->[0] = "555-4567";
$person[1]->{PHONE}->[1] = "555-4444";

Or, to make it even more complex we could have a hash of a hash of an array:

$person[1]->{PHONE}->{CELL}->[0] = "555-1111";
$person[1]->{PHONE}->{CELL}->[1] = "555-2222";
$person[1]->{PHONE}->{HOME}->[0] = "555-3333";
$person[1]->{PHONE}->{JOB}->[0] = "555-4444";
$person[1]->{PHONE}->{JOB}->[1] = "555-5555";

Using this syntax will really help clean up a lot of your code. You won't have to store the information into individual structures that are then only used to make references. Instead, you can simple setup your structure the way you want without intermediary steps.

Now to your problem: You're trying to store a bunch of information about files into a series of arrays. What you're hoping is that $array_mode[1] goes with $array_file[1] and you have to keep all of these arrays in sync. This is a pain and it is complex.

The entire purpose of using references is to eliminate this need for multiple variables. If you're going to use references, why not simply store your entire file structure into a single array.

What you really, really want is an array of hash references. And, that hash reference will be keyed based upon your file attributes. Here is your code restructured into using an array of hash references. I didn't even bother to check the rest of it. For example, I'm not sure how your localtime thing will work:

use strict;
use warnings;
use feature qw(say);
use File::stat;

my @files;
for my $file ( @ARGV ) {
    my $info = stat( $file );
    my $file = {};       #This will be a reference to a hash
    $file->{NAME} = $file;
    $file->{SIZE} = $info->size;
    $file->{RET_MODE} = $info->mode & 0777;
    $file->{LAST_MOD} = localtime $info->mtime;  #Does this work?    
    push @files, $file    #Pushes the hash reference onto the array
}

That's way shorter and cleaner. Plus, you know that $files[0]->{NAME} goes with $files[1]->{SIZE}, and if you remove $files[0] from your array, or transfer it to another variable, all of the attributes of that file go together.

Here's how you'd print it out:

for my $file ( @files ) {
    say "File Name: " . $file->{NAME};
    say "File Size: " . $file->{SIZE};
    say "Last Modified: " . $file->{LAST_MOD};
    say "File Mode: " . $file->{RET_MODE};
}

Simple and easy to do.


However, I would argue what you really want is a hash of hashes. Let your file name be the key to your main hash, and let {SIZE}, {LAST_MOD}, and {RET_MODE} be the keys to your sub hash:

my %files = {};   #This is a hash of hashes
for my $file_name ( @ARGV ) {
    my $info = stat( $file );
    $files{$file_name}->{SIZE} = $info->size;
    $files{$file_name}->{RET_MODE} = $info->mode & 0777;
    $files{$file_name}->{LAST_MOD} = localtime $info->mtime;  #Does this work?    
}

Now if someone asks, "When was foo.txt last modified?", you can say:

say "File 'foo.txt' was last modified on " . $file{foo.txt}->{LAST_MOD};

And to print out your entire structure:

for my $file_name ( sort keys %files ) {
    say "File: $file_name";
    for my attribute ( sort keys %{ $file_name } ) {
        say "    $attribute: " . $files{$file_name}->{$attribute};
    }
}

Next step is to learn about Object Oriented Perl! Object Oriented Perl uses these types of references, but will greatly simplify the handling of these references, so you make fewer programming mistakes.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.