Matt
Matt

Reputation: 295

Perl editting a file

I'm trying to open a file, search for a specific string in the file to begin my search from and then performing a replacement on a string later on in the file. For example, my file looks like:

Test Old

Hello World

Old

Data

Begin_search_here

New Data

Old Data

New Data

I want to open the file, begin my search from "Begin_search_here" and then replace the next instance of the word "Old" with "New". My code is shown below and I'm correctly finding the string, but for some reason I'm not writing in the correct location.

open(FILE, "+<$filename") || die "problem opening file";

my search = 0;
while(my $line = <FILE>)
{
if($line =~ m/Begin_search_here/)
{
$search = 1;
}

if($search == 1 && $line =~m/Old/)
{
$line = s/Old/New/;
print FILE $line
}

close FILE;

Upvotes: 1

Views: 302

Answers (6)

Axeman
Axeman

Reputation: 29844

I have done a number of edits like this, that I came up with a generic (yet stripped-down) strategy:

use strict;
use warnings;

use English qw<$INPLACE_EDIT>;
use Params::Util qw<_CODE>;

local $INPLACE_EDIT = '.bak';
local @ARGV = '/path/to/file';

my @line_actions 
    = ( qr/^Begin_search_here/
      , qr/^Old Data/ => sub { s/^Old/New/ }
      ); 
my $match = shift @line_actions;
while ( <> ) { 
    if ( $match and /$match/ ) { 
        if ( _CODE( $line_actions[0] )) { 
            shift( @line_actions )->( $_ );
        }
        $match = shift @line_actions;
    }
    print;
}

Upvotes: 1

Sean
Sean

Reputation: 29772

Here ya go:

local $^I = '.bak';
local @ARGV = ($filename);
local $_;
my $replaced = 0;

while (<>) {
    if (!$replaced && /Begin_search_here/ .. $replaced) {
        $replaced = s/Old/New/;
    }
    print;
}

Explanation:

Setting the $^I variable enables inplace editing, just as if you had run perl with the -i flag. The original file will be saved with the same name as the original file, but with the extension ".bak"; replace ".bak" with "" if you don't want a backup made.

@ARGV is set to the list of files to do inplace editing on; here just your single file named in the variable $filename.

$_ is localized to prevent overwriting this commonly-used variable in the event this code snippet occurs in a subroutine.

The flip-flop operator .. is used to figure out what part of the file to perform substitutions in. It will be false until the first time a line matching the pattern Begin_search_here is encountered, and then will remain true until the first time a substitution occurs (as recorded in the variable $replaced), when it will turn off.

Upvotes: 4

David W.
David W.

Reputation: 107030

Be very careful about editing a file in place. If the data you're replacing is a different length, you wreck the file. Also, if your program fails in the middle, you end up with a destroyed file.

Your best bet is to read in each line, process the line, and write each line to a new file. This will even allow you to run your program, examine the output, and if you have an error, fix it and rerun the program. Then, once everything is okay, add in the step to move your new file to the old name.

I've been using Perl since version 3.x, and I can't think of a single time I modified a file in place.

use strict;
use warnings;

open (INPUT, "$oldfile") or die qq(Can't open file "$oldFile" for reading);
open (OUTPUT, "$oldfile.$$") or die qq(Can't open file "$oldfile.$$" for writing);

my $startFlag = 0;
while (my $line = <INPUT>) {
    if ($line ~= /Begin_search_here/) {
        $startFlag = 1;
    }
    if ($startFlag) {
        $line =~ s/New/Old/;
    }
    print OUTPUT "$line";
}

# 
# Only implement these two steps once you've tested your program
#
unlink $oldfile;
rename $oldfile.$$", $oldfile;

Upvotes: 0

mob
mob

Reputation: 118595

You are misusing the random-access file mode. By the time you update $line and say print FILE $line, the "cursor" of your filehandle is already positioned at the beginning of the next line. So the original line is not changed and the next line is over-written, instead of overwriting the original line.

Inplace editing (see perlrun) looks like it would be well suited for this problem.

Otherwise, you need to read up on the tell function to save your file position before you read a line and seek back to that position before you rewrite the line. Oh, and the data that you write must be exactly the same size as the data you are overwriting, or you will totally fubar your file -- see this question.

Upvotes: 1

CanSpice
CanSpice

Reputation: 35790

You would probably be best served by opening the input file in read mode (open( my $fh, '<', $file ) or die ...;), and writing the modified text to a temporary output file, then copying the temporary file overtop of the input file when you're done doing your processing.

Upvotes: 1

btilly
btilly

Reputation: 46389

This works. It will, as you specified, only replaces one occurrence.

#! /usr/bin/perl -pi.bak

if (not $match_state) {
  if (/Begin_search_here/) {
    $match_state = "accepting";
  }
}
elsif ($match_state eq "accepting") {
  if (s/Old/New/) {
    $match_state = "done";
  }
}

Upvotes: 0

Related Questions