8

I'm trying to figure out the proper PBP approved way to process a multi line string one line at a time. Many Perl coders suggest treating the multi line string as a filehandle, which works fine unless you have "use strict" in your script. Then you get a warning from the compiler about not being able to use a string as a symbol while strict refs is in use.

Here's a simple working example of the problem:

#use strict;
use warnings; 

my $return = `dir`;
my $ResultsHandle = "";
my $matchLines = "";
my $resultLine = "";
open $ResultsHandle, '<', \$return;
while (defined ($resultLine = <$ResultsHandle>)) {
    if ($resultLine =~ m/joe/) {
        $matchLines = $matchLines . "\t" . $resultLine;
    }
}
close($ResultsHandle);
print "Original string: \n$return\n";
print "Found these matching lines: \n$matchLines\n";

Notice that the "use strict" line is commented out. When I run this script without use strict, I get what I want and expect:

Original string: 
 Volume in drive D has no label.
 Volume Serial Number is 50D3-54A6

 Directory of D:\Documents and Settings\username\My Documents\Eclipse\myTestProject

09/18/2009  11:38 AM    <DIR>          .
09/18/2009  11:38 AM    <DIR>          ..
09/18/2009  11:36 AM               394 .project
09/18/2009  11:37 AM                 0 joe.txt
09/18/2009  11:37 AM                 0 joey.txt
09/18/2009  11:38 AM                 0 kurt.txt
09/18/2009  11:43 AM               497 main.pl
09/18/2009  11:38 AM                 0 shane.txt
               6 File(s)            891 bytes
               2 Dir(s)   6,656,188,416 bytes free

Found these matching lines: 
    09/18/2009  11:37 AM                 0 joe.txt
    09/18/2009  11:37 AM                 0 joey.txt

Here's the problem, though. When I uncomment the "use strict" line, I get the following warning or error from Perl:

Can't use string ("") as a symbol ref while "strict refs" in use at D:/Documents and Settings/username/My Documents/Eclipse/myTestProject/main.pl line 8.

Line 8 is the "open $ResultsHandle, '<', \$return;" line, by the way. So since Perl Best Practices requires me to use strict, how does PBP expect me to process a multi line string one line at a time? Any suggestions from the SO community?

Thanks!

Kurt W. Leucht
  • 4,529
  • 8
  • 30
  • 45
  • 4
    If you do want to talk about best practices, I suggest you take a look at all your variable initialization, at the way you define variables like in oldfashioned C (at the top), and your use of backticks for something that can easily be achieved by Perl itself. – innaM Sep 18 '09 at 21:03
  • 1
    Thanks, Manni. The directory content retrieval was just for demonstration purposes. My actual program actually calls another program and processes its output. And the old fashioned variable initialization is something I need to work more on. But I often cut & paste from old scripts and this is what I get. :-) I'm trying to do better, though. – Kurt W. Leucht Sep 21 '09 at 16:47

7 Answers7

11

Don't initialise $ResultsHandle:

use strict;
use warnings; 

my $return = `dir`;
my $ResultsHandle;  # <-- leave undefined
my $matchLines = "";
my $resultLine = "";
open $ResultsHandle, '<', \$return;
while (defined ($resultLine = <$ResultsHandle>)) {
    if ($resultLine =~ m/joe/) {
        $matchLines = $matchLines . "\t" . $resultLine;
    }
}
close($ResultsHandle);
print "Original string: \n$return\n";
print "Found these matching lines: \n$matchLines\n";

If you leave $ResultsHandle undefined before the open(), it will be filled in with a reference to the file handle. Because you were setting it to a string, open() presumed that it was supposed to be a symbolic reference to a variable instead --- not allowed under use strict.

dave4420
  • 44,728
  • 6
  • 108
  • 146
  • Wow. Thanks! Shows how little I know about Perl! I guess I thought I had to initialize it to SOMETHING. Guess I was wrong. Thanks for the quick answer! – Kurt W. Leucht Sep 18 '09 at 16:15
  • Also shows that I don't understand "strict" at all. I've been just trying to please the compiler and the Perl Critic module both without fully understanding all the messages they've both been generating. – Kurt W. Leucht Sep 18 '09 at 16:21
  • 2
    Another way to think about it: `open()` initialises `$ResultsHandle` for you, *if it's not already been initialised*. `use strict` disallows things which can be useful occasionally, but more often cause trouble. – dave4420 Sep 18 '09 at 17:00
7

The more succinct PBP way is to use open like so:

open my $ResultsHandle, '<', \$return;

This eliminates the need for that earlier "my $Resultshandle;" declaration and avoids incurring that strict warning that you ran into.

AndyG
  • 35,661
  • 8
  • 94
  • 126
draegtun
  • 22,021
  • 5
  • 45
  • 70
4

You can also use a regexp as an iterator:

my $data = q{Hello
This
Is
A
Test};

while( $data =~ /(.+)$/mg) {
    print "line is '$1'\n";
}

This is slightly less convoluted compared to using a filehandle that represents a string.

jrockway
  • 39,825
  • 8
  • 59
  • 86
3

Convert the multi-line string into a list of single line strings with split:

my @resultLines = split /\n/, $result;     #   or  /\r\n/ for Windows?
foreach my $resultLine (@resultLines) {
    if ($resultLine =~ m/joe/) {
        $matchLines
            = $matchLines . "\t" 
                 . $resultLine . "\n";  # put \n or \r\n back on the end
    }
}
mob
  • 110,546
  • 17
  • 138
  • 265
  • if you use '\n' as line separator, it will not assign \n characters to $resultLine variable. better use split /^/m, $result , it will assign whole line with line ending. Be patient, last line not allways have got end of line characters. – Znik Dec 08 '16 at 09:28
2

Change

my $ResultsHandle = "";

to

my $ResultsHandle;
moonshadow
  • 75,857
  • 7
  • 78
  • 116
0

Open a filehandle using a pipe from "dir" command.

E.g.

open my $FOO, "dir|" or die "Can not run 'dir': $!";
innaM
  • 46,211
  • 4
  • 64
  • 85
DVK
  • 119,765
  • 29
  • 201
  • 317
  • 2
    Indeed. If "dir" is a must, go for the pipe. But I'd rather use readdir or a simple glob. – innaM Sep 18 '09 at 21:00
0

Better result with split can be done by:

my $result="LINE1
line2
linE3
";
#attention, /^/m allows properly operate on multiline string using regex
#and ^ is character empty begin all lines
foreach my $resultLine (split /^/m, $result) {
    print $resultline;  #withount '\n' because it have got
    #some checks & operations
}
Znik
  • 839
  • 11
  • 14