Stupid DATA Tricks
Image credit: Rob Nguyen on Flickr
I’ve previously written about Stupid Open Tricks, so now it’s time for some stupid DATA
tricks. You probably already know that you can “embed” a file inside a Perl program then read it from the DATA
filehandle. David Farrell wrote about this in Perl tokens you should know and he’s the one who reminded me about the curiousity that I’ll demonstrate here.
Anything after the __DATA__
line is not part of the program but is available to the program through the special DATA
filehandle:
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
__DATA__
Dog
Cat
Bird
The output shows each line after __DATA__
:
---Outputting DATA
Dog
Cat
Bird
---Done
I typically go the other way by starting with a data file and adding a program to the top of it:
#!/usr/bin/perl
use v5.26;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new;
while( my $row = $csv->getline(*DATA) ) {
say join ':', $row->@[3,7];
}
__DATA__
...many CSV lines...
This is the end, my friend, the END
You probably also know that you can use __END__
instead. I’m used to using that because it’s a holdover from Perl 4 and that’s where I first learned this:
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
__END__
Dog
Cat
Bird
You get the same output:
---Outputting DATA
Dog
Cat
Bird
---Done
But now let’s get a little tricky. Define a package at the end of the program. This still uses __END__
:
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
package not::main;
__END__
Dog
Cat
Bird
Again, this outputs the same thing as before. Nothing surprising here, but the suspense must be building:
---Outputting DATA
Dog
Cat
Bird
---Done
Change that __END__
to __DATA__
and try again:
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
package not::main;
__DATA__
Dog
Cat
Bird
Now you don’t see those lines:
---Outputting DATA
---Done
If you’ve read the documentation and cared about this sort of thing (or like me, forgotten it), you may have noticed that the DATA
handle lives in the package that’s in scope at the end of the program:
Text after DATA may be read via the filehandle “PACKNAME::DATA”, where “PACKNAME” is the package that was current when the DATA token was encountered.
I can use the package specification to get the lines back:
#!/usr/bin/perl
print "---Outputting DATA\n", <not::main::DATA>, "---Done\n";
package not::main;
__DATA__
Dog
Cat
Bird
Now those lines are back:
---Outputting DATA
Dog
Cat
Bird
---Done
But what about the __END__
? Well, that was a Perl 4 thing, before there were packages. Perl 5 added packages, then Perl 5.6 added the __DATA__
token. The __END__
kept doing what it was doing in the way it was doing it (package-less), and __DATA__
did something related by new:
For compatibility with older scripts written before DATA was introduced, END behaves like DATA in the top level script (but not in files loaded with “require” or “do”) and leaves the remaining contents of the file accessible via “main::DATA”.
Some other DATA tricks
There’s a few other interesting things you can do.
Program size
You can get the entire file size with the -s
file test operator. The __DATA__
(or __END__
) has to be there, but you don’t need any data after those tokens.
use v5.10;
my $size = -s DATA;
say "File size is $size";
__DATA__
The file size this reports includes everything in the file, not just the part before the end of processing.
use v5.10;
my $size = -s DATA;
say "File size is $size";
my $data = tell DATA;
say "Data starts at $data";
say "Data size is ", $size - $data
__END__
Dog
Cat
Bird
The program size includes the __END__
token and the newline after it. The rest belongs to the data:
File size is 164
Data starts at 151
Data size is 13
You can use DATA
in other file things, including stat
.
Read it twice
If you want to read the data twice, you can reset the file cursor. First, remember where DATA
starts by calling tell
before you read any lines. When you are ready to read it again, seek
to that same position:
#!/usr/bin/perl
my $data_start = tell DATA;
print "---Outputting DATA\n", <DATA>, "---Done\n";
seek DATA, $data_start, 0;
print "---Outputting DATA\n", <DATA>, "---Done\n";
__END__
Dog
Cat
Bird
Using line numbers
#!/usr/bin/perl
my $data_start = tell DATA;
print "---Outputting DATA\n";
while( <DATA> ) {
print "$. $_"
}
print "---Done\n";
__END__
Dog
Cat
Bird
Now you see some line numbers, but those start counting from the first line under __DATA__
:
---Outputting DATA
1 Dog
2 Cat
3 Bird
---Done
To get the real line numbers, you can figure out where the __END__
token is. This assumes that it’s not in the middle of documentation or in a string:
#!/usr/bin/perl
my $data_start = tell DATA;
my $end_line;
UNITCHECK {
open my $fh, '<', $0;
while( <$fh> ) { last if /\A__END__$/ }
$end_line = $.
}
print "---Outputting DATA\n";
while( <DATA> ) {
$n = $end_line + $.;
print "$n $_"
}
print "---Done\n";
__END__
Dog
Cat
Bird
Now you see the offsets in the whole file and not the count after the __END__
:
---Outputting DATA
19 Dog
20 Cat
21 Bird
---Done
There are some more vigorous methods in Can a Perl program know the line number where DATA begins? .
Multiple embedded files
This isn’t a DATA
thing, but you can make several embedded files with Inline::Files:
#!/usr/bin/perl
use Inline::Files;
print "---Outputting dogs\n", <DOGS>, "---Done\n";
print "---Outputting cats\n", <CATS>, "---Done\n";
print "---Outputting birds\n", <BIRDS>, "---Done\n";
__DOGS__
Rin Tin Tin
Lassie
Ol' Yellar
__CATS__
Grumpy Cat
Garfield
Maru
Mr Bigglesworth
__BIRDS__
Woody Woodpecker
Roadrunner
Zazu
Sam the Eagle
Each of those get their own filehandles:
---Outputting dogs
Rin Tin Tin
Lassie
Ol' Yellar
---Done
---Outputting cats
Grumpy Cat
Garfield
Maru
Mr Bigglesworth
---Done
---Outputting birds
Woody Woodpecker
Roadrunner
Zazu
Sam the Eagle
---Done
Inline::Files has a problem because it overrides open
. You have to use CORE::open
to get to the real one:
use Inline::Files;
print "---Outputting dogs\n", <DOGS>, "---Done\n";
print "---Outputting cats\n", <CATS>, "---Done\n";
print "---Outputting birds\n", <BIRDS>, "---Done\n";
CORE::open my $fh, '<:utf8', '/etc/hosts' or die $!;
print "---Outputting hosts\n", <$fh>, "---Done\n";
Tags
brian d foy
brian d foy is a Perl trainer and writer, and a senior editor at Perl.com. He’s the author of Mastering Perl, Mojolicious Web Clients, Learning Perl Exercises, and co-author of Programming Perl, Learning Perl, Intermediate Perl and Effective Perl Programming.
Browse their articles
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub