Ryan McClure
Ryan McClure

Reputation: 1233

Most efficient way to write over file after reading

I'm reading in some data from a file, manipulating it, and then overwriting it to the same file. Until now, I've been doing it like so:

open (my $inFile, $file) or die "Could not open $file: $!";

    $retString .= join ('', <$inFile>); 
    ...

close ($inFile);

open (my $outFile, $file) or die "Could not open $file: $!";

    print $outFile, $retString;

close ($inFile);

However I realized I can just use the truncate function and open the file for read/write:

open (my $inFile, '+<', $file) or die "Could not open $file: $!";

    $retString .= join ('', <$inFile>); 
    ...
    truncate $inFile, 0;
    print $inFile $retString;

close ($inFile);

I don't see any examples of this anywhere. It seems to work well, but am I doing it correctly? Is there a better way to do this?

Upvotes: 0

Views: 99

Answers (4)

Andrey
Andrey

Reputation: 1818

The Tie::File module may help if you are changing some lines in a file.

Like this

use strict;
use warnings;

use Tie::File;

tie my @source, 'Tie::File', 'file.txt' or die $!;

for my $line (@source) {
   # Modify $line here      
}

untie @source;

Upvotes: 1

Borodin
Borodin

Reputation: 126762

The canonical way to read an entire file contents is to (temporarily) set the input record separator $/ to undef, after which the next readline will return all of the rest of the file. But I think your original technique is much clearer and less involved than just reopening the file for write.

Note that all the following examples make use of the autodie pragma, which avoids the need to explicitly test the status of open, close, truncate, seek, and many other related calls that aren't used here.

Opening in read/write mode and using truncate would look like this

use strict;
use warnings;
use autodie;

use Fcntl 'SEEK_SET';

my ($file) = @ARGV;

open my $fh, '+<', $file;
my $ret_string = do { local $/; <$fh>; };

# Modify $ret_string;

seek $fh, 0, SEEK_SET;
truncate $fh, 0;
print $fh $ret_string;
close $fh;

whereas a simple second open would be

use strict;
use warnings;
use autodie;

my ($file) = @ARGV;

open my $fh, '<', $file;
my $ret_string = do { local $/; <$fh>; };

# Modify $ret_string;

open $fh, '>', $file;
print $fh $ret_string;
close $fh;

There is a third way, which is to use the equivalent of the edit-in-place -i command-line option. If you set the built-in variable $^I to anything other than undef and pass a file on the command line to the program then Perl will transparently rename by appending the value of $^I and open a new output file with the original name.

If you set $^I to the empty string '' then the original file will be deleted from the directory and will disappear when it is closed (note that Windows doesn't support this, and you have to specify a non-null value). But while you are testing your code it is best to set it to something else so that you have a route of retreat if you succeed in destroying the data.

That mode would look like this

use strict;
use warnings;

$^I = '.old';

my $ret_string = do { local $/; <>; };

# Modify $ret_string;

print $ret_string;

Note that the new output file is selected as the default output, and if you want to print to the console you have to write an explicit print STDOUT ....

Upvotes: 2

Miller
Miller

Reputation: 35208

I would recommend using $INPLACE_EDIT:

use strict;
use warnings;

my $file = '...';

local @ARGV = $file;
local $^I = '.bak';
while (<>) {
    # Modify the line;
    print;
}
# unlink "$file$^I"; # Optionally delete backup

For additional methods for editing a file, just read perlfaq5 - How do I change, delete, or insert a line in a file, or append to the beginning of a file?.

Upvotes: 2

mpapec
mpapec

Reputation: 50677

I would change the way you're reading the file, not how you open it. Joining lines is less efficient than reading whole file at once,

 $retString .= do { local $/; <$inFile> };

As for truncate you might want to seek to begining of the file first as perldoc suggests

The position in the file of FILEHANDLE is left unchanged. You may want to call seek before writing to the file.

Upvotes: 1

Related Questions