regexperl

Perl search and replace words in file


I am not sure how to search and replace word from Perl.

I have .txt that contains image file names in random order.

Animals.txt

There is an image Dog_Image_85.jpg
Cat has an image Cat_Image_front.jpg
Here is another image Dog_Image_61.jpg
Cat has another image Cat_Image_back.jpg
Next image Dog_Image_55.jpg
Next Image Cat_Image_side.jpg
Next Image Dog_Image_200.jpg

I only want to change the Dog image names to a counter so it goes:

There is an image Dog_Image_1.jpg
Cat has an image Cat_Image_front.jpg
Here is another image Dog_Image_2.jpg
Cat has another image Cat_Image_back.jpg
Next image Dog_Image_3.jpg
Next Image Cat_Image_side.jpg
Next Image Dog_Image_4.jpg

I have tried these but I get an error each time:

#!/usr/local/bin/perl -w -I ../lib -I ./

use strict;

my $Animals = "Animals";

my $file = "$Animals.txt";

perl -pi -e 's/Dog_Image_.*.jpg/"Dog_Image_.++$i.jpg"/ge' $file;

This gives me an error: Can't use bareword perl.

I also tried with:

system('s/Dog_Image_.*.jpg/"Dog_Image_.++$i.jpg"/ge' $file);'

It still gives an error.


Solution

  • It appears that you are trying to run a shell command, which would invoke perl to run a perl command-line program ("one-liner"), out of a Perl script. This is of course possible but it is a bit of a convoluted back-on-itself acrobatics, usually quote hungry. Why not do the job in that Perl script?

    Here is one way, using a useful Path::Tiny library

    use warnings;
    use strict;
    use feature 'say';
    
    use Path::Tiny;
    
    my $file_name = ...
    ...
    
    {
        my $dog_cnt = 0;
        path($file_name) -> edit( sub { 
            s/Dog_Image_\K([0-9]+)\.jpg/++$dog_cnt . '.jpg'/ge 
        });
    }
    

    The method edit runs the code in the callback on the content of the file that is slurped into $_, then overwrites the file. There is also edit_lines which applies the given code to one line at a time. None of this behaves exactly like the -i from the command-line but that is not a drawback.

    I put the code inside a {} block to localize that $dog_cnt counter. Another option is to define that variable inside the callback, in particular if the surrounding code doesn't need the counter.

    sub {
        my $dog_cnt = 0;
        s/Dog_Image_\K([0-9]+)\.jpg/++$dog_cnt . '.jpg'/ge 
    }
    

    Then the method edit_lines won't work for the code as it stands because the callback runs for each line so the counter is always redefined to zero. Change as suitable.

    Or, edit the file normally -- open, process line by line, overwrite the original in a way of your choosing, after whatever checking you desire. It's not much code, just a few lines. It beats running a complex shell command out of a script, by a country mile. Not to mention running a perl command-line program out of a Perl script.


    The library doesn't state how it "overwrites" the file but in my tests the inode number changes, so it writes out a file with the changed content and then renames it over the original.

    If the inode number needs to stay the same -- sometimes needed for tools used on the file -- then that can be done as follows.

    Change the file by opening and processing it as usual and either writing the new version to a temporary file or keeping it in memory. Then open the original for writing -- what will truncate it -- and write that new content into it (from that temporary file or from memory, wherever you had it). Now the original's content changed but the file'ss inode number didn't. Remove the temporary file if there was one.