- Item 51. Don't ignore the file test operators.
- Item 52. Always use the three-argument open.
- Item 53. Consider different ways of reading from a stream.
- Item 54. Open filehandles to and from strings.
- Item 55. Make flexible output.
- Item 56. Use File::Spec or Path::Class to work with paths.
- Item 57. Leave most of the data on disk to save memory.
Item 54. Open filehandles to and from strings.
Since Perl 5.6, you can open filehandles on strings. You don't have to treat strings any differently from files, sockets, or pipes. Once you stop treating strings specially, you have a lot more flexibility about how you get and send data. Reduce the complexity of your application by reducing the number of cases it has to handle.
And this change is not just for you. Though you may not have thought that opening filehandles on strings was a feature, it is. People tend to want to interact with your code in ways that you don't expect.
Read from a string
If you have a multiline string to process, don't reach for a regex to break it into lines. You can open a filehandle on a reference to a scalar, and then read from it as you would any other filehandle:
my $string = <<'MULTILINE';
Buster
Mimi
Roscoe
MULTILINE
open my ($str_fh), '<', \$string;
my @end_in_vowels = grep /[aeiou]$/, <$str_fh>;
Later, suppose you decide that you don't want to get the data from a string that's in the source code, but you want to read from a file instead. That's not a problem, because you are already set up to deal with filehandles:
my @end_in_vowels = grep /[aeiou]$/, <$other_fh>;
It gets even easier when you wrap your output operations in a subroutine. That subroutine doesn't care where the data come from as long as it can read from the filehandle it gets:
my @matches = ends_in_vowel($str_fh); push @matches, ends_in_vowel($file_fh); push @matches, ends_in_vowel($socket); sub ends_in_vowel { my ($fh) = @_; grep /[aeiou]$/, <$fh>; }
Write to a string
You can build up a string with a filehandle, too. Instead of opening the string for reading, you open it for writing:
my $string = q{};
open my ($str_fh), '>', \$string;
print $str_fh "This goes into the string\n";
Likewise, you can append to a string that already exists:
my $string = q{};
open my ($str_fh), '>>', \$string;
print $str_fh "This goes at the end of the string\n";
You can shorten that a bit by declaring $string at the same time that you take a reference to it. It looks odd at first, but it works:
open my ($str_fh), '>>', \my $string
;
print $str_fh "This goes at the end of the string\n";
This is especially handy when you have a subroutine or method that normally expects to print to a filehandle, although you want to capture that output in memory. Instead of creating a new file only to read it back into your program, you just capture it directly.
seek and tell
Once you have a filehandle to a string, you can do all the usual filehandle sorts of things, including moving around in this "virtual file." Open a string for reading, move to a location, and read a certain number of bytes. This can be really handy when you have an image file or other binary (non–line-oriented) format you want to work with:
use Fcntl qw(:seek); # for the constants my $string = 'abcdefghijklmnopqrstuvwxyz'; my $buffer;open my ($str_fh), '<', \$string;
seek( $str_fh, 10, SEEK_SET )
; # move ten bytes from start my $read =read( $str_fh, $buffer, 4 )
; print "I read [$buffer]\n"; print "Now I am at position ", tell($str_fh), "\n";seek( $str_fh, -7, SEEK_CUR )
; # move seven bytes back my $read =read( $str_fh, $buffer, 4 )
; print "I read [$buffer]\n"; print "Now I am at position ", tell($str_fh), "\n";
The output shows that you are able to move forward and backward in the string:
I read [klmn] Now I am at position 14 I read [hijk] Now I am at position 11
You can even replace parts of the string if you open the filehandle as read-write, using +< as the mode:
use Fcntl qw(:seek); # for the constants my $string = 'abcdefghijklmnopqrstuvwxyz'; my $buffer; open my ($str_fh),'+<'
, \$string; # move 10 bytes from the start seek( $str_fh, 10, SEEK_CUR );print $str_fh '***'
; print "String is now:\n\t$string\n"; read( $str_fh, $buffer, 3 ); print "I read [$buffer], and am now at ", tell($str_fh), "\n";
The output shows that you've changed the string, but can also read from it:
String is now: abcdefghij***nopqrstuvwxyz I read [nop], and am now at 16
You could do this with substr, but then you'd limit yourself to working with strings. When you do it with filehandles, you can handle quite a bit more.
Things to remember
- Treat strings as files to avoid special cases.
- Create readable filehandles to strings to break strings into lines.
- Create writeable filehandles to strings to capture output.