【问题标题】:Why can 'hallo\nworld' match both \n and \\n in R?为什么 'hallo\nworld' 可以同时匹配 R 中的 \n 和 \\n?
【发布时间】:2013-12-07 07:32:13
【问题描述】:

为什么grep 对待\n\\n 的方式相同?

例如,两者都匹配hallo\nworld

grep("hallo\nworld", pattern="\n")
[1] 1
grep("hallo\nworld", pattern="\\n")
[1] 1

我看到hallo\nworld被解析成

hallo  
world

也就是说,hallo 在一行上,world 在一行上。

那么在grep("hallo\nworld", pattern="\n") 中,pattern="\n" 是换行还是字面意义上的\n

另请注意,其他人也会发生这种情况; \a \f \n \t \r\\a \\f \\n \\t \\r 都被同等对待。但是\d\w\s不能用!为什么不呢?

我选择了不同的字符串进行测试,发现了正则表达式概念中的秘密。

转义有两种概念,一种是字符串转义,简单易懂;另一种是在正则模式表达式字符串中转义。在 R 中,grep(x, pattern=" some string here ")\\n=\n= 等模式是换行符。但在普通字符串中,\\n!=\n,前者字面意思是\n,后者是换行符。我们可以通过以下方式证明这一点:

cat("\n")

cat("\\n")
\n> 

如何证明这一点?我会尝试使用其他字符,而不仅仅是 \n,看看它们是否以相同的方式匹配。

special1 <- c( "\a", "\f", "\n", "\t", "\r")
special2 <- c("\\a","\\f","\\n","\\t","\\r")
target <- paste("hallo", special1, "world", sep="")
for (i in 1:5){
    cat("i=", i, "\n")
    if( grep(target[i], pattern=special1[i]) == 1)
        print(paste(target[i], "match", special1[i], "succeed"))
    if( grep(target[i], pattern=special2[i]) == 1)
        print(paste(target[i], "match", special2[i], "succeed"))
}

输出:

i= 1   
[1] "hallo\aworld match \a succeed"  
[1] "hallo\aworld match `\\a` succeed"  
i= 2   
[1] "hallo\fworld match \f succeed"  
[1] "hallo\fworld match `\\f` succeed"  
i= 3   
[1] "hallo\nworld match \n succeed"  
[1] "hallo\nworld match `\\n` succeed"  
i= 4   
[1] "hallo\tworld match \t succeed"  
[1] "hallo\tworld match `\\t` succeed"  
i= 5   
[1] "hallo\rworld match \r succeed"  
[1] "hallo\rworld match `\\r` succeed" 

请注意,\a \f \n \t \r\\a \\f \\n \\t \\r 在 R 正则表达式字符串中的处理方式相同>

不仅如此,你不能在R正则表达式模式中写\d\w\s
您可以编写以下任何一种:

pattern="\a" "pattern=\f" "pattern=\n" "pattern=\t" "pattern=\r"

但你不能写任何这些!

pattern="\d" "pattern="\w" "pattern=\s"  in grep.

我认为这也是一个错误,因为 \d \w \s 被不平等对待 \a \f \n \t \r

【问题讨论】:

  • 其实反过来:\n\\n 都匹配"hallo\nworld",或者更准确地说,是"hallo\nworld" 的子串。另外,请更准确地回答您的问题:您想实现什么?你为什么要关心差异?是否存在pattern 是用户输入的应用程序?简而言之,请更详细! (另外,在逗号之前加一个空格,在之后加一个空格,真的很碍眼。真的是“word”“comma”“space”“word”。)跨度>
  • 你为什么不写你最后的编辑作为答案?
  • 如果你确信这是一个错误,那么你应该写信给R-devel。使用 SO 的 R Core 成员相对较少,因此他们不太可能在这里看到它。
  • \d\w\s而言,与\a\f\n\t\r的区别在于是字符类,而不是字符。字符转义序列在任何字符串中都有其含义,而字符类则没有。

标签: regex r escaping


【解决方案1】:

\n\\n\\\n 都匹配的原因是搜索模式的双重评估。我通过运行几个示例观察到这一点:

grep("hello\nworld", pattern="\n")
[1] 1
grep("hello\nworld", pattern="\\n")
[1] 1
> grep("hello\nworld", pattern="\\\n")
[1] 1
> grep("hello\nworld", pattern="\\\\n")
integer(0)
> grep("hello\\nworld", pattern="\\\\n")
[1] 1

记住计算反斜杠转义序列的规则:

  • \\ 替换为 \
  • \n 被替换为 NEWLINE 字符
  • \ + NEWLINE 被替换为 NEWLINE 字符
  • (有关详细信息,请参阅?regex 中的文档)

考虑到这一点,如果你评估模式两次,你会得到:

  1. \n => NEWLINE => NEWLINE
  2. \\n => \n => NEWLINE
  3. \\\n => \ + NEWLINE => NEWLINE
  4. \\\\n => \\n => \n
  5. \\\\\n => \\ + NEWLINE => \ + NEWLINE
  6. \\\\\\n => \\\n => \ + NEWLINE
  7. \\\\\\\n => \\\ + NEWLINE => \ + NEWLINE
  8. \\\\\\\\n => \\\\n => \\n

等等。示例 1-3 都评估为单个 NEWLINE,这就是这些模式将匹配的原因。 (同时,您尝试与模式匹配的字符串只计算一次。)

@Aaron 发布的A discussion on the R mailing list 对双重评估的解释如下:

[评估] 有两个级别,因为反斜杠是转义字符 R 字符串和正则表达式。

请注意,其他语言不会像这样评估模式。以 Python 为例:

import re
>>> re.search(r'\n', 'hello\nworld') is not None
True
>>> re.search(r'\\n', 'hello\nworld') is not None
False

或 Perl:

$ perl -e 'print "hello\nworld" =~ /\n/ || 0, "\n"'
1
$ perl -e 'print "hello\nworld" =~ /\\n/ || 0, "\n"'
0

我们可以继续。所以R 中的双重评估似乎不寻常。为什么以这种方式实施?我认为最终的答案在于R-devel

致谢

我感谢 @Aaron 的关键 cmets 帮助改​​进了这个答案。

【讨论】:

  • 这听起来不错,但我很确定"\\n""\n" 都已经是字符串,所以对它们调用as.character 没有任何效果。
  • 是的,你的最后一句话。看我的回答。
  • R 计算反斜杠(不是模式)两次,因为在 R 字符串和正则表达式中都需要对反斜杠进行转义。看我的回答。
  • 另外,我建议编辑您的答案,将实际答案放在首位并删除错误的部分。
  • 好多了,谢谢。虽然我不认为Internal 的东西很重要,所以我认为这会分散答案的注意力。
【解决方案2】:

注意反斜杠本身是特殊的,你必须用反斜杠转义反斜杠。

\\n 的意思是“我真的想匹配换行符,而不是文字 \n

grep("hallo\nworld", pattern = "\\n")
[1] 1

grep("hallo\\nworld", pattern = "\\\\n")
[1] 1

【讨论】:

  • 或者使用fixed = TRUE参数...grep("hallo\\nworld", pattern = "\\n" , fixed = TRUE )
  • 如果\\n被解析为换行符,\n被解析为文字\n\n中的hallo\nworld被解析为换行符或文字\nhallo\nworld同时匹配\n\\n,R正则表达式和grep函数存在bug,逻辑矛盾。
  • 是的,我认为这可能是一个错误。
【解决方案3】:

跟进hwnd的回答,看看以下内容:

cat("x\ny")
## x
## y
cat("x\\ny")
## x\ny
grep("hallo\nworld", pattern="[\n]")
## [1] 1
grep("hallo\nworld", pattern="[\\n]")
## integer(0)

所以:"\n" 是文字换行符,"\\n" 是反斜杠 + ngrep 将其解释为换行符。这就是为什么在我的第一个示例中找到匹配项(搜索集合{ newline } 中的任何字符),而在我的第二个示例中未找到匹配项(搜索集合{ \ n } 中的任何字符)。

这不是错误,这是完全预期的行为。关于这一点,真的并且绝对确定,你为什么不post R-help 或 R-devel?

【讨论】:

  • 不,先生,这是个问题,pattern="[\\n]" 是\+n 两个字符,所以grep("hallo\nworld", pattern="[\\n]") 无法匹配。
  • @it_is_a_literature:是的,这就是我们所看到的,也是我所期望的,也是我写的。对不起,如果我的解释不够清楚。
  • 毫无疑问hallo\nworld 可以匹配其中之一:\n\\n。 mcuh 怀疑为什么 hallo\nworld 可以匹配它们:\n\\n。 hwnd觉得可能是bug,我也觉得,你不这么认为,请直接回答问题,为什么可以同时匹配,你解释一下为什么可以匹配其中一个以及原因,另一个情况如何?
  • +1 是的,这个。不过,我没有立即看到它,因为括号示例让我感到困惑。
【解决方案4】:

这确实是由于lebatsnok提到的“双重转义”。正如 Peter Dalgaard 在 R-help 上所写,“反斜杠是 R 字符串和正则表达式的转义字符。”见https://stat.ethz.ch/pipermail/r-help/2003-August/037524.html。另请参阅?regex 中关于加倍反斜杠的说明,尽管对我来说这不像 Dalgaard 的评论那么清楚。

所以\n 在第一遍中成为换行符,在第二遍中保持不变。

\\n 在第一遍中变成\n (\\->\),然后在第二遍中变成换行符。

\\\n 变成 \ 后跟第一遍中的换行符,这显然在第二遍中变成了换行符,因为它也匹配。

另外,关于 a、f、n、t 和 r 允许有反斜杠但 d、w 和 s 不允许的问题,请注意,这些是具有特殊含义的特定元字符,如 ?regex 中所述:

当前实现解释 '\a' 为 'BEL', '\e' 为 'ESC', '\f' 为 'FF', '\n' 为 'LF', '\r' 为 'CR' 和 '\t' 作为 'TAB'。

【讨论】:

  • 在重读@krlmlr 的回答时,我注意到这正是那里所说的;我可能对带括号的示例感到困惑。
  • \\\n 成为换行符,因为转义的换行符自然是换行符,就像转义的空格是空格一样,依此类推。 ?regex 帮助解释了哪些转义序列具有特殊含义,例如 \t 是一个选项卡,并且所有未在此处列出的内容都会对其进行评估,因此 \NEWLINE -&gt; NEWLINE
【解决方案5】:

因为字符串hallo\nworld在解析时包含文字文本\n以及line feed字符。

如果你的字符串实际上是:

hallo
world

然后它将仅匹配 \n 而不是 \\n

【讨论】:

  • 错误。当你在字符串中间开始换行时,这意味着“\n”(即换行符)
  • 就像我说的......它包含它作为一个文字字符串,并且在解析时它是一个LF字符。通俗易懂,没有错。
  • 试试source("http://www.psych.ee/ajutine/easy.R", echo=TRUE)。如果如你所说,结果应该是 FALSE 而不是现在的样子。
  • 太棒了 - 仍然没有解释为什么字符串同时匹配 \n\\n
猜你喜欢
  • 2011-04-18
  • 1970-01-01
  • 2013-12-02
  • 2022-09-26
  • 2013-03-04
  • 1970-01-01
  • 2021-06-11
  • 2022-01-01
  • 2011-04-28
相关资源
最近更新 更多