如何在使用严格模式的Perl中逐行处理多行字符串?

8
我正在尝试找到一种适用于PBP的方法来逐行处理多行字符串。许多Perl编程人员建议将多行字符串视为文件句柄进行处理,这个方法在脚本中没有使用"use strict"时是有效的。但是,如果你的脚本启用了"use strict",那么编译器会发出一个关于不能在使用strict refs时将字符串用作符号的警告。
下面是一个简单的工作示例:
#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";

请注意 "use strict" 这一行被注释了。当我不使用 use strict 运行这个脚本时,我得到了我想要的和预期的结果:

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

然而,问题在于当我取消"use strict"行的注释时,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.

顺便说一下,第8行是“open $ResultsHandle, '<', \$return;”这一行。因此,由于Perl最佳实践要求我使用strict,那么PBP希望我如何一次处理多行字符串呢?SO社区有什么建议吗?

谢谢!


4
如果你确实想谈论最佳实践,我建议你检查一下所有变量的初始化方式,以及类似于旧式C语言(在顶部)定义变量的方式,还要注意使用反引号来实现Perl本身可以轻松实现的功能。 - innaM
1
谢谢,Manni。目录内容检索只是为了演示目的。我的实际程序实际上调用另一个程序并处理其输出。而旧式变量初始化是我需要更多工作的地方。但我经常从旧脚本中复制和粘贴,这就是我得到的结果。 :-) 不过我正在努力改进。 - Kurt W. Leucht
7个回答

11
不要初始化$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";

如果在执行open()之前不定义$ResultsHandle,它将会被赋值为文件句柄的引用。由于你将其设置为一个字符串,open()假定它应该是一个变量的符号引用,这违反了use strict的规定。


哇,谢谢!这表明我对Perl知之甚少!我想我认为我必须将其初始化为某些东西。我猜我错了。感谢您的快速回答! - Kurt W. Leucht
同时也表明我并不完全理解“严格”的含义。我一直在试图取悦编译器和 Perl Critic 模块,但并没有完全理解它们所生成的所有消息。 - Kurt W. Leucht
2
另一种思考方式是:如果 $ResultsHandle 还没有被初始化,open() 会为您初始化它。use strict 禁止一些偶尔有用但更常引起麻烦的事情。 - dave4420

7
更为简洁的PBP方式是使用open函数:
open my $ResultsHandle, '<', \$return;

这样就不需要之前的"my $Resultshandle;"声明,并且避免了你遇到的那个strict警告。


4

您也可以使用正则表达式作为迭代器:

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

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

与使用表示字符串的文件句柄相比,这种方法稍微简单一些。


3

使用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
    }
}

1
如果您使用'\n'作为行分隔符,它将不会将\n字符分配给$resultLine变量。最好使用split /^/m, $result,它将分配带有行结尾的整行。请耐心等待,最后一行并不总是有行结束符。 - Znik

2

更改

my $ResultsHandle = "";

to

my $ResultsHandle;

0

使用拆分(split)功能可以获得更好的结果:

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
}

0

使用来自“dir”命令的管道打开文件句柄。

例如:

open my $FOO, "dir|" or die "Can not run 'dir': $!";

2
确实。如果必须使用“dir”,那就用管道。但我宁愿使用readdir或简单的glob。 - innaM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接