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

Hello how to I return an array from a funciton? If i put a foreach loop within the funciton, it prints out what is in the array so I know it's being populated. However I cannot seem to figure out why it's not passing the array back into the main program. Below is the code I have written, does anyone see anything wrong?
sub getfiles() { opendir DIR,$logSite or die "Unable to open Directory, please try ag +ain"; foreach $pattern (@ARGV) { push (@uploads, grep /$pattern/, readdir(DIR)); } closedir DIR; return \@uploads; }

Original content restored above by GrandFather

Thanks tilly for your help. That worked like a charm. I still have to get a grasp on context and dereferencing.

Replies are listed 'Best First'.
Re: sub functions return array's
by tilly (Archbishop) on Mar 02, 2009 at 15:16 UTC
    The biggest bug I see is that you are calling readdir multiple times in list context, but it will only return the directory once. If you want to iterate through it multiple times you should call it once and store that in an array. The second biggest potential cause of problems is that you are returning a reference to an array rather than the array directly, which could be a problem depending on how you are calling it.

    However that said, virtually every line has "room for improvement". Here is a list of specifics:

    • Get rid of the () in your function declaration. Prototypes in Perl almost certainly don't do what you think they do, and are likely to cause confusion for maintenance programmers. Consider them and idea that didn't work out and drop them.
    • These days instead of saying DIR you can say my $dir and have a private variable. Private variables are good.
    • You are passing in the directory to read as a global variable named $logSite. Decades of experience backed up by much research says that using global variables like this is poor practice. Perl has a perfectly good way of passing arguments to functions - use it.
    • While you are at it add the directory and the error you got to the error message. You know, like perlstyle tells us to. This makes debugging problems much easier.
    • Your use of foreach $pattern (@ARGV) { strongly suggests that you are not using strict.pm. Using that pragma will catch a lot of your bugs immediately, so it is well worth the little bit of declaration effort to make it work properly.
    • Again using @uploads without declaring it private means that you are using a global variable with all of the resulting ability to accidentally overwrite global data. It also means that if you call your function twice, you will get bad data.
    • If a file matches multiple patterns it will be in your array multiple times. Is that really what you want?
    • There is no need to close the directory if you are not checking for errors. (Theoretically checking for errors there is a good idea. I am willing to be sloppy there though because the odds of an error are so low.)
    • I would return @uploads rather than \@uploads to reduce potential surprise depending on how it is called.
    Here is a rewritten version of your function. I have left your naming convention and indentation style alone because they are reasonable if different from my usual:
    sub getfiles { my ($dirName, @patterns) = @_; opendir(my $dir, $dirName) or die "Can't open '$dirName': $!"; my @return; for my $file (readdir($dir)) { for my $pattern (@ARGV) { if ($file =~ /$pattern/) { push @return, $file; # This avoids having the file in the result twice last; } } } return @return; }
    And then you call it like this:
    my @uploads = getfiles($logSite, @ARGV);
Re: sub functions return array's
by hbm (Hermit) on Mar 02, 2009 at 14:57 UTC

    You are returning a reference to the array, not the array itself. That is a fine thing to do, but then you want to dereference it at the receiving end. Something like this:

    sub getfiles() { opendir DIR,$logSite or die "Unable to open Directory, please try ag +ain"; foreach $pattern (@ARGV) { push (@uploads, grep /$pattern/, readdir(DIR)); } closedir DIR; return \@uploads; } my $aref = &getfiles; print @$aref; # or whatever
Re: sub functions return array's
by Bloodnok (Vicar) on Mar 02, 2009 at 14:55 UTC
    You might want to try declaring @uploads outside the scope of the foreach loop, i.e.
    sub getfiles() { opendir DIR,$logSite or die "Unable to open Directory, please try ag +ain"; my @uploads; foreach $pattern (@ARGV) { push (@uploads, grep /$pattern/, readdir(DIR)); } closedir DIR; return \@uploads; }
    Using strictures (use warnings; use strict;) would've helped enormously.

    A user level that continues to overstate my experience :-))
Re: sub functions return array's
by bellaire (Hermit) on Mar 02, 2009 at 14:57 UTC
    It would be helpful to see the calling syntax, in this case you are passing back an array reference (a scalar), which must be utilized as such by the caller. That is, if you want to use the list you must dereference it.
    my $uploads = getfiles(); for my $uploaded_file (@$uploads) { print "Filename: $uploaded_file\n"; }
    Alternatively you can actually return a list of values (don't use backslash to create a reference before returning):
    ...snip... return @uploads; } my @uploads = getfiles()
Re: sub functions return array's
by cdarke (Prior) on Mar 02, 2009 at 15:01 UTC
    Hard to say without your full code, but a few comments:
    You are using an empty prototype with sub getfiles() - why?
    Where are your variables coming from? use warnings; use strict
    Are you capturing the reference being returned correctly?

    The following works for me:
    #!/usr/bin/perl use strict; use warnings; sub getfiles { my ($logSite) = @_; my @uploads; opendir DIR,$logSite or die "Unable to open Directory, please try ag +ain"; foreach my $pattern (@ARGV) { push (@uploads, grep /$pattern/, readdir(DIR)); } closedir DIR; return \@uploads; } my $ref = getfiles('.'); print "@$ref\n";
Re: sub functions return array's
by CountZero (Bishop) on Mar 02, 2009 at 16:03 UTC
    how to I return an array from a funciton?
    That's easy: you cannot.

    Perl 5 subroutines return flattened lists.

    From the docs (perlsub):

    The Perl model for function call and return values is simple: all functions are passed as parameters one single flat list of scalars, and all functions likewise return to their caller one single flat list of scalars.
    One of these scalars can of course be a reference to an array (as you have done) but I see no really good reason why you should do so. Usually I only return references (note the plural!) when I must distinguish between the return values, e.g. a list of directories and a list of files is returned. Without references they would be indistinguishable. If you only return one list, it is better not to use a reference, which you probably have to de-reference anyway ...

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

Re: sub functions return array's
by jethro (Monsignor) on Mar 02, 2009 at 14:56 UTC

    No, nothing wrong here. It might be better to have a 'my @uploads;' before the loop so that you don't use the global variable, but that shouldn't prevent it from working.

    Please post the code where you access the returned array.

Re: sub functions return array's
by cdarke (Prior) on Mar 02, 2009 at 16:38 UTC
    You changed the original question, which is a bit confusing. Anyway, as most (all?) of the answers to your previous question said, you are returning a reference, which is what the ARRAY(HEX numbers) is. You need to dereference it, for example:
    my $ref = getfiles(); my @uploads = @$ref;
    Alternatively do not return a reference, just return a list. This can be done by ommiting the \:
    return @aref;