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

Hi Monks,

There's nothing fun about having to come in front of people and announce abject failure. Everything I'm to post here will be garbage that didn't work. I wasn't intending to do something grandiose, simply trying to use Path::Tiny and its methods to copy files and directories to a place where I will archive them. But how does a person produce self-contained code and data for others to see, when the message always seems to be "no such file or directory"? How can the diagnostic luminaries at the monastery know what does or does not exist on my machine when I can't seem to be sure which end of my copy method calls are not working?

At first, I was trying to work on it using the tempdir ideas at dagolden 2013 path-tiny feature. After all, I didn't need to have the copied files after a compressed archive exists. I made a manifest of the files I wanted copied from the "grandfather directory," using ls >1.manifest, and then winnowed the list down to files and directories I want. (I have no appropriate code for the directories yet, but a file is the first item on the list, and I have yet to get it copied once.)

The caller uses a library wherein it finds utils2.pm, where I have 3 archive functions. They all result in errors. I uncover the hashmarks to adjust the callee:

#!/usr/bin/perl -w use 5.011; use lib "template_stuff"; use utils2; #my $return1 = archive1(); #say "return is $return1"; #my $return2 = archive2(); #say "return is $return2"; my $return3 = archive3(); say "return is $return3"; __END__

This is the output from the call to archive1():

$ ./3.archive.pl path1 is /home/bob/1.scripts/pages/1.qy parent2 is /home/bob/1.scripts/pages files are 2.create.bash 11.clone.pl 1.initialize.pl 1.manifest 1.med 1.meditation 1.pop 1.qy 1.rings 2.med 5.create.sh 5.unicode temp dir is /tmp/backup_2elt_b read me is /tmp/backup_2elt_b/grandfather/README.txt copy failed for /home/bob/1.scripts/pages/2.create.bash to /tmp/backup_2elt_b/grandfather: No such file or directory at templ +ate_stuff/utils2.pm line 32. cannot remove path when cwd is /tmp/backup_2elt_b/grandfather for /tmp +/backup_2elt_b: at /usr/share/perl/5.26/File/Temp.pm line 1583. $

copy failed for /home/bob/1.scripts/pages/2.create.bash to /tmp/backup_2elt_b/grandfather: No such file or directory at template_stuff/utils2.pm line 32.<

line 32 is the one with the copy command:

sub archive1 { use warnings; use 5.011; use Path::Tiny; my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $parent1 = $path1->parent(); say "parent2 is $parent1"; my $file1 = "1.manifest"; my $from = path( $parent1, $file1 ); my @files = $from->lines_utf8; say "files are @files"; my $tempdir = Path::Tiny->tempdir('backup_XXXXXX'); say "temp dir is $tempdir"; my $readme = $tempdir->child( 'grandfather', 'README.txt' )->touchpa +th; say "read me is $readme"; my $grand_dir = $readme->parent; chdir $grand_dir; foreach my $file (@files) { #syntax is from -> to my $return = path( $parent1, $file )->copy( $grand_dir, $file ); if ( $file =~ m/\.(pl|sh)$/ ) { $return->chmod(0755); } say "return is $return"; } system("cd $grand_dir"); system("ls -l"); my $b = "dummy"; return $b; }

The LHS of the copy command looks solid to me, as it could find the manifest in that directory and list its contents. So the RHS must* be the problem, and I wondered if the /tmp directory had disappeared before the file copy could happen. No sign of the directory created with the touchpath command existed after the program was finished with execution.

*true only if my logic is solid, which it manifestly isn't

So I try a version that does not rely on a tempdir and we come to archive2(). I had only recently created space on my machine for this html template to hold its .ini files, so I thought I could use it for archiving at least until I get myself out of this jam. This place exists already:

$ pwd /home/bob/Documents/html_template_data $ ls 3.values.ini example3.ini $

Abridged output and then source:

$ ./3.archive.pl path1 is /home/bob/1.scripts/pages/1.qy parent1 is /home/bob/1.scripts/pages files are 2.create.bash 11.clone.pl 1.initialize.pl 1.manifest 1.med 1.meditation 1.pop 1.qy 1.rings 2.med 5.create.sh 5.unicode grand dir is /home/bob/Documents/html_template_data $VAR1 = 1; -----from /home/bob/1.scripts/pages ... 2.create.bash clone1.pl fork UserAgentWithS +tats.pm ... path 3 is /home/bob/1.scripts/pages/2.create.bash $VAR1 = 1; -----to /home/bob/Documents/html_template_data 3.values.ini example3.ini path 4 is /home/bob/Documents/html_template_data/2.create.bash copy failed for /home/bob/1.scripts/pages/2.create.bash to /home/bob/Documents/html_template_data/2.create.bash : No such file or directory at template_stuff/utils2.pm line 80. $

I make the paths as explicit as I know how to in the ultimate version, whose output and source are within readmore tags. I include the entire .pm here.

$ ./3.archive.pl path1 is /home/bob/1.scripts/pages/1.qy parent1 is /home/bob/1.scripts/pages files are 2.create.bash 11.clone.pl 1.initialize.pl 1.manifest 1.med 1.meditation 1.pop 1.qy 1.rings 2.med 5.create.sh 5.unicode grand dir is /home/bob/Documents/html_template_data path 3 is /home/bob/1.scripts/pages/2.create.bash path 4 is /home/bob/Documents/html_template_data/2.create.bash copy failed for /home/bob/1.scripts/pages/2.create.bash to /home/bob/Documents/html_template_data/2.create.bash : No such file or directory at template_stuff/utils2.pm line 129. $ $ cat utils2.pm package utils2; require Exporter; use utils1; our @ISA = qw(Exporter); our @EXPORT = qw( archive1 archive2 archive3); sub archive1 { use warnings; use 5.011; use Path::Tiny; my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $parent1 = $path1->parent(); say "parent2 is $parent1"; my $file1 = "1.manifest"; my $from = path( $parent1, $file1 ); my @files = $from->lines_utf8; say "files are @files"; my $tempdir = Path::Tiny->tempdir('backup_XXXXXX'); say "temp dir is $tempdir"; my $readme = $tempdir->child( 'grandfather', 'README.txt' )->touchpa +th; say "read me is $readme"; my $grand_dir = $readme->parent; chdir $grand_dir; foreach my $file (@files) { #syntax is from -> to my $return = path( $parent1, $file )->copy( $grand_dir, $file ); if ( $file =~ m/\.(pl|sh)$/ ) { $return->chmod(0755); } say "return is $return"; } system("cd $grand_dir"); system("ls -l"); my $b = "dummy"; return $b; } sub archive2 { use warnings; use 5.011; use Path::Tiny; use Data::Dumper; my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $parent1 = $path1->parent(); say "parent1 is $parent1"; my $file1 = "1.manifest"; my $from = path( $parent1, $file1 ); my @files = $from->lines_utf8; say "files are @files"; my $grand_dir = "/home/bob/Documents/html_template_data"; say "grand dir is $grand_dir"; foreach my $file (@files) { #syntax is from -> to my $path3 = path($parent1, $file); my $return6 = chdir $parent1 or warn "why is this not here?"; say Dumper $return6; say "-----from"; system("pwd"); system("ls"); say "path 3 is $path3"; my $return5 = chdir $grand_dir or warn "why is this not here?"; say Dumper $return5; say "-----to"; system("pwd"); system("ls"); my $path4 = path($grand_dir, $file ); say "path 4 is $path4"; my $return = path( $path3 )->copy( $path4); say Dumper $return; if ( $file =~ m/\.(pl|sh)$/ ) { $return->chmod(0755); } say "return is $return"; } system("cd $grand_dir"); system("ls -l"); my $b = "dummy"; return $b; } sub archive3 { use warnings; use 5.011; use Path::Tiny; use Data::Dumper; my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $parent1 = $path1->parent(); say "parent1 is $parent1"; my $file1 = "1.manifest"; my $from = path( $parent1, $file1 ); my @files = $from->lines_utf8; say "files are @files"; my $grand_dir = "/home/bob/Documents/html_template_data"; say "grand dir is $grand_dir"; #system ("cd $parent1\/"); # system ("pwd"); foreach my $file (@files) { #syntax is from -> to my $path3 = path($parent1, $file); say "path 3 is $path3"; #my $path5 = path($path3)->assert( sub { $path3->exists } ); #say "path 5 is $path5"; my $path4 = path($grand_dir, $file ); say "path 4 is $path4"; my $return = path( $path3 )->copy( $path4); say Dumper $return; if ( $file =~ m/\.(pl|sh)$/ ) { $return->chmod(0755); } } my $b = "dummy"; return $b; } 1; $

My first Big question is how do I write these copy commands correctly? The only thing that all of these crummy functions have in common is me.

Also, when I sent the returned value to Data::Dumper, I got "1" instead of a hash. How do I pick apart the error message properly?

EXCEPTION HANDLING Simple usage errors will generally croak. Failures of underlying Perl +functions will be thrown as exceptions in the class Path::Tiny::Error +. A Path::Tiny::Error object will be a hash reference with the following + fields: op a description of the operation, usually function call and any + extra info file the file or directory relating to the error err hold $! at the time the error was thrown msg a string combining the above data and a Carp-like short stac +k trace Exception objects will stringify as the msg field.

Finally, is this anywhere near how the assert feature of Path::Tiny is to be used?

my $path3 = path($parent1, $file); say "path 3 is $path3"; #my $path5 = path($path3)->assert( sub { $path3->exists } ); #say "path 5 is $path5";

Why is it so much harder to copy files in perl than bash? Thank you for your comments.

Replies are listed 'Best First'.
Re: creating valid paths for Path::Tiny
by soonix (Abbot) on Aug 14, 2018 at 05:28 UTC
Re: creating valid paths for Path::Tiny
by Lotus1 (Vicar) on Aug 14, 2018 at 14:12 UTC

    In addition to what soonix posted about chomping newlines a recommendation for troubleshooting and general validation is to use file tests to check for the existence of files. They can save you a lot of hair pulling in this situation.

    foreach my $file (@files) { if( -f $file) { print "<$file> is a plain file.\n"; } else { print "<$file> is not a plain file.\n"; } }

    This can be shortened with the conditional operator:

    use strict; use warnings; my @files = qw( file1 test.pl.txt file3 4); push @files, "file5 "; foreach my $file (@files){ print "<$file> is ", -f $file ? '' : 'not ', "a plain file.\n"; }

    I encourage you to consider using strict also.

      Because I like Path::Tiny, I wanted to add that it does include methods for these tests, too. Then I noticed that the author himself says
      Use -f instead if you really mean to check for a plain file.

      Another ++ I'd give for your example of enclosing the file name in visible delimiters like "<$file>".

      However, use 5.011 implies strict (although use states 5.012 as the first with this property) so OP already is "strict-compliant" ;-)

        Path::Tiny seems like a really useful module. Thanks for pointing out that it includes file tests.

        Your observation about 'use' seems like it would be a good bug report for Perldocs.

      Thanks all for comments. I'm getting decent partial results, with a manifest that got a newline in it as other processes added filenames to it. Instead of editing out the blank line, I tried to code around it.

      $ cat 1.manifest 2.create.bash 11.clone.pl 1.initialize.pl 1.manifest 1.med 1.meditation 1.pop 1.qy 1.rings 2.med 5.create.sh 5.unicode 3.create.bash $

      Caller is getting more complicated, and I send a reference of the main data hash to archive1() to embellish on:

      $ cat 4.archive.pl #!/usr/bin/perl -w use 5.011; use lib "template_stuff"; use utils2; use Path::Tiny; use utf8; use open qw/:std :utf8/; use Data::Dumper; # initializations that must precede main data structure my $fspecfile = File::Spec->rel2abs(__FILE__); ### does this^^^ have a Path::Tiny equivalent? ## turning things to Path::Tiny my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $title = $path1->basename; say "base is $title"; # script parameters my %vars = ( init_dir => $path1, title => $title, script_file => $fspecfile, ); # caller my $rvars = \%vars; my $return1 = archive1( $rvars ); # returns created dir chdir( $return1 ); system("pwd"); system("find ."); __END__ $

      I highlighted a code question in the above listing and am puzzled that in the following, a key/value pair for $vars{"grandfather"} seems not to be created. In other words, the hash does not seem to be extensible in the way I thought all hashes in perl were:

      $ cat utils2.pm package utils2; require Exporter; use utils1; use utf8; use open qw/:std :utf8/; use Data::Dumper; our @ISA = qw(Exporter); our @EXPORT = qw( archive1 ); sub archive1 { use warnings; use 5.011; use Path::Tiny; my $rvars = shift; my %vars = %$rvars; say Dumper $rvars; $vars{"grandfather"} = $vars{"init_dir"}->parent(); my $file1 = "1.manifest"; my $from = path( $vars{"grandfather"}, $file1 ); say "from is $from"; my @files = $from->lines_utf8( { chomp => 1 } ); say Dumper $rvars; say "files are @files"; my $tempdir = Path::Tiny->tempdir('backup_XXXXXX'); say "temp dir is $tempdir"; my $readme = $tempdir->child( 'grandmother', 'README.txt' )->touchpa +th; say "read me is $readme"; my $grand_dir = $readme->parent; chdir $grand_dir; foreach my $item (@files) { say "item is <<$item>>"; next if ($item eq ""); my $abs = path( $vars{"grandfather"}, $item ); say "abs is <$abs>"; if ( -d $abs ) { say "$abs is a directory"; } if ( -f $abs ) { say "$item is a plain file"; #syntax is from -> to my $return = path($abs)->copy( $grand_dir, $item ); if ( $item =~ m/\.(pl|sh)$/ ) { $return->chmod(0755); } say "return is $return"; } } my $b = $tempdir; return $b; } 1; $

      Output:

      $ ./4.archive.pl path1 is /home/bob/1.scripts/pages/1.qy base is 1.qy $VAR1 = { 'init_dir' => bless( [ '/home/bob/1.scripts/pages/1.qy', '/home/bob/1.scripts/pages/1.qy', '', '/home/bob/1.scripts/pages/', '1.qy' ], 'Path::Tiny' ), 'title' => '1.qy', 'script_file' => '/home/bob/1.scripts/pages/1.qy/4.archive.p +l' }; from is /home/bob/1.scripts/pages/1.manifest $VAR1 = { 'init_dir' => bless( [ '/home/bob/1.scripts/pages/1.qy', '/home/bob/1.scripts/pages/1.qy', '', '/home/bob/1.scripts/pages/', '1.qy' ], 'Path::Tiny' ), 'title' => '1.qy', 'script_file' => '/home/bob/1.scripts/pages/1.qy/4.archive.p +l' }; files are 2.create.bash ... 5.unicode 3.create.bash temp dir is /tmp/backup_MKb3nU read me is /tmp/backup_MKb3nU/grandmother/README.txt item is <<2.create.bash>> abs is </home/bob/1.scripts/pages/2.create.bash> 2.create.bash is a plain file return is /tmp/backup_MKb3nU/grandmother/2.create.bash ... item is <<5.unicode>> abs is </home/bob/1.scripts/pages/5.unicode> /home/bob/1.scripts/pages/5.unicode is a directory item is <<>> item is <<3.create.bash>> abs is </home/bob/1.scripts/pages/3.create.bash> 3.create.bash is a plain file return is /tmp/backup_MKb3nU/grandmother/3.create.bash /tmp/backup_MKb3nU . ./grandmother ./grandmother/1.initialize.pl ./grandmother/3.create.bash ./grandmother/5.create.sh ./grandmother/1.manifest ./grandmother/2.create.bash ./grandmother/README.txt ./grandmother/11.clone.pl cannot remove path when cwd is /tmp/backup_MKb3nU for /tmp/backup_MKb3 +nU: at /usr/share/perl/5.26/File/Temp.pm line 1583. $

      Fishing for tips....

        ### does this^^^ have a Path::Tiny equivalent?

        Yes. It is the absolute method.

        #!/usr/bin/env perl use strict; use warnings; use Path::Tiny; my $abs = path(__FILE__)->absolute; print "$abs\n";

        Datz_cozee75 wrote:

        my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $title = $path1->basename; say "base is $title"; # script parameters my %vars = ( init_dir => $path1, title => $title, script_file => $fspecfile, ); ... $vars{"grandfather"} = $vars{"init_dir"}->parent();
        I highlighted a code question in the above listing and am puzzled that in the following, a key/value pair for $vars{"grandfather"} seems not to be created. In other words, the hash does not seem to be extensible in the way I thought all hashes in perl were:

        Hi Datz_cozee75. If you have a look at perldata you can read about how hashes work. A hash is an associative array of scalar values. That means the only thing you can store in a hash is a string, a number or a reference. In the code section above you seem to be trying to call the parent() method against the value of $vars{"init_dir"}. Did you get any warnings when you ran this program?

        Update: I tried a small test of your code above and it works. I realized after I posted and looked more closely at the output you posted that the hash value is holding a reference to the path object. Why do you say that a key/value pair for $vars{"grandfather"} is not created? It looks like the following line in your output shows that the key value pair was created.

        from is /home/bob/1.scripts/pages/1.manifest

        Since you asked for tips I suggest starting with a much smaller program and build functionality until you have your final result. Smaller, simpler functions allow you to test and get it working before adding functionality. Also, your tested code can be kept separate from new code that way.

        Here is my test script:

        use warnings; use strict; use Path::Tiny; use feature 'say'; use Data::Dumper; my $path1 = Path::Tiny->cwd; say "path1 is $path1"; my $title = $path1->basename; say "base is $title"; # script parameters my %vars = ( init_dir => $path1, title => $title, ); $vars{"grandfather"} = $vars{"init_dir"}->parent(); print Dumper(\%vars); print "vars-grandfather : ", $vars{"grandfather"}->basename, "\n"; print "vars-grandfather : ", $vars{"grandfather"}; __DATA__ path1 is C:/usr/pm base is pm $VAR1 = { 'init_dir' => bless( [ 'C:/usr/pm', 'C:\\usr\\pm', 'C:', '/usr/', 'pm' ], 'Path::Tiny' ), 'title' => 'pm', 'grandfather' => bless( [ 'C:/usr', 'C:\\usr' ], 'Path::Tiny' ) }; vars-grandfather : usr vars-grandfather : C:/usr