In Perl, how can I read an entire file into a string?

Viewed 190995

I'm trying to open an .html file as one big long string. This is what I've got:

open(FILE, 'index.html') or die "Can't read file 'filename' [$!]\n";  
$document = <FILE>; 
close (FILE);  
print $document;

which results in:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN

However, I want the result to look like:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

This way I can search the entire document more easily.

17 Answers

I would do it like this:

my $file = "index.html";
my $document = do {
    local $/ = undef;
    open my $fh, "<", $file
        or die "could not open $file: $!";
    <$fh>;
};

Note the use of the three-argument version of open. It is much safer than the old two- (or one-) argument versions. Also note the use of a lexical filehandle. Lexical filehandles are nicer than the old bareword variants, for many reasons. We are taking advantage of one of them here: they close when they go out of scope.

Add:

 local $/;

before reading from the file handle. See How can I read in an entire file all at once?, or

$ perldoc -q "entire file"

See Variables related to filehandles in perldoc perlvar and perldoc -f local.

Incidentally, if you can put your script on the server, you can have all the modules you want. See How do I keep my own module/library directory?.

In addition, Path::Class::File allows you to slurp and spew.

Path::Tiny gives even more convenience methods such as slurp, slurp_raw, slurp_utf8 as well as their spew counterparts.

All the posts are slightly non-idiomatic. The idiom is:

open my $fh, '<', $filename or die "error opening $filename: $!";
my $data = do { local $/; <$fh> };

Mostly, there is no need to set $/ to undef.

From perlfaq5: How can I read in an entire file all at once?:


You can use the File::Slurp module to do it in one step.

use File::Slurp;

$all_of_it = read_file($filename); # entire file in scalar
@all_lines = read_file($filename); # one line per element

The customary Perl approach for processing all the lines in a file is to do so one line at a time:

open (INPUT, $file)     || die "can't open $file: $!";
while (<INPUT>) {
    chomp;
    # do something with $_
    }
close(INPUT)            || die "can't close $file: $!";

This is tremendously more efficient than reading the entire file into memory as an array of lines and then processing it one element at a time, which is often--if not almost always--the wrong approach. Whenever you see someone do this:

@lines = <INPUT>;

you should think long and hard about why you need everything loaded at once. It's just not a scalable solution. You might also find it more fun to use the standard Tie::File module, or the DB_File module's $DB_RECNO bindings, which allow you to tie an array to a file so that accessing an element the array actually accesses the corresponding line in the file.

You can read the entire filehandle contents into a scalar.

{
local(*INPUT, $/);
open (INPUT, $file)     || die "can't open $file: $!";
$var = <INPUT>;
}

That temporarily undefs your record separator, and will automatically close the file at block exit. If the file is already open, just use this:

$var = do { local $/; <INPUT> };

For ordinary files you can also use the read function.

read( INPUT, $var, -s INPUT );

The third argument tests the byte size of the data on the INPUT filehandle and reads that many bytes into the buffer $var.

A simple way is:

while (<FILE>) { $document .= $_ }

Another way is to change the input record separator "$/". You can do it locally in a bare block to avoid changing the global record separator.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}

Either set $/ to undef (see jrockway's answer) or just concatenate all the file's lines:

$content = join('', <$fh>);

It's recommended to use scalars for filehandles on any Perl version that supports it.

You're only getting the first line from the diamond operator <FILE> because you're evaluating it in scalar context:

$document = <FILE>; 

In list/array context, the diamond operator will return all the lines of the file.

@lines = <FILE>;
print @lines;
use Path::Tiny qw( path );
 
my $file = 'data.txt';
my $data = path($file)->slurp_utf8;

Slurp mode - reading a file in one step: https://perlmaven.com/slurp

For a text-based file, without installing extra modules (only core ones, i.e. installed by default), you can try this:

use IO::File;
my $content = join '', IO::File->new($filename)->getlines;
Related