我希望使用Ruby regex匹配不包含字符串'localhost'的网址
基于答案和评论here,我将两个解决方案放在一起,这两个解决方案似乎都有效:
解决方案A:
(?!.*localhost)^.*$
示例:http://rubular.com/r/tQtbWacl3g
解决方案B:
^((?!localhost).)*$
示例:http://rubular.com/r/2KKnQZUMwf
问题在于我不明白他们在做什么。例如,根据文档,^
可以以各种方式使用:
[^abc] Any single character except: a, b, or c
^ Start of line
但我不明白它是如何在这里应用的。
有人可以为我分解这些表达方式,以及它们之间的区别吗?
答案 0 :(得分:5)
在两种情况下,^
只是行的开头(因为它不在字符类中使用)。由于^
和前瞻都是零宽度断言,我们可以在第一种情况下切换它们 - 我认为这样可以更容易解释:
^(?!.*localhost).*$
^
将表达式锚定到字符串的开头。然后,前瞻从该位置开始,并尝试在字符串的任何位置找到localhost
(“任何地方”由.*
前面的localhost
处理)。如果可以找到localhost
,则前瞻的子表达式匹配,因此否定前瞻导致模式失败。由于前瞻被绑定在字符串的开头由相邻的^
开始,这意味着整个模式无法匹配。但是,如果.*localhost
不匹配(因此localhost
没有出现在字符串中),则前瞻成功,而.*$
只需要匹配其余字符串。
现在另一个
^((?!localhost).)*$
这一次,前瞻只检查当前位置(里面没有.*
)。 但每个字符都会重复前瞻。这样它可以再次检查每个位置。以下是大致发生的情况:^
确保我们再次从字符串的开头开始。前瞻检查是否在该位置找到了单词localhost
。如果没有,一切都很好,.
消耗一个字符。 *
然后重复这两个步骤。我们现在是字符串中的一个字符,并且前瞻检查第二个字符是否开始单词localhost
- 如果不是,则一切都很好,.
消耗另一个字符。这是为字符串中的每个字符完成的,直到我们到达结尾。
在这种特殊情况下,两种方法都是等效的,您可以根据性能(如果重要)或可读性(如果不是;可能是第一种)选择一种方法。但是,在其他情况下,第二个变体是首选,因为它允许您对字符串的固定部分执行此重复,而第一个变体将始终检查整个字符串。
答案 1 :(得分:3)
NODE EXPLANATION
--------------------------------------------------------------------------------
(?! look ahead to see if there is not:
--------------------------------------------------------------------------------
.* any character except \n (0 or more times
(matching the most amount possible))
--------------------------------------------------------------------------------
localhost 'localhost'
--------------------------------------------------------------------------------
) end of look-ahead
--------------------------------------------------------------------------------
^ the beginning of the string
--------------------------------------------------------------------------------
.* any character except \n (0 or more times
(matching the most amount possible))
--------------------------------------------------------------------------------
$ before an optional \n, and the end of the
string
--------------------------------------------------------------------------------
' '
NODE EXPLANATION
--------------------------------------------------------------------------------
^ the beginning of the string
--------------------------------------------------------------------------------
( group and capture to \1 (0 or more times
(matching the most amount possible)):
--------------------------------------------------------------------------------
(?! look ahead to see if there is not:
--------------------------------------------------------------------------------
localhost 'localhost'
--------------------------------------------------------------------------------
) end of look-ahead
--------------------------------------------------------------------------------
. any character except \n
--------------------------------------------------------------------------------
)* end of \1 (NOTE: because you are using a
quantifier on this capture, only the LAST
repetition of the captured pattern will be
stored in \1)
--------------------------------------------------------------------------------
$ before an optional \n, and the end of the
string
--------------------------------------------------------------------------------
答案 2 :(得分:3)
作为旁听,这两种解决方案都很慢。更好的方法是使用:
^(?:[^l]+|l(?!ocalhost))+
换句话说:所有不是l
或l
的字符都没有跟ocalhost
这样可以获得更好的结果,因为您无需检查每个位置。 (对于像http://localhost:1234/toto
这样的网址,这种模式会在~15步中失败,对于其他两种模式会失败~50步
您可以使用原子组和占有量词来改进此模式以禁止回溯:
^(?>[^l]++|l(?!ocalhost))++
请注意,在您的特定情况下,考虑到您只想检查网址的主机部分,您可以加快模式。例如:
^http:\/\/(?>[^l\s\/]++|l(?!ocalhost))++(?>\/\S*+|$)
答案 3 :(得分:2)
根据文档,^可以以各种方式使用:
[^abc] Any single character except: a, b, or c ^ Start of line
但我不明白它是如何在这里应用的。
在正则表达式中
(?!.*localhost)^.*$
^不在任何括号内,因此第二个适用。这是一个简单的例子:
/^x/
正则表达式表示匹配行的开头,后跟字母x。所以它会匹配这样的行:
xcellent
x-ray
但是,正则表达式与行不匹配:
axb
excellent
...因为x在行开始后不会直接出现。您可能想知道为什么'axb'不匹配。毕竟'a'是该行的开头,然后是'x'。但是,“行首”恰好位于第一个字符的左侧,如下所示:
|
V
axb
^被称为零宽度匹配,因为它匹配'a'左侧的细长条,例如,在起始引号和“axb”中的“a”之间。那里没有任何空间,所以^匹配0宽度的东西。
这是另一个例子:
/x^/
这表示匹配字符x后跟行的开头。好吧,没有一行可以首先是x,然后是第二行的开头,所以不会匹配任何东西。
现在你的正则表达式:
(?!.*localhost)^.*$
与“行首”相似,前瞻是零宽度。这意味着前瞻扫描字符串寻找匹配,但是当它找到匹配时,它返回到字符串的开头,然后查找正则表达式的其余部分:
^.*$
一句建议,当正则表达式需要 lookarounds (向前看或看后方)时,99%的时间有更简单的方法来做你想要的。例如,你可以写:
url = "....."
if url.index('http') == 0
#then the line starts with 'http'
else
#the line doesn't start with http
end
这更易于阅读,并且不需要尝试破译复杂的正则表达式。