4

我想使用 Ruby 正则表达式匹配不包含字符串 'localhost' 的 url

根据此处的答案和评论,我将两种解决方案放在一起,这两种解决方案似乎都有效:

解决方案 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  

但我不明白它是如何在这里应用的。

有人可以为我分解这些表达方式,以及它们之间的区别吗?

4

4 回答 4

5

在您的两种情况下,^这只是行的开头(因为它没有在字符类中使用)。由于两者^和前瞻都是零宽度断言,我们可以在第一种情况下切换它们 - 我认为这更容易解释:

^(?!.*localhost).*$ 

^表达式锚定到字符串的开头。然后从该位置开始前瞻并尝试找到localhost字符串的任何位置(“任何位置”由.*前面的 处理localhost)。如果localhost可以找到,则前瞻的子表达式匹配,因此前瞻会导致模式失败。由于前瞻被相邻的 this 方式绑定在字符串的开头^,因此模式整体无法匹配。但是,.*localhost如果 不匹配(因此localhost不会出现在字符串中),则前瞻成功,并且.*$简单地负责匹配字符串的其余部分。

现在另一个

^((?!localhost).)*$

这次先行只检查当前位置(里面没有.*)。但是对于每个字符都会重复前瞻。这样,它会再次检查每个位置。这大致是发生了什么:^确保我们再次从字符串的开头开始。前瞻检查是否localhost在该位置找到了单词。如果不是,一切都很好,并.消耗一个字符。然后*重复这两个步骤。我们现在是字符串中的一个字符,并且前瞻检查是否是第二个字符开始单词localhost- 再次,如果不是,一切都很好,并.消耗另一个字符。这是针对字符串中的每个字符完成的,直到我们到达末尾。

在这种特殊情况下,两种方法都是等效的,您可以根据性能(如果重要)或可读性(如果不重要;可能是第一种)来选择一种。但是,在其他情况下,第二个变体更可取,因为它允许您对字符串的固定部分执行此重复,而第一个变体将始终检查整个字符串。

于 2013-08-18T15:41:15.860 回答
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
--------------------------------------------------------------------------------
于 2013-08-18T15:41:52.253 回答
3

顺便说一句,这两种解决方案都很慢。更好的方法是使用:

^(?:[^l]+|l(?!ocalhost))+

换句话说:所有不是 al或 a 的字符l都不跟在后面ocalhost

这将为您提供更好的结果,因为您不必检查每个位置。(对于像http://localhost:1234/toto这种模式的 url 将在 ~15 步内失败,而其他两种模式在 ~50 步内失败)

您可以使用原子组和所有格量词来改进此模式以禁止回溯:

^(?>[^l]++|l(?!ocalhost))++

请注意,在您的特定情况下,您可以加快您的模式,因为您只想检查 url 的主机部分。例子:

^http:\/\/(?>[^l\s\/]++|l(?!ocalhost))++(?>\/\S*+|$)
于 2013-08-18T15:51:25.897 回答
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)^.*$

就像“行首”^一样,前瞻是零宽度。这意味着前瞻扫描字符串以寻找匹配项,但是当它找到匹配项时,它会返回到字符串的开头,然后查找正则表达式的其余部分:

^.*$

一句忠告,当一个正则表达式需要环视(lookaheads 或lookbehinds)时,99% 的时间有更简单的方法来做你想做的事。例如,你可以写:

url = "....."

if url.index('http') == 0
   #then the line starts with 'http'
else
   #the line doesn't start with http
end

这更容易阅读,并且不需要尝试破译复杂的正则表达式。

于 2013-08-18T16:00:14.823 回答