题
我正在维护一个脚本,该脚本可以从各种来源获取输入,并按行对其进行处理。根据实际使用的源,换行符可能是 Unix 风格、Windows 风格,甚至对于某些聚合输入来说,是混合的(!)。
当从文件读取时,它是这样的:
@lines = <IN>;
process(\@lines);
...
sub process {
@lines = shift;
foreach my $line (@{$lines}) {
chomp $line;
#Handle line by line
}
}
所以,我需要做的就是用可以删除 Unix 风格或 Windows 风格换行符的东西替换 chomp。我想出了太多的方法来解决这个问题,这是 Perl 的常见缺点之一:)
您对消除通用换行符的最简洁方法有何看法?什么是最有效的?
编辑:一个小的澄清 - 方法“process”从某处获取行列表, 不一定从文件中读取. 。每行可能有
- 没有尾随换行符
- Unix 风格的换行符
- Windows 风格的换行符
- 只需回车(当原始数据具有 Windows 风格的换行符并使用 $/ = ' ' 读取时)
- 线条具有不同样式的聚合集
解决方案
在挖了一点之后 Perlre 文档有点,到目前为止,我将提出我最好的建议,似乎效果很好。 PERL 5.10添加了 r字符类作为广义的线路破解:
$line =~ s/\R//g;
与:
(?>\x0D\x0A?|[\x0A-\x0C\x85\x{2028}\x{2029}])
我将保持这个问题一段时间,只是为了看看是否有更多的好方法等待建议。
其他提示
每当我经过输入并要删除或替换字符时,我都会通过这样的小子例程运行它。
sub clean {
my $text = shift;
$text =~ s/\n//g;
$text =~ s/\r//g;
return $text;
}
这可能不是很花哨的,但是这种方法多年来一直对我来说无瑕。
阅读 Perlport 我建议这样
$line =~ s/\015?\012?$//;
为您使用的任何平台以及您可能正在处理的任何线条馈示样式安全,因为 r和 n中的任何样式都可能通过不同的perl口味而有所不同。
$line =~ s/[\r\n]+//g;
2017 年注释:由于设计错误和未维护的错误,不推荐使用 File::Slurp。使用 文件::吸食者 或者 路径::小 反而。
延伸你的答案
use File::Slurp ();
my $value = File::Slurp::slurp($filename);
$value =~ s/\R*//g;
File::Slurp 抽象出文件 IO 的内容,只为您返回一个字符串。
笔记
需要注意的是添加
/g
,没有它,给定一个多行字符串,它只会替换 第一的 令人反感的角色。另外,删除
$
, ,这对于这个目的来说是多余的,因为我们想要剥离 全部 换行符,不仅仅是在其含义之前的换行符$
在此操作系统上。在多行字符串中,
$
匹配结尾 细绳 这将是有问题的)。第 3 点意味着第 2 点是在您也想使用的假设下得出的
/m
否则 '$' 对于具有 >1 行的字符串中的任何实际内容基本上毫无意义,或者进行单行处理,实际上理解的操作系统$
并设法找到\R*
进行$
例子
while( my $line = <$foo> ){
$line =~ $regex;
}
鉴于上述表示法,操作系统无法理解您的文件“
”或“
”分隔符,在默认情况下,操作系统的默认分隔符设置为 $/
将导致将整个文件作为一个连续的字符串读取(除非您的字符串中包含 $OS 的分隔符,它将以此分隔)
所以在这种情况下所有这些正则表达式都是无用的:
/\R*$//
:只会删除最后一个序列\R
在文件中/\R*//
:只会删除第一个序列\R
在文件中/\012?\015?//
:什么时候只会删除第一个012\015
,\012
, 或者\015
顺序,\015\012
将导致\012
或者\015
被发射。/\R*$//
:如果文件中恰好不存在 '\015$OSDELIMITER' 的字节序列,那么 不 除了操作系统自己的换行符之外,其他换行符都将被删除。
看来没有人明白我在说什么,所以这里是示例代码,即 经测试 到 不是 删除换行符。运行它,您会看到它保留了换行符。
#!/usr/bin/perl
use strict;
use warnings;
my $fn = 'TestFile.txt';
my $LF = "\012";
my $CR = "\015";
my $UnixNL = $LF;
my $DOSNL = $CR . $LF;
my $MacNL = $CR;
sub generate {
my $filename = shift;
my $lineDelimiter = shift;
open my $fh, '>', $filename;
for ( 0 .. 10 )
{
print $fh "{0}";
print $fh join "", map { chr( int( rand(26) + 60 ) ) } 0 .. 20;
print $fh "{1}";
print $fh $lineDelimiter->();
print $fh "{2}";
}
close $fh;
}
sub parse {
my $filename = shift;
my $osDelimiter = shift;
my $message = shift;
print "Parsing $message File $filename : \n";
local $/ = $osDelimiter;
open my $fh, '<', $filename;
while ( my $line = <$fh> )
{
$line =~ s/\R*$//;
print ">|" . $line . "|<";
}
print "Done.\n\n";
}
my @all = ( $DOSNL,$MacNL,$UnixNL);
generate 'Windows.txt' , sub { $DOSNL };
generate 'Mac.txt' , sub { $MacNL };
generate 'Unix.txt', sub { $UnixNL };
generate 'Mixed.txt', sub {
return @all[ int(rand(2)) ];
};
for my $os ( ["$MacNL", "On Mac"], ["$DOSNL", "On Windows"], ["$UnixNL", "On Unix"]){
for ( qw( Windows Mac Unix Mixed ) ){
parse $_ . ".txt", @{ $os };
}
}
为了 清楚地 未处理的输出,请参见此处: http://pastebin.com/f2c063d74
请注意,某些组合当然有效,但它们很可能是您自己天真的测试过的组合。
请注意,在此输出中,所有结果必须采用以下形式 >|$string|<>|$string|<
和 无换行 被视为有效输出。
和 $string
是一般形式 {0}$data{1}$delimiter{2}
在所有输出源中,应该有:
- 之间没有什么
{1}
和{2}
- 仅有的
|<>|
之间{1}
和{2}
在您的示例中,您可以去:
chomp(@lines);
或者:
$_=join("", @lines);
s/[\r\n]+//g;
或者:
@lines = split /[\r\n]+/, join("", @lines);
直接在文件上使用这些:
perl -e '$_=join("",<>); s/[\r\n]+//g; print' <a.txt |less
perl -e 'chomp(@a=<>);print @a' <a.txt |less
为了扩展TED Cambron的答案以及此处尚未解决的问题:如果您从输入文本的一部分中删除了所有二线休息,则最终将在稍后输出该文本时彼此相互融合而没有空格。这就是我使用的:
sub cleanLines{
my $text = shift;
$text =~ s/\r/ /; #replace \r with space
$text =~ s/\n/ /; #replace \n with space
$text =~ s/ / /g; #replace double-spaces with single space
return $text;
}
最后的替代使用G'Gheedy”修饰符,因此它继续找到双空间,直到它代替它们为止。 (有效地代替任何单一空间)