Substitution Operator Not Working on Directory Path Strings

H

Hike Mike

I am trying to list all files in a destination path that are newer than
the same file found in a source path (so I can quickly determine which
files have been modified in the destination since I obtained the
source). My strategy is to do a File::Find on the destination
top-level directory and, foreach file found, substitute the top-level
path of the absolute file path with the source path, store this in a
scalar and then compare the modification times of the two absolute
paths.

I think the problem is the s/// operator is not doing the substitution
because the strings used contain '/' string.

listNewerFiles.pl:

__BEGIN__

#!/usr/bin/perl

use strict;
use warnings;

use File::stat ":FIELDS";
use File::Spec::Functions 'catfile';
use File::Find;

die <<ERR unless @ARGV==2;
Supply a source path and destination path.

ERR

my $fromPath = $ARGV[0];
my $toPath = $ARGV[1];

opendir FROMDIR, $fromPath or die "ummmm: $fromPath: $!\n";
closedir FROMDIR or die "Cannot close $fromPath directory: $!";

opendir TODIR, $toPath or die "ummmm: $toPath: $!\n";
closedir TODIR or die "Cannot close $toPath directory: $!";

find (\&search, $fromPath);

sub search {
return unless grep { /\.java$/ } $_;
stat($_) or die "No $_: $!";
my $modFromTime = $st_mtime;
my $newPath = $File::Find::name;
print "fullpath: $newPath\n";
$newPath =~ s{$fromPath}{$toPath};
print "compare with: $newPath\n";
}

__END__

run on windows file system:

U:\perl>.\listNewerFiles.pl D:\projects\bullion-old
D:\projects\bullion-new

__OUTPUT__BEGIN__

fullpath:
D:\projects\bullion-old/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java
compare with:
D:\projects\bullion-old/appClient/src/com/myco/appClient/Trad
er.java

__OUTPUT__END__

I was expecting:
compare with:
D:\projects\bullion-new/appClient/src/com/myco/appClient/Trader.java
 
H

Hike Mike

my original posting contained an error:

__OUTPUT__FIXED__BEGIN__

fullpath:
D:\projects\bullion-old/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java
compare with:
D:\projects\bullion-old/appAuthen/src/com/myco/appClient/Trad
er.java

__OUTPUT__FIXED__END__

I was expecting:
compare with:
D:\projects\bullion-new/appAuthen/src/com/myco/appClient/Trader.java
 
H

Hike Mike

my second posting also contained a typo (embarrassed):

__OUTPUT__FIXED__AGAIN__
fullpath:
D:\projects\bullion-old/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java
compare with:
D:\projects\bullion-old/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java

__OUTPUT__FIXED__AGAIN__END__

I was expecting:
compare with:
D:\projects\bullion-new/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java
 
P

Paul Lalli

Hike said:
I think the problem is the s/// operator is not doing the substitution
because the strings used contain '/' string.

If this were true, it would not be a problem.
U:\perl>.\listNewerFiles.pl D:\projects\bullion-old
D:\projects\bullion-new

It is not true however. The strings you use contain the '\' character,
which *is* a problem.

When using variables in a pattern-match that you want treated as pure
strings, you need to escape all the possible "special" characters the
strings may contain:

s/\Q$foo/$bar/;

for more information:
perldoc -f quotemeta

Paul Lalli
 
H

Hike Mike

s/\Q$foo/$bar/;

this works like a charm, but why do I need the quotemeat function call
only in the first expression and not the second?

If this were true, it would not be a problem.

wouldn't the '/' character be interpreted as part of the operator after
expansion?

how do I tell perl to use '\' as the file separator instead of '/' or
does it not matter?
 
P

Paul Lalli

Hike said:
this works like a charm, but why do I need the quotemeat function call
only in the first expression and not the second?

Because the first part of the s/// is a regular expression. The second
part is a plain string. Therefore, the first part is parsed twice -
first, double-quotish variable interpolation occurs. This expands $foo
to be 'dir\subdir'. Then the result of that iterpolation is passed to
the regular expression engine. In the regexp, \ is a special
character, and needs to be backslashed.
wouldn't the '/' character be interpreted as part of the operator after
expansion?

No. By the time $foo is expanded in the first part, Perl already knows
where the regexp starts and stops. The '/' would just be considered a
literal character in the pattern. You only need to backslash the '/'
when typing it out so that the Perl parser knows where the regexp
starts and stops.

(Which means I guess I lied earlier - there's really three parsing
steps: The Perl parser which says "This is a pattern match
substituting whatever's in $bar for whatever's in $foo"; Second is the
variable interpolation which says "$foo contains 'dir\subdir' and $bar
contains 'newdir\subdir'"; and third is the regular expression engine
which says "'dir\subdir' is a regular expression that matches d, i, r,
whitespace, u, b, d, i, r")
how do I tell perl to use '\' as the file separator instead of '/' or
does it not matter?

Don't understand this question. Perl does not know what a file
separator is. That's dependend on the underlying operating system.
(Your underlying operating system, btw, *does* understand that / can be
a directory separator, even if it's "shell" - cmd.com - does not.)

Paul Lalli
 
H

Hike Mike

Don't understand this question. Perl does not know what a file
separator is. That's dependend on the underlying operating system.
(Your underlying operating system, btw, *does* understand that / can be
a directory separator, even if it's "shell" - cmd.com - does not.)

I was wondering why I see output like:
__

D:\projects\bullion-old/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java
__

the 'D:\projects\bullion-old' part was $ARGV[0] but the
'/appAuthen/src/com/myco/oesapp/authen/Auth' must have come from the
Find module.

I would have thought that the paths used for the the output of Find
would be system dependent.
 
P

Paul Lalli

Hike said:
Don't understand this question. Perl does not know what a file
separator is. That's dependend on the underlying operating system.
(Your underlying operating system, btw, *does* understand that / can be
a directory separator, even if it's "shell" - cmd.com - does not.)

I was wondering why I see output like:
__

D:\projects\bullion-old/appAuthen/src/com/myco/oesapp/authen/Auth
enPowers.java
__

the 'D:\projects\bullion-old' part was $ARGV[0] but the
'/appAuthen/src/com/myco/oesapp/authen/Auth' must have come from the
Find module.

Ah, I see. Thank you for clarifying. Basically, you want a way to
make $File::Find::name use '\' as the directory separator. I don't see
any particular way to make that happen in the docs.
I would have thought that the paths used for the the output of Find
would be system dependent.

I'm willing to bet it is. Again, '/' is a perfectly valid directory
separator even on the Win32 platform. It is only the broken command
line shell command.com or cmd.exe or whatever that doesn't understand
'/'. To see what I mean, try entering your arguments using the /
separator instead of \. The program will work just as well, you will
have avoided the original problem entirely, and the output will look
less bizarre.

Paul Lalli
 
H

Hike Mike

To see what I mean, try entering your arguments using the /
separator instead of \. The program will work just as well, you will
have avoided the original problem entirely, and the output will look
less bizarre.

yes...and it works much better when passed to a pipe or other command
as well :)
 
J

John W. Krahn

Hike said:
I am trying to list all files in a destination path that are newer than
the same file found in a source path (so I can quickly determine which
files have been modified in the destination since I obtained the
source). My strategy is to do a File::Find on the destination
top-level directory and, foreach file found, substitute the top-level
path of the absolute file path with the source path, store this in a
scalar and then compare the modification times of the two absolute
paths.

I think the problem is the s/// operator is not doing the substitution
because the strings used contain '/' string.

listNewerFiles.pl:

I see that Paul has answered most of your questions but ...

#!/usr/bin/perl

use strict;
use warnings;

use File::stat ":FIELDS";
use File::Spec::Functions 'catfile';
use File::Find;

die <<ERR unless @ARGV==2;
Supply a source path and destination path.

ERR

my $fromPath = $ARGV[0];
my $toPath = $ARGV[1];

opendir FROMDIR, $fromPath or die "ummmm: $fromPath: $!\n";
closedir FROMDIR or die "Cannot close $fromPath directory: $!";

opendir TODIR, $toPath or die "ummmm: $toPath: $!\n";
closedir TODIR or die "Cannot close $toPath directory: $!";

No need to use opendir() to test for directory existence:

-d $fromPath or die "ummmm: $fromPath: $!\n";
-d $toPath or die "ummmm: $toPath: $!\n";

find (\&search, $fromPath);

sub search {
return unless grep { /\.java$/ } $_;

You don't need to use a list operator for a boolean test on a scalar.

return unless /\.java$/;


John
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,239
Members
46,827
Latest member
DMUK_Beginner

Latest Threads

Top