Howto treat a Perl Scalar like a Filehandle

This morning, for the second time in as many weeks, I banged my head against a Perl problem I thought should be simple: I wanted to open a filehandle against a scalar (or perform some other chicanery) so that if I have a scalar $text full of text, I could do this:

while (<$fh>) {
    #do something with $_
}

Where the filehandle $fh would refer to the contents of the scalar $text. This seems like an obvious thing someone may want to do. While you could just as easily do something like foreach (split /\n/,$text) { #... }, I had a situation where I might have data in a scalar or in a file (or even STDIN) and I wanted to treat them all the same. I expected this would be covered in the Camel and/or the Cookbook, but I couldn’t find any such thing. I didn’t have much luck on the web either. In the end, I took the low road and faked it with a system call and a pipe, since I the script I was working on is infrequently used. Here’s that version:

open(FH, qq[echo "$text" |]) or die "Can't pipe.";
while (<fh>) { #... }

When the same problem came up this morning in a web service I’m working on, I decided to try researching it again, since I really didn’t like the pipe solution. After alot of digging, I came across the perl module IO::Scalar, which lets you do exactly what I wanted. The IO:Scalar version of the code looks like this:

use IO::Scalar;
my $fh = new IO::Scalar \$text;
while (<$fh>) { #... }

My test app worked nicely on the Unix development server, but then I realized I still had a problem. For some period of time, this webservice will be running on a Windows2000 web server (don’t ask). IO::Scalar is not part of the standard Perl distro. I’m using ActiveState’s ActivePerl, which makes installing Modules via perl -MCPAN -e shell, well, challenging. ActiveState has a nice Perl Package Manager; unfortunately I could find no ready-made package for IO::Scalar, so I was stuck. While stumbling around the ActiveState site looking for inspiration, I found PerlIO::scalar. Now I was on to something.

(A moment for a side note here. I would write far fewer lines of Perl code per hour if not for the fantastic Perldoc.com maintained by Carlos Ramirez. It is an absolutely indispensible resource for me. However, it’s a bit buggy. I can’t get it to let me search the perl 5.8.0 docs, and the 5.8.4 docs appear incomplete (missing standard modules). After finding the info on ActiveState’s site, I figured out how to get to it on Perldoc.com.)

According the docs for PerlIO::scalar:

PerlIO::scalar only exists to use XSLoader to load C code that provides support for treating a scalar as an “in memory” file.

The docs on the ActiveState site also note that it’s not necessary to use PerlIO::Scalar. This reduces the code to the following:

open $fh, "<", \$text;
while (<$fh>) { #... }

Excellent. Not only is it concise and easy to use, it’s part of the standard distro. I’ve documented this not only for my own future reference, but to add my bit to the world’s largest help database. I just hope I wasn’t being totally obtuse, to later find that this is Camel page 52 material.

Both comments and pings are currently closed.

7 Responses to “Howto treat a Perl Scalar like a Filehandle”

  1. David Says:

    nice! just what I needed. mime::parser->parse requires a file handle….

  2. Jason Says:

    And yes, this works for output as well…

    my $buffer = '';  #init buffer
    open $fh, ">" \$text;
    print $fh "lots of stuff...";
    # $buffer contains "lots of stuff...";
    

    great for unit tests of methods that need an XML::Writer.

  3. Michael Says:

    Nice, thanks for posting this. Note: It should be

     while () {} 

  4. Jason Says:

    Thanks Michael, good catch. Fixed!

  5. KES Says:

    Oops… but this does not work =(

    my $mmm; open OLDOUT, “>&”, STDOUT or die “0000: $!”; close STDOUT; open STDOUT, “>”, \$mmm;

    open my $ff, “>&”, STDOUT or die “1111: $!”; open FH, “>&”, $ff or die “2222: $!”; open F2, “>&”, FH or die “3333: $!”; print F2 “hello\n”;

    close STDOUT; open STDOUT, “>&”, OLDOUT; print $mmm;

  6. David Says:

    Thanks for posting this – it has solved my problem!

  7. Gord Taylor Says:

    Wow. You posted this back in 2004, and still useful today. Thanks – that saved me a lot of time messing around with substituting nr for some dummy value, and parsing through a multiline manually.

    Changing $/ into a custom value is much easier/cleaner, but only seems to work with files handles…