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

Hi, Its been about 3 years since I wrote Perl code and I have seem to forgot the little that I knew. I wrote something to search 2 directories and compare the files and copy any files that exist in the other. It works but not like how I want it to. For some reason it copies all the files every time when it is suppose to only copy the files directory 2 does not have. Im assuming it has something to do with the map function. I am using ActivePerl for my development. The code is below, any help would be greatly appreciated. Thanks in advance!
use Class::Date qw(:errors date localdate gmdate now); # Check system date for filename $date = localdate(time); $date->monname; $date->yr; $month = substr($date->monname,0,3); $file_name = $month . $date->yr . "BU"; # Opens log to write to open (MYFILE, '>>c:\\Perl scripts\\TEST1.log'); # Opens source directory and compares all files to all the files - If + new files, it copies it to 2nd location opendir (DIR1, "C:\\TEST1") or die print MYFILE "Today is $date \n Cou +ldn't open directory, C:\\TEST1"; while ($file1 = readdir DIR1) { #print "$file1 \n"; push(@files1, $file1); } #print "@files1 \n"; closedir DIR1; #opendir (DIR2, "C:\\TEST2") or die print MYFILE "Today is $date \n Co +uldn't open directory, C:\\TEST2"; while ($file2 = readdir DIR2) { #print "$file2 \n"; push(@files2, $file2); } #print "@files2 \n"; closedir DIR2; #Compares the directories and copies the missing files to the other di +rectory %temp = map {$_,$_} @files2; for(@files1) { next if exists $temp{$_}; system("copy C:\\TEST1\\$_ C:\\TEST2\\"); print MYFILE "$_ has been copied\n"; } close(MYFILE);

Replies are listed 'Best First'.
Re: Comparing Directories and copy files
by toolic (Bishop) on Jul 27, 2010 at 21:34 UTC
    The following should check for files which only exist in dir1 and copy them to dir2 (UNTESTED):
    use strict; use warnings; use File::Slurp qw(read_dir); use File::Copy; my $dir1 = 'C:\TEST1'; my $dir2 = 'C:\TEST2'; my %files1 = map { $_ => 1 } grep { -f "$dir1/$_" } read_dir($dir1); opendir my $dh, $dir2 or die "can not open $dir2: $!"; while (my $entry = readdir $dh) { next unless -f "$dir2/$entry"; copy("$dir1/$entry" => "$dir2/$entry") unless exists $files1{$entr +y}; } closedir $dh;
      I dont use File:Copy because I do not believe that ActivePerl supports that module.
        I dont use File:Copy because I do not believe that ActivePerl supports that module.
        Firstly, File::Copy is a core module, which means that it should be included in every properly installed Perl distribution. Secondly, I use ActiveState (5.8.8), and it is installed for me. What happens when you try this at your command prompt?
        perldoc File::Copy

        Use system instead, if you want to.

Re: Comparing Directories and copy files
by dasgar (Priest) on Jul 27, 2010 at 23:34 UTC

    Based on your description of what you're wanting to do, it looks like there are 2 potential problems that you might not have encountered yet.

    First, the combination of opendir and readdir will list all contents of a directory, including ".", "..", and any subdirectories. Unless I missed it somewhere, your code is not excluding directories.

    The second potential problem is the scenario of a file name that has a blank space in it. In that case, you would need to encapsulate the file's path within double-quotes in order for the DOS copy command to work. The same would be true if either directory had a blank space somewhere in the full path.

    The code below should do what you want while avoiding the issues above. I've included comments to make it easier to follow what the code is doing. Also, note that I'm using backticks to submit DOS commands and store the output of those commands in a variable. (It's easy to confuse the backtick (`) and the single quote ('), which I have done in the past. :D)

    use strict; use File::Copy; # Setup source and target directories my $source_dir = "c:\\perl_stuff\\appendix"; my $target_dir = "c:\\perl_stuff\\test"; # Setup dir commands my $cmd1 = "dir \/b \/a:-d ".$source_dir; my $cmd2 = "dir \/b \/a:-d ".$target_dir; # Get file list from dir command for source directory my $list1 = `$cmd1`; # Strip out blank lines $list1 =~ s/^\s*$//gm; # Get file list from dir command for target directory my $list2 = `$cmd2`; # Strip out blank lines $list2 =~ s/^\s*$//gm; # Dump the dir command outputs into arrays (my @files1) = (split /\n/,$list1); (my @files2) = (split /\n/,$list2); # Loop through array of file lists from source directory foreach my $file1 (@files1) { # If current file is not in target directory, copy it if (!(grep $_ eq $file1, @files2)) { # Setup variables will full path for the file to be copied my $file2 = $target_dir."\\".$file1; $file1 = $source_dir."\\".$file1; # Do the copy copy($file1,$file2) || die "Unable to copy '$file1' to '$file2 +': $!\n"; } }

    Others may have more efficient methods for doing this, but this would be my approach. If you'd prefer to avoid the DOS dir command that I used, you can check out Win32API::File and it's GetFileAttributes function to exclude directories in obtaining file lists of the directories.

    Hope this helps!

Re: Comparing Directories and copy files
by NiJo (Friar) on Jul 28, 2010 at 17:59 UTC
    You are reinventing the (cw)rsync wheel.