How can I optimize Perl code that checks for directory existence? - perl

sub DirectoryExists {
my $param = shift;
# Remove first element of the array
shift #{$param};
# Loop through each directory to see if it exists
foreach my $directory (#{$param}) {
unless (-e $directory && -d $directory) {
return 0;
}
}
# True
return 1;
}
Is there any way to optimize this code?
Is there any good way to optimize this code?

That algorithm is pretty efficient, because it stops at the first item but you might want to give List::Util::first a try.
use List::Util qw<first>;
#...
return defined first { -e && -d } #$param;
The only major optimization would be that it runs in the C-layer. It's also a pretty recognizable idiom in Perl, and so despite the golf look, the purpose is to "speak perl", not to golf.
List::MoreUtils::any would give you a similar effect and as well, it's a better fit to what you're trying to express: you're asking if any in the array are directories. (a hint though, stack parameter passing is slightly to significantly faster than constructing a reference and passing it--at least in my tests.)
Anyway, here's what it looks like:
return any { -e && -d } #$param;
Means to return true if any satisfy that expression. any often runs in the C-layer, if the module could load its XS version. Otherwise it's "Pure Perl" and probably runs similar to yours.
However, I'm pretty sure you don't have to test for both existence and directory. I'm pretty sure that if the file does not exist, it's not going to be seen as a directory. So, you could collapse it to one condition.

I would write that code as:
sub all_directories_exist {
my $param = shift;
# Remove first element of the array
shift #{$param};
for my $dir ( #{ $param } ) {
return unless -e $directory;
return unless -d _;
}
return 1;
}
I am guessing —although I haven't benchmarked it— one cannot get much faster than that.
Two points:
Do NOT return 0 to indicate failure. You will be surprised if your sub is called in list context.
Are you sure you want to modify the array pointed to by $param?

Related

Odd use of False constant in if-then statement

Python is my main language, but have to maintain a rather large legacy Perl codebase.
I have an odd logic statement that I can't make heads or tails over.
At top, a constant is defined as:
use constant FALSE => 0;
sub thisFunc {
FALSE if ($self->{_thisVar} ne "tif");
...
...
return statement,etc..
}
So I'm reading that as a kinda' fancy, non-standard if-then statement,
that if $thisVar string is not equal to "tif", then FALSE. Huh?
Not something like $that = FALSE, just FALSE.
The form of this statement appears in the file several times.
This codebase is in use, and vetted over the years by very good team,
so I think it is valid and has meaning. "use strict;" is set at top.
Could someone be so kind as to explain what is meant by logic.
I've Google'd it but no joy.
Thanks in advance,
"if" logic in Perl can be constructed in couple of ways:
the obvious one:
if ($flag) { do_something() }
less obvious one:
do_something() if ($flag);
This example shows how exactly behaves that odd "FALSE if" statement - which only meaning is found when it is LAST statement in subroutine:
use strict;
use constant FALSE => 0;
sub thisFunc {
my $arg = shift;
FALSE if ($arg ne "tif");
}
print "return val: ".thisFunc("ble")."\n";
print "return val: ".thisFunc("tif")."\n";
output from running above is:
return val: 0
return val:
It is pointless. I suspect it's suppose to be
return FALSE if $self->{_thisVar} ne "tif";
There is a similar construct that isn't pointless. If the loop condition has side-effects, the following isn't pointless:
1 while some_sub();
aka
while (some_sub()) { }
aka
while (1) {
some_sub()
or last;
}
Practical example:
$ perl -E'$_ = "xaabbx"; 1 while s/ab//; say'
xx

is there a return value to file::find in perl? How to tell if the find fails?

I'm trying to learn perl and in particular use the File::Find module to search through a directory tree of pictures to see if any filenames already existing match those on a camera. If the filename is there, I'll assume the file has already been transferred from this camera and won't process it any further. If the filename is not found, I'd like to take some sort of action on the file.
I've used find2perl to create a basic structure and it works for finding the file. But I can't seem to find a way to tell if the find failed. The File::Find::find doesn't seem to return any value to act upon, and I'm not sure how to act upon, or use any return value from the '&wanted' subdirectory that it's using.
What is the optimal method of determining if the File::Find::find was not successful in finding any matching files? Should I use a global flag variable that is set to a certain value at the top of the program and is only changed if the find is successful? I guess I could check that value after the find to see if it has changed (success) or not (nothing found).
Any ideas or suggestions?
Here's the basic structure:
# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, '/files/multimedia/pictures/');
exit;
sub wanted {
my ($dev,$ino,$mode,$nlink,$uid,$gid);
if ( (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && /^$ARGV[0]\z/si ) {
print("found: $name\n");
}
}
If no desired files are found, the wanted subroutine will not be called.
What I usually do is exactly what you suggested - set a flag to false before calling find, and have wanted set it to true.
That's in cases where I actually need to know that information - I haven't actually needed it for quite a while.
In fact, a count may be better than a flag since it delivers more information. The following code:
use File::Find;
sub wanted {
my ($dev,$ino,$mode,$nlink,$uid,$gid);
$quant += 1;
if ( (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && /^$ARGV[0]\z/si ) {
print("found: $name\n");
}
}
$quant = 0;
File::Find::find({wanted => \&wanted}, '/nosuchdir');
print ("Found $quant files in /nosuchdir\n");
$quant = 0;
File::Find::find({wanted => \&wanted}, '/tmp');
print ("Found $quant files in /tmp\n");
generates the following output on my system:
Found 0 files in /nosuchdir
Found 39 files in /tmp
That way, a $count of zero means that no files were found, anything else tells you that there were files (and also tells you how many).

How can I test if a filename matching a pattern exists in Perl?

Can I do something like this in Perl? Meaning pattern match on a file name and check whether it exists.
if(-e "*.file")
{
#Do something
}
I know the longer solution of asking system to list the files present; read it as a file and then infer whether file exists or not.
You can use glob to return an array of all files matching the pattern:
#files = glob("*.file");
foreach (#files) {
# do something
}
If you simply want to know whether a file matching the pattern exists, you can skip the assignment:
if (glob("*.file")) {
# At least one file matches "*.file"
}
On Windows I had to use File::Glob::Windows as the Windows path separating backslashes don't seem to work perl's glob.
On *nix systems, I've used the following with good results.
sub filesExist { return scalar ( my #x = `ls -1a 2> /dev/null "$_[0]"` ) }
It replies with the number of matches found, or 0 if none. Making it easily used in 'if' conditionals like:
if( !filesExist( "/foo/var/not*there.log" ) &&
!filesExist( "/foo/var/*/*.log" ) &&
!filesExist( "/foo/?ar/notthereeither.log" ) )
{
print "No matches!\n";
} else {
print "Matches found!\n";
}
Exactly what patterns you could use would be determined by what your shell supports. But most shells support the use of '*' and '?' - and they mean the same thing everywhere I've seen. Of course, if you removed the call to the 'scalar' function, it would return the matches - useful for finding those variable file names.

How do I run code only after Perl's Find::Find finishes?

Perl question for you:
#!/usr/bin/perl
use File::Find;
#Find files
find(\&wanted, $dir);
sub wanted { #Do something }
#Done going through all files, do below:
other stuff { }
So, I basically want to parse a directory and find certain kinds of files. I can do that successfully with File::Find. However, my next step is, once I'm done searching the files, I want to do my next process.
The problem is , whatever, I put after the sub wanted { #Do something } , gets executed, everytime, the file I want is not found! I know this is only logical for the program to do that. But, could you tell me what I'd need to do to accomplish this:
1] Find files : using > sub wanted { #Do something }
2] While no more files to search : >do something else { }
Thanks!
UPDATED ANSWER: (after OP clarified that he simply wants to run some code after find() finishes searching):
Since find() is not searching in parallel, it will simply return when all of the search is completed. Therefore you don't need to do ANYTHING special to achieve your goal:
find(\&wanted, #directories_to_search);
# Here be code that runs after search completes.
ORIGINAL ANSWER
You can set founding of files flag in wanted subroutine:
my $files_not_found = 1;
find(\&wanted, #directories_to_search);
sub wanted { #args=#_; $files_not_found = 0; }
if ($files_not_found) {
print "No files found!\n";
}
# Here you do things after find is finished.

How can I cleanly handle error checking in Perl?

I have a Perl routine that manages error checking. There are about 10 different checks and some are nested, based on prior success. These are typically not exceptional cases where I would need to croak/die. Also, once an error occurs, there's no point in running through the rest of the checks.
However, I can't seem to think of a neat way to solve this issue except by using something analogous to the following horrid hack:
sub lots_of_checks
{
if(failcond)
{
goto failstate:
}
elsif(failcond2)
{
goto failstate;
}
#This continues on and on until...
return 1; #O happy day!
failstate:
return 0; #Dead...
}
What I would prefer to be able to do would be something like so:
do
{
if(failcond)
{
last;
}
#...
};
An empty return statement is a better way of returning false from a Perl sub than returning 0. The latter value will actually be true in list context:
sub lots_of_checks {
return if fail_condition_1;
return if fail_condition_2;
# ...
return 1;
}
Perhaps you want to have a look at the following articles about exception handling in perl5:
perl.com: Object Oriented Exception Handling in Perl
perlfoundation.com: Exception Handling in Perl
You absolutely can do what you prefer.
Check: {
last Check
if failcond1;
last Check
if failcond2;
success();
}
Why would you not use exceptions? Any case where the normal flow of the code should not be followed is an exception. Using "return" or "goto" is really the same thing, just more "not what you want".
(What you really want are continuations, which "return", "goto", "last", and "throw" are all special cases of. While Perl does not have full continuations, we do have escape continuations; see http://metacpan.org/pod/Continuation::Escape)
In your code example, you write:
do
{
if(failcond)
{
last;
}
#...
};
This is probably the same as:
eval {
if(failcond){
die 'failcond';
}
}
If you want to be tricky and ignore other exceptions:
my $magic = [];
eval {
if(failcond){
die $magic;
}
}
if ($# != $magic) {
die; # rethrow
}
Or, you can use the Continuation::Escape module mentioned above. But
there is no reason to ignore exceptions; it is perfectly acceptable
to use them this way.
Given your example, I'd write it this way:
sub lots_of_checks {
local $_ = shift; # You can use 'my' here in 5.10+
return if /condition1/;
return if /condition2/;
# etc.
return 1;
}
Note the bare return instead of return 0. This is usually better because it respects context; the value will be undef in scalar context and () (the empty list) in list context.
If you want to hold to a single-exit point (which is slightly un-Perlish), you can do it without resorting to goto. As the documentation for last states:
... a block by itself is semantically identical to a loop that executes once.
Thus "last" can be used to effect an early exit out of such a block.
sub lots_of_checks {
local $_ = shift;
my $all_clear;
{
last if /condition1/;
last if /condition2/;
# ...
$all_clear = 1; # only set if all checks pass
}
return unless $all_clear;
return 1;
}
If you want to keep your single in/single out structure, you can modify the other suggestions slightly to get:
sub lots_of_checks
{
goto failstate if failcond1;
goto failstate if failcond2;
# This continues on and on until...
return 1; # O happy day!
failstate:
# Any clean up code here.
return; # Dead...
}
IMO, Perl's use of the statement modifier form "return if EXPR" makes guard clauses more readable than they are in C. When you first see the line, you know that you have a guard clause. This feature is often denigrated, but in this case I am quite fond of it.
Using the goto with the statement modifier retains the clarity, and reduces clutter, while it preserves your single exit code style. I've used this form when I had complex clean up to do after failing validation for a routine.