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

Dear Most Intelligent Monks,

Please have patience on this confused novice with such a basic question. I am probably mixing up my punctuation and syntax to be honest.

I have chatted with many and read many nodes in my quest for the recursive directory copy all the .TXT (text files) to a single target i.e. a Perl program that goes into every subdirectory and copies all the .TXT files into a single target directory. There are no non-unique files (no duplicates). This is going to be run in MS-DOS so I have to follow the proper nomenclature. I have looked at http://search.cpan.org/~dmuey/File-Copy-Recursive-0.38/Recursive.pm and unfortunately it copies the entire directory structure to the target directory when actually I just want the files and not the source structure.

So after going through various implementations based upon PerlMonk nodes and on the web I came up with the following

#!/usr/local/bin/perl -w use strict; use File::Find; use File::Copy; my $sourcedirectory = $ARGV[0]; my $targetdirectory = $ARGV[1]; copyfiles(); sub copyfiles() { #the $sourcedirectory below tells the find where to start #looking for files. The .TXT tells it only to return the files #with the path if the file is a .TXT file. It then uses the copy #of File::Copy to copy the entire path\filename to a SINGLE #directory target. find(sub { copy ("{ win_path{$File::Find::name$/" if(/\.TXT$/)},"$sourcedirectory") } ,"$targetdirectory"); } } # ruzam figured out the following line on # how to convert the resulting string to MS-DOS complaint format sub win_path { (my $path = shift) =~ s![\\/]+!\\!g; return $path; }

So it should be the same as if I had the following on the c: drive:

c:\20090127\A\01.TXT c:\20090127\A\02.TXT c:\20090127\B\03.TXT c:\20090127\B\04.TXT c:\20090127\B\05.TXT c:\20090127\C\06.TXT

Running the perl program from the command prompt:

C:\>perl program.pl c:\20090127\ C:\targetdirectory\

Would result in the following to be found in the directory in c:\targetdirectory\

c:\targetdirectory\01.TXT c:\targetdirectory\02.TXT c:\targetdirectory\03.TXT c:\targetdirectory\04.TXT c:\targetdirectory\05.TXT c:\targetdirectory\06.TXT

Thank you for you patience for my very long winded question!!! I do appreciate it very very much!!!

Replies are listed 'Best First'.
Re: Recursive Directory Copying to Single Target Directory
by ysth (Canon) on Jan 28, 2009 at 06:23 UTC
    What was the question? Your approach seems right, but your code looks a little mangled around the copy() call. Make sure the stuff inside the sub { ... } is what you want to happen for each file found and the arguments to find are correct (compare to examples in File::Find, with your sub { ... } in place of \&wanted).
      The problem is that my program does not work. I took you advice and cleaned it as shown below:
      #!/usr/local/bin/perl -w use strict; use File::Find; use File::Copy; my $sourcedirectory = $ARGV[0]; my $targetdirectory = $ARGV[1]; copyfiles(); sub copyfiles() { #the $sourcedirectory below tells the find where to start #looking for files. The .TXT tells it only to return the files #with the path if the file is a .TXT file. It then uses the copy #of File::Copy to copy the entire path\filename to a SINGLE #directory target. find(sub { copy(({win_path{$File::Find::name$/"if(/\.TXT$/)}", "$sourcedirectory"),"$targetdirectory"); } ) } # ruzam figured out the following line on # how to convert the resulting string to MS-DOS complaint format sub win_path { (my $path = shift) =~ s![\\/]+!\\!g; return $path; }

      But I get the following error during when I try to run it:

      Scalar found where operator expected at notworking.pl line 21, near "$ +File::Find ::name$/" (Missing operator before $/?) String found where operator expected at notworking.pl line 21, near "$ +/"if(/\.TX T$/)}"" (Missing operator before "if(/\.TXT$/)}"?) syntax error at notworking.pl line 21, near "$File::Find::name$/" syntax error at notworking.pl line 23, near ") " Illegal declaration of subroutine main::win_path at notworking.pl line + 27.
        That stuff in there is still nothing that makes any sense. You're going to need to decide what has to happen and write the code to do it, not just throw in stuff until it stops complaining.

        A few hints: the directory to look in should be passed to find(), not copy(). copy() should be passed a filename (presumably returned from win_path) and the target directory. You'll need an if statement around the copy() call to make it only called for .TXT files.

Re: Recursive Directory Copying to Single Target Directory
by jwkrahn (Abbot) on Jan 28, 2009 at 07:06 UTC

    You probably want something like this (UNTESTED)

    #!/usr/local/bin/perl use warnings; use strict; use File::Find; use File::Copy; @ARGV == 2 or die "Usage: $0 sourcedirectory targetdirectory\n"; my ( $sourcedirectory, $targetdirectory ) = @ARGV; find sub { return unless -f && /\.txt\z/i; copy( $_, "$targetdirectory/$_" ); }, $sourcedirectory;
Re: Recursive Directory Copying to Single Target Directory
by Bloodnok (Vicar) on Jan 28, 2009 at 09:13 UTC
    It strikes me that, assuming an invocation of perl <script> <target directory> <source directory>..., the following may be something along the lines you require (i.e. untested)...
    use warnings; use strict; use File::Find; use File::Copy; use autodie qw/File::Copy::copy/; my $tgt_dir = shift; find( sub { return unless -f $_; copy($File::Find::name, qq($tgt_dir/$_)); }, @ARGV )

    A user level that continues to overstate my experience :-))
Re: Recursive Directory Copying to Single Target Directory
by dHarry (Abbot) on Jan 28, 2009 at 08:15 UTC

    I suggest to check the return value of copy as well. For example what if the disk is full? From the File::Copy documentation:

    All functions return 1 on success, 0 on failure. $! will be set if an error was encountered.

    BTW Are you sure the the file names are unique across all directories?

Re: Recursive Directory Copying to Single Target Directory
by matze77 (Friar) on Jan 28, 2009 at 08:46 UTC

    Hi i must admit honestly that i dont understand your code completeley. But if i understand your problem right you want to:?

    Create a list of directories (down the "directory-tree) starting from $sourcedir.
    Store this list in an array e.g..
    Loop (foreach array element e.g.):

    {
    Descent into every dir and copy *txt to $targetdir
    }

    (Special treatment required for "." and ".." directories (the current and the dir nearer to the "root-dir" these must be removed from the list)
    Thanks in Advance
    MH

Re: Recursive Directory Copying to Single Target Directory
by zentara (Cardinal) on Jan 28, 2009 at 15:40 UTC
    Another neat option, File::NCopy
    #!/usr/bin/perl use File::NCopy; $file = File::NCopy->new(recursive => 1); $file->copy($dir1, $dir2); # Copy $dir1 to $dir2 recursively # or if the dir is big and you would rather fork # (cd $srcdir; tar cf - *) | (cd $dstdir; tar xf -) #or even better # cd /source/dir # find . -print | cpio -pd /dest/dir

    I'm not really a human, but I play one on earth Remember How Lucky You Are