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

Hi Monks,

I'm having trouble with a simple search and replace. I'm searching and replacing a string in the file. The substitution is correct, however, I can't get it to make changes in the file. Everytime I make changes nothing gets changed in the actual file. Can anyone help? Thanks. I have the following code:

#! /usr/bin/perl; use File::Copy; #! /usr/bin/perl; use File::Copy; $^I = '~'; open (IN, "file.txt"); s/To: [\.\@\D\,]*/To: someone\@email.com/ while <IN>; close(IN);
I also tried this which didnt change the actual file either:
open (INFILE, "<$dirContent"); open (OUTFILE, "<$dirContent"); while (my $row = <INFILE>) { if($row =~ /Cc:/){ $row =~ s/Cc: [\.\@\D\,]*//; print OUTFILE $row; } if( $row =~/^To:/){ $row =~s/^To: [\.\@\D\,]*/To:email\@mail.com/; print OUTFILE $row; } print OUTFILE $row;} } close (INFILE); close (OUTFILE);
thanks~
jc

Replies are listed 'Best First'.
Re: replacing in file
by skyknight (Hermit) on Aug 01, 2003 at 17:52 UTC

    In the first code example, all you're doing is modifying the contents of $_ in the while loop, and then not bothering to do anything with it. In the second example...

    open (OUTFILE, "<$dirContent");

    You just opened your "out file" for reading. You wanted...

    open (OUTFILE, ">$dirContent");

    Worse still... You're opening the same file twice! You have two choices for what you want to do... Either open the original file for reading, and a new one for writing, then rename the new to the old when you're done, or better still, do this all from the command line (which will basically do the same thing under the hood.

    perl -p -i.bak -e '/oldstring/newstring/'

    Having to write a whole new file may seem unfortunate and inefficient, but you don't really have much choice. A file is a contiguous block of bytes, and if you grow or shrink a piece of it, you basically have to shift all the following pieces around, or rewrite the whole file. The only way around rewriting the whole file is to have a file of fixed record lenghts, or to store modification information at the end of the file, and have a very dynamic, on the fly type of data extraction occuring. The latter is how a lot of text editors work, recording lots of small changes at the end of the file, and every now and again performing a monolithic clean up and synchronization.

      Hey skyknight
      The problem with the one liner is that it's actually part of a bigger script. I tried calling the one liner perl program in a larger program, but it doesnt do the job. For some oddball reason, I wanted to replace the line "To: <list of email addys>" to just "To: me@home.com". The regex I used only got rid of the To: and left everything else. Here's the one liner in my perl script:
      `perl -p -e 's/^To: [\.\@\D\,]*/To:me@home.com/' Mailbox`;
      Can you give me any other specifics?
        Here's (roughly) what Perl does when you use the -pi.bak flag on the file filename (and what you'll have to emulate):
        1. Open the file "filename" for read, with handle READ
        2. Rename "filename" to "filename.bak"
        3. Open the file "filename" for write+truncate with handle WRITE
        4. Read through all the lines from READ, modify them, then print them to WRITE

        It's not working because you lost the -i switch, which specifies edit-in-place. Do "perl --help" to get a list of command line switches. You really shouldn't invoke an external process, though, if you don't have to do so. Just follow the other idiom I mentioned. To elaborate...

        open(OLD, "<old.txt") or die "can't open original file: $!"; open(NEW, ">new.txt") or die "can't open new file: $!"; while(<OLD>) { s/whatever/somethingelse/; print NEW; } close(OLD); close(NEW) rename("new.txt", "old.txt");
Re: replacing in file
by Anonymous Monk on Aug 01, 2003 at 18:01 UTC

    You have to realize that files on disk aren't the same as the contents of those files in memory. When you do s/foo/bar/ while <IN> you are making a copy of the line of IN in memory, then doing the substitution-- and then throwing it away!

    Your second example is opening the file in the wrong mode-- you really wanted to say open(OUTFILE, ">", "$dirContent"). I always think of that > as a funnel to pour stuff into the file. Using three-argument open when you explicitly want a read/write mode makes your code easier to read. (By the way, ,open(FILE, "<", $file) is the same as open(FILE, $file). And you may want to use open(args) or die($!) to check if you succeeded.)

    Of course, you don't want to output into the file while you're still inputting from it. You can build up a scalar, then close infile, open outfile and print at the end, or you can make a temp file, then rename it to the name of the original file at the end, or whatever. But on my (Linux) system, opening the file for output erases it-- before you get a chance to read it in.

Re: replacing in file
by blue_cowdawg (Monsignor) on Aug 01, 2003 at 18:05 UTC

    Up-front diclaimer: I think I understand what you are trying to do here, but I am not sure. If I understand correctly you are trying to take a list of files and replace To: "<What ever is here> with To: <another address>." If not then my reply is going to not make sense.

    I see two ways of doing this and regexes are not my greatest strength but here goes. A one liner:

        $ perl -spi -e 's/^To:\s+[\.\@\D\,]*/To: email.com/' FILE
    NOTE: I am suspicious of the regex that I glombed from your original post. I used it "as-is" figuring you might have a reason for using it. I might have been tempted to use something much more generic.

    Next I look at your code and see a couple of problems, besides the regex. When you are opening files you have:

        open (INFILE, "<$dirContent");# this is ok... but... open (OUTFILE, "<$dirContent"); # this is fishy.
    You have opened OUTFILE for reading not for writing. Also, is $dirContent a single filename or a list of file names?

    Secondly, in your first code sniglet I am curious why you are use'ing <cpan:://File::Copy> and then not doing anything with it?

    Just a few first pass thoughts...


    Peter @ Berghold . Net

    Sieze the cow! Bite the day!

    Test the code? We don't need to test no stinkin' code! All code posted here is as is where is unless otherwise stated.

    Brewer of Belgian style Ales