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

the program i am working on is designed to open a file, read one line at a time..perform some substitutions and write the changed line back to disk. what would be the best way to do this? opening the file, and using a while loop to change one line at a time and then print it back to the filehandle...but how do you make one line replace another in the original file? or would opening a second empty file and printing the new lines to that make more sense? sorry for being so vague...any help would be greatly appreciated.

Edited 2001-04-24 by mirod: changed the title.

  • Comment on In-place editing of files (was: general perl question)

Replies are listed 'Best First'.
Re: general perl question
by mirod (Canon) on Apr 24, 2001 at 14:46 UTC

    You want to use the -i and -p options. Do a perldoc perlrun for a complete list of Perl options.

    Your script should look like:

    #!/bin/perl -w -i .bak -p s/foo/toto/g; s/^\s*//;

    This will do the substitution on each line of the file(s) passed as arguments and save the original files with a .bak suffix.

      mirod's reply is probably what you want.

      If you'd like to do this in a program, set the $^I variable (same thing as -i on the cmd line) to .bak or whatever. Or don't set it, and you'll just write over the file. Make sure @ARGV is set to the file name(s), either via the cmd line or in the program.
Re: general perl question
by zigster (Hermit) on Apr 24, 2001 at 14:50 UTC
    OK first as an aside if you want to do this on the command line look at -i and -p see perldoc perlrun -i means edit the file in place and -p means loop over all the lines in the file soo:
    perl -pi '.orig' -e 's/SPONGE/WIBBLE/' FILE
    Will do the search and replace SPONGE for WIBBLE on file FILE, write the changes back, while saving the original file as FILE.orig. See perlrun for more info.

    UPDATE
    Could you please try and name your nodes with care. General Perl question will not help someone else looking for the same answer as you. It is a good question but a more carefully worded question would have really increased the value of the node.
    --

    Zigster

      Um I don't think this is being run from the command line, but a script the only flags you can't use in a script IIRC is -M and -m. The script finds all files ending in .bak and processes them.

      ++ for the node naming chastisement though. ;-)

      --
      
      Brother Frankus.
Re: general perl question
by repson (Chaplain) on Apr 24, 2001 at 15:17 UTC
    If your program is not being run on the command line you could use code similar to this.
    my $filename = 'foo.txt'; open IN, "< $filename" or die "Can't open $filename: $!\n"; open OUT, "> $filename.bak" or die "Can't write to $filename: $!\n"; while (<IN>) { s/foo/blah/g; print OUT $_; } close IN; close OUT; rename "$filename.bak", $filename;
    However this code is only the barest code that will work, and should probably not be used without file locks and other sanity checks.
      The flags work without the command line, too. The following will convert any files specified on the command line to all caps.
      #!/usr/bin/perl -w -i.bak -p $_ = uc;
      In fact, there was an Obfuscated Perl contest entry a while back that does DOS-to-Unix conversions using only command line switches on the shebang line.

      xoxo,
      Andy

      # Andy Lester  http://www.petdance.com  AIM:petdance
      %_=split';','.; Perl ;@;st a;m;ker;p;not;o;hac;t;her;y;ju';
      print map $_{$_}, split //,
      'andy@petdance.com'
      
        What I meant was that my code can be dropped in the middle of a program, while to use the switches in the middle of a program you would have to spawn a new perl with those switches.
        #!/usr/bin/perl -w ..... system(q#perl -i.bak -p -e '$_ = uc'#); .....
Re: In-place editing of files (was: general perl question)
by LunaticLeo (Scribe) on Apr 24, 2001 at 22:11 UTC
    Several respondents seem to be skirting the issues here. How do I open a file for reading and writing and edit on a per line basis?

    One element of this that most people foget is that it is possible to make the file smaller with search and replace operations. That means you have to truncate the file length.

    Here is my code:

    use IO::File qw( &SEEK_SET ); my $fh = IO::File->new("+<foo.out") or die "failed to open file: $!"; while (<$fh>) { my $line = $_; chomp $line; $line =~ s/foo/f/g; push @output, $line; } my $output = join("\n", @output); $fh->seek( 0 , SEEK_SET ); $fh->print($output, "\n"); $fh->truncate( length($output) ); $fh->close; exit 0;
    Clearly, this code walks the file while building up the new output. Then we reset the file position to the beginning; write over the old file contents; and truncate the file length to be the same size as the new contents. If we don't do the last step we might have left over garbage at the end of the file.

    TIMTOWTDI: Here is a non-OO way:

    use Fcntl qw( &SEEK_SET ); open(FH, "+<foo.out") or die "failed to open file"; while (<FH>) { my $line = $_; chomp $line; $line =~ s/foo/f/g; push @output, $line; } my $output = join("\n", @output); seek(FH, 0 , SEEK_SET ); print FH $output, "\n"; truncate(FH, length($output) ); close(FH); exit 0;