【问题标题】:Why the line terminator `\r\n` causes groups not to be matched?为什么行终止符 `\r\n` 导致组不匹配?
【发布时间】:2021-06-11 12:35:38
【问题描述】:

我正在使用 Perl v5.30 在 Linux 上使用 Windows 行终止符 (\r\n) 处理文本文件。

我不明白为什么,对于这些文本文件,捕获组不匹配字符,而正则表达式匹配。

例子:

$ echo $'Line1\r\nLine2\n' | perl -ne 'print /(.*)/'
Line2

$ echo $'Line1\r\nLine2\n' | perl -ne '/(.*)/ && print "match\n"'
match
match
match

第一行没有被捕获,但所有(三)行都匹配。

为什么会这样?

【问题讨论】:

  • 不要相信终端,它会骗你。总是使用一些东西以明确的形式转储数据。 B::perlstring() 是一个核心模块功能,可以很好地完成此操作。 perl -MB -nE 'say B::perlstring( /(.*)/ );
  • 您几乎不应该发明以与您遇到问题的方式不同的方式显示问题的问题。您说您在读取具有不同行尾的文件时遇到问题,并且此问题或此代码中不涉及任何文件。你还没有得到任何关于如何解决这些问题的建议。这种特殊情况是关于打印带有特殊字符的字符串时发生的情况。您的解决方案将是如何修复或删除错误的行尾。
  • @TLP 而且你永远不应该添加没有技术价值的 cmets,只是为了论证 - 代码在功能上与 cating 到文件相同,并将文件用作 Perl 输入.其实,由于上述逻辑避免了人们阅读多余的cat命令,你应该感谢我!
  • @Marcus 人们提出问题时常犯的错误是,就他们认为需要知道的事情提出问题以解决他们遇到的另一个问题。它被称为XY-problem。我的陈述是事实,而不是争论。坦率地说,我什至不知道您所说的“读取多余的 cat 命令”是什么意思,但我假设您是在谈论执行 cat foo.txt | perl -ne'....' 的常见新手错误,而您可以执行 perl -ne'...' foo.txt

标签: perl line-endings capturing-group


【解决方案1】:

但它是

$ echo $'Line1\r\nLine2\n' | perl -ne 'print /(.*)/' | od -c
0000000   L   i   n   e   1  \r   L   i   n   e   2
0000013

问题是您的终端在接收到 CR 时会将光标归位,因此 Line2 最终会覆盖 Line1

【讨论】:

    【解决方案2】:

    使用cat -vxxd 查看输出的真实内容。

    $ echo $'Line1\r\nLine2\n' | perl -ne 'print /(.*)/' | cat -v
    Line1^MLine2
    

    ^M 对应\r,它将光标移回行首,因此第二个匹配覆盖第一个。

    这解释了两个匹配,但第三个在哪里?添加一些东西来分隔匹配项:

     $ echo $'Line1\r\nLine2\n' | perl -ne 'print /(.*)/, "|"' | cat -v
    Line1^M|Line2||
    

    echo 在其输出中添加一个换行符,因此最后一行为空,但仍匹配 .*

    【讨论】:

      【解决方案3】:

      其他人已经向您展示了为什么输出隐藏了您期望看到的内容。但是,对于最初的问题,我会考虑处理这些行尾,这样你就不用考虑它们了。似乎你有混合行尾,所以我的第一个想法是找到有问题的程序并修复它的输出:)

      如果您不想要,请从组中排除垂直空格(\v),并选择您自己的输出行结尾(此处为-l):

      $ echo $'Line1\r\nLine2\n' | perl -nle 'print /([^\v]+)/'
      Line1
      Line2
      
      

      或者修改输入字符串得到你想要的:

      $ echo $'Line1\r\nLine2\n' | perl -nle 'print s/\R//r'
      Line1
      Line2
      
      

      也许预处理该行:

      $ echo $'Line1\r\nLine2\n' | perl -nle 's/\R// and print /(.*)/'
      Line1
      Line2
      
      

      或者可能是别的什么,所以没有什么可以解决的。

      【讨论】:

      • Re "或者其他的,所以没有什么可解决的",例如dos2unix 工具
      【解决方案4】:

      我无法确定您的问题是否已得到回答,但值得注意的是,在输入时,perl 将 \r\n 转换为 \n,然后,如果输出是 Widows,它确实输出相反。

      底线,如果你尝试匹配 \r\n,你很可能会失败 - 此外,如果你读取了例如包含 \r\n 的 10 个字节,然后在 perl 中检查输入的长度,它将只有 9 个字节,因为 \r 将消失。

      这实质上允许脚本跨多个平台工作,而无需将 \n 的引用更新为 \r\n,反之亦然 iyswim。

      例如在windows上,下面的脚本会返回6、5:

      while(<DATA>){
        print length . "\n";
      }
      
      __DATA__
      hello
      world
      

      但是,如果我添加“binmode DATA;”,我会得到 7, 5

      请注意,这是 iirc,特定于平台的。例如如果在bin模式下将windows文本文件传输到linux,在linux中读取文件时,“\r\n”不会被翻译成“\n”。

      【讨论】:

      • printf 'a\r\n' | perl -lnwe 'print /\r/' show 1,你确定在输入时,perl 将 /r/n 转换为 /n
      • 好吧,除了我使用 '/' 而不是 '\' (现在已修复),相当肯定。 while(){ 打印长度。 "\n"; } 数据你好世界
      • 如果我将 a\nb\r\nc\n 放入 DATA 中,我将得到 2 3 2 作为输出。所以在 Linux 上,Perl 不翻译任何东西(除非被告知)。
      • 在 Linux 上,不必这样做 - 行尾已经只是 "\n"。
      • Re "值得注意的是,在输入时,perl 会将 \r\n 转换为 \n",只是 Perl 的 Windows 构建,这里不相关。
      猜你喜欢
      • 2012-10-24
      • 2013-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-23
      • 1970-01-01
      • 2011-07-09
      相关资源
      最近更新 更多