Reputation: 295
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
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
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
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
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
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
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