Home > Articles > Web Development > Perl

  • Print
  • + Share This
This chapter is from the book

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';

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:
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.
  • + Share This
  • 🔖 Save To Your Account