1

I have a script which pulls all the pm files in my directory and look for certain pattern and change them to desired value, i tried Tie::File but it's not looking to content of the file

use File::Find;
use Data::Dumper qw(Dumper);
use Tie::File;
my @content;
find( \&wanted, '/home/idiotonperl/project/');

sub wanted {
    push @content, $File::Find::name;
return;  
}
my @content1 = grep{$_ =~ /.*.pm/} @content;
@content = @content1;
for my $absolute_path (@content) {
    my @array='';
    print $absolute_path;
    tie @array, 'Tie::File', $absolute_path or die qq{Not working};
    print Dumper @array;
    foreach my $line(@array) {
         $line=~s/PERL/perl/g;
    }
    untie @array;
 }

the output is

 Not working at tiereplacer.pl line 22.
 /home/idiotonperl/main/content.pm

this is not working as intended(looking into the content of all pm file), if i try to do the same operation for some test file under my home for single file, the content is getting replaced

  @content = ‘home/idiotonperl/option.pm’

it’s working as intended

zdim
  • 64,580
  • 5
  • 52
  • 81
questionar
  • 274
  • 2
  • 18
  • Do you insist on using `tie` -- do you have some (very) particular reason for it? – zdim Mar 03 '17 at 07:25
  • 1
    No...found this way to be easy for editing the particular line in the file – questionar Mar 03 '17 at 07:30
  • 2
    I would _not_ recomment using `tie` unless you have very strong reasons for that. I posted how to do what you aske in a fairly standard. Let me know how it goes. – zdim Mar 03 '17 at 07:43
  • @zdim: I very much disagree. `Tie::File` can make "simple things easy". It is pointless to fret about the overhead that `tie` imposes if it is in the order of a few milliseconds or less. (I presume that is your objection? You don't explain.) – Borodin Mar 03 '17 at 10:02
  • @Borodin I didn't mean to debate the value of it. It's like a piece of magic. I meant to say that it shouldn't be used for jobs that are done easily with only the most basic tools. It just raises the complexity right there. If the OP had used a simple loop this question would not happen. (I should have explained what I meant.) I didn't mean to complain about the overhead. Assessing that correctly is a part of using any tool. – zdim Mar 03 '17 at 10:24

3 Answers3

2

I would not recommend to use tie for that. This simple code below should do as asked

use warnings;
use strict;
use File::Copy qw(move);    
use File::Glob ':bsd_glob';

my $dir = '/home/...';

my @pm_files = grep { -f } glob "$dir/*.pm";

foreach my $file (@pm_files) 
{
    my $outfile = 'new_' . $file;  # but better use File::Temp

    open my $fh,     '<', $file    or die "Can't open $file: $!";
    open my $fh_out, '>', $outfile or die "Can't open $outfile: $!";

    while (my $line = <$fh>)
    {
        $line =~ s/PERL/perl/g;
        print $fh_out $line;     # write out the line, changed or not
    }
    close $fh;
    close $fh_out;

    # Uncomment after testing, to actually overwrite the original file
    #move $outfile, $file  or die "Can't move $outfile to $file: $!";
}

The glob from File::Glob allows you to specify filenames similarly as in the shell. See docs for accepted metacharacters. The :bsd_glob is better for treatment of spaces in filenames.

If you need to process files recursively then you indeed want a module. See File::Find::Rule

The rest of the code does what we must do when changing file content: copy the file. The loop reads each line, changes the ones that match, and writes each line to another file. If the match fails then s/ makes no changes to $line, so we just copy those that are unchanged.

In the end we move that file to overwrite the original using File::Copy.

The new file is temporary and I suggest to create it using File::Temp.


  The glob pattern "$dir/..." allows for an injection bug for directories with particular names. While this is very unusual it is safer to use the escape sequence

my @pm_files = grep { -f } glob "\Q$dir\E/*.pm";

In this case File::Glob isn't needed since \Q escapes spaces as well.

zdim
  • 64,580
  • 5
  • 52
  • 81
1

Solution using my favorite module: Path::Tiny. Unfortunately, it isn't a core module.

use strict;
use warnings;
use Path::Tiny;

my $iter = path('/some/path')->iterator({recurse => 1});
while( my $p = $iter->() ) {
        next unless $p->is_file && $p =~ /\.pm\z/i;
        $p->edit_lines(sub {
            s/PERL/perl/;
            #add more line-editing
        });
        #also check the path(...)->edit(...) as an alternative
}
clt60
  • 62,119
  • 17
  • 107
  • 194
0

Working fine for me:

#!/usr/bin/env perl
use common::sense;
use File::Find;
use Tie::File;

my @content;

find(\&wanted, '/home/mishkin/test/t/');

sub wanted {
    push @content, $File::Find::name;
    return;
}

@content = grep{$_ =~ /.*\.pm$/} @content;

for my $absolute_path (@content) {
    my @array='';
    say $absolute_path;
    tie @array, 'Tie::File', $absolute_path or die "Not working: $!";

    for my $line (@array) {
        $line =~ s/PERL/perl/g;
    }

    untie @array;
}
Demiurg
  • 111
  • 1
  • 9
  • works fine for me...had some permission issue in writing files – questionar Mar 03 '17 at 09:33
  • The `my @array = ()` isn't better? – clt60 Mar 03 '17 at 09:58
  • also, you don't need the `@content = grep{$_ =~ /.*\.pm$/} @content;` because you can filter the filenames directly in the `wanted` function, like: `find( sub{ push @content, $File::Find::name if -f && /\.pm$/i; }, q{.} );` – clt60 Mar 03 '17 at 09:58