【问题标题】:Neatest way to remove linebreaks in Perl在 Perl 中删除换行符的最简洁方法
【发布时间】:2010-10-27 06:38:01
【问题描述】:

我正在维护一个脚本,该脚本可以从各种来源获取输入,并按行处理。根据所使用的实际来源,换行符可能是 Unix 风格、Windows 风格,甚至对于某些聚合输入,可能是混合(!)。

当从文件中读取时,它是这样的:

@lines = <IN>;
process(\@lines);

...

sub process {
    @lines = shift;
    foreach my $line (@{$lines}) {
        chomp $line;
        #Handle line by line
    }
}

所以,我需要做的是将 chomp 替换为删除 Unix 样式或 Windows 样式的换行符的东西。 我想出了太多的方法来解决这个问题,这是 Perl 的常见缺点之一 :)

您对消除通用换行符的最佳方式有何看法?什么是最有效的?

编辑:一个小的澄清 - 方法“进程”从某处获取行列表,不一定从文件中读取。每行可能有

  • 没有尾随换行符
  • Unix 风格的换行符
  • Windows 风格的换行符
  • 只是回车(当原始数据具有 Windows 样式的换行符并且使用 $/ = '\n' 读取时)
  • 线条具有不同样式的聚合集

【问题讨论】:

  • 如果 操作符识别换行符,不会 chomp 吗?
  • 但是 操作符不能正确识别换行符,除了使用 是一种特殊情况,输入并不总是来自文件。
  • 要么运行我刚刚粘贴的代码,要么阅读它生成的附加输出。你会希望看到我想要表达的观点。 “混合”的情况是迄今为止最糟糕的。

标签: perl line-breaks


【解决方案1】:

2017 年的注意事项:由于设计错误和未维护的错误,不推荐使用 File::Slurp。请改用File::SlurperPath::Tiny

扩展你的答案

use File::Slurp ();
my $value = File::Slurp::slurp($filename);
$value =~ s/\R*//g;

File::Slurp 抽象出 File IO 的东西,只为你返回一个字符串。

注意

  1. 重要的是要注意添加 /g ,没有它,给定一个多行字符串,它只会替换 first 违规字符。

  2. 1234563 @ 在这个操作系统上。
  3. 在多行字符串中,$ 匹配 string 的结尾,这将是有问题的)。

  4. 第 3 点表示第 2 点是假设您还想使用 /m 否则 '$' 对于 >1 行的字符串中的任何实际操作基本上没有意义,或者,单行处理,一个真正理解$并设法找到继续$\R*的操作系统

示例

while( my $line = <$foo> ){
      $line =~ $regex;
}

鉴于上述符号,操作系统不理解您的文件“\n”或“\r”分隔符,在默认情况下,操作系统的默认分隔符设置为$/,将导致将整个文件读取为一个连续的字符串(除非您的字符串中有 $OS 的定界符,它将由它定界)

所以在这种情况下,所有这些正则表达式都是无用的:

  • /\R*$// :只会擦除文件中\R 的最后一个序列
  • /\R*// :只会擦除文件中\R 的第一个序列
  • /\012?\015?// :当只擦除第一个 012\015\012\015 序列时,\015\012 将导致 \012\015 被发射。

  • /\R*$// :如果文件中恰好没有 '\015$OSDELIMITER' 字节序列,则除了操作系统自己的换行符之外,NO 换行符将被删除。

似乎没人明白我在说什么,所以这里是示例代码,测试删除换行符。运行它,你会看到它留下了换行符。

#!/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 };
    }
}

对于CLEARLY未处理的输出,请参见此处:http://pastebin.com/f2c063d74

请注意,某些组合当然有效,但它们很可能是您自己天真地测试过的组合。

请注意,在此输出中,所有结果的格式必须为 &gt;|$string|&lt;&gt;|$string|&lt;NO LINE FEEDS 被视为有效输出。

$string 的一般形式是{0}$data{1}$delimiter{2},在所有输出源中,应该有:

  1. {1}{2} 之间没有任何内容
  2. 仅在{1}{2} 之间的|&lt;&gt;|

【讨论】:

  • 如果您在处理其内容之前剥离 每个 换行符,您如何知道换行符在哪里(例如,换行符构成新记录) ?
  • 任务是删除 all 换行,无论当前操作系统如何
  • 不,任务是从字符串列表中删除尾随换行符。
  • 那么你的整个提案就有缺陷了。因为如果您的读取换行符是 \015 并且看到了 \015 \012 ,则 \012 将 NEVER 被删除,因为它位于字符串的 START 处, 不是 END
  • 好吧,实际运行代码,使用 s/\R*$// 从行中删除 \015、\015\012 和 \012。
【解决方案2】:

为了扩展上面 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 'greedy' 修饰符,因此它会继续查找双空格,直到将它们全部替换。 (有效地替换单个空格以外的任何内容)

【讨论】:

    【解决方案3】:

    在你的例子中,你可以去:

    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
    

    【讨论】:

    • 我不认为 chomp 和其他事情一样——如果你在 unix 系统上有一个 dos 文件,它将把 \n 去掉,留下 \r * chomp这个更安全的 "chop" 版本删除了与 $/ 的当前值相对应的任何尾随字符串(在 "English" 模块中也称为 $INPUT_RECORD_SEPARATOR)。 *
    【解决方案4】:

    每当我通过输入并想要删除或替换字符时,我都会通过像这样的小子程序运行它。

    sub clean {
    
        my $text = shift;
    
        $text =~ s/\n//g;
        $text =~ s/\r//g;
    
        return $text;
    }
    

    这可能并不花哨,但这种方法多年来对我来说一直完美无缺。

    【讨论】:

    • 我打赌这个解决方案可能比条件正则表达式更有效。很好的答案。
    【解决方案5】:

    在对perlre 文档进行了一些深入研究之后,我将提出我目前看来效果不错的最佳建议。 Perl 5.10 添加了 \R 字符类作为通用换行符:

    $line =~ s/\R//g;
    

    同理:

    (?>\x0D\x0A?|[\x0A-\x0C\x85\x{2028}\x{2029}])
    

    我会暂时保留这个问题,看看是否有更多漂亮的方法等待提出建议。

    【讨论】:

    • 我鼓励你接受你自己的答案,如果它适合你。 \R 在某些特殊平台上可能无法按预期工作(这就是我之前建议硬连线方法的原因),但是如果您不喜欢编写可移植代码而只是想完成工作,那么您就完成了。您可能会考虑首先将 Kent Fredric 的测试文件放在您的代码中,因为它们确实是一个很好的测试用例。
    • 这也保存了我的问题。一直在寻找解决方案。
    【解决方案6】:

    阅读perlport 我建议类似

    $line =~ s/\015?\012?$//;
    

    对于您使用的任何平台以及您可能正在处理的任何换行样式都是安全的,因为 \r 和 \n 中的内容可能会因不同的 Perl 风格而有所不同。

    【讨论】:

    • 潜在错误:1) 没有 /g ,因此它不适用于多行字符串。 2) $ ,所以它只会匹配直接出现在字符串末尾之前的分隔符。 3) 修正了 \015 \012 的顺序,这样如果他们有 \012\015 它只会吃掉其中一个。
    • 1)+2) 由于我不知道行内的内容,我不得不假设其中可能有不应删除的换行符(例如,具有换行数据列的数据库记录)。我的意图是尽可能地匹配 chomp() 的行为。 3) 我见过旧的 Mac 只使用 \015,而 Windows 仍然使用 \015\012,但我还没有看到使用 \012\015 的真实系统,所以我觉得这个顺序是安全的。 ;)
    • 看看我更新的答案以及它发出的内容,在基于行的阅读中特别普遍存在的情况在您尝试之前并不明显。 ie: local $/ = "\015" # 突然你有很多 \012 出现在输出中。
    • 小心!只需合并两行,即可将 X 行的最后一个“单词”与 X+1 行的“第一个”单词连接起来。根据上下文,您可能不想删除,而是用空格(或其他分隔符)替换
    【解决方案7】:
    $line =~ s/[\r\n]+//g;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-15
      • 2015-04-11
      • 1970-01-01
      • 2011-08-21
      • 1970-01-01
      • 2012-06-07
      • 1970-01-01
      • 2010-09-18
      相关资源
      最近更新 更多