如何使用一行正则表达式来获取匹配的内容

时间:2010-06-22 06:31:45

标签: ruby regex

我是红宝石的新手,我想知道我是否只能使用一行来完成这项工作。

以此网站的“搜索”为例。当用户键入[ruby] regex时,我可以使用以下代码来获取标记和关键字

'[ruby] regex' =~ /\[(.*?)\](.*)/
tag, keyword = $1, $2

我们可以把它写成一行吗?


更新

非常感谢你!我可以更难,更有趣的是,输入可能包含多个标签,例如:

[ruby] [regex] [rails] one line

是否可以使用一行代码来获取tags数组和关键字?我试过了,但失败了。

3 个答案:

答案 0 :(得分:43)

您需要Regexp#match方法。如果您编写/\[(.*?)\](.*)/.match('[ruby] regex'),则会返回MatchData个对象。如果我们调用该对象matches,那么,除其他外:

  • matches[0]返回整个匹配的字符串。
  • matches[n]返回第n个捕获组($n)。
  • matches.to_a返回一个由matches[0]matches[N]组成的数组。
  • matches.captures返回一个仅包含捕获组的数组(matches[1]matches[N])。
  • matches.pre_match返回匹配字符串之前的所有内容。
  • matches.post_match返回匹配字符串后的所有内容。

还有更多方法,对应其他特殊变量等;您可以查看MatchData's docs了解更多信息。因此,在这种特定情况下,您需要编写的所有内容都是

tag, keyword = /\[(.*?)\](.*)/.match('[ruby] regex').captures

编辑1:好的,对于您的难度较大的任务,您需要String#scan方法,@Theo使用的方法;但是,我们将使用不同的正则表达式。以下代码应该有效:

# You could inline the regex, but comments would probably be nice.
tag_and_text = / \[([^\]]*)\] # Match a bracket-delimited tag,
                 \s*          # ignore spaces,
                 ([^\[]*) /x  # and match non-tag search text.
input        = '[ruby] [regex] [rails] one line [foo] [bar] baz'
tags, texts  = input.scan(tag_and_text).transpose

input.scan(tag_and_text)将返回标记搜索文本对列表:

[ ["ruby", ""], ["regex", ""], ["rails", "one line "]
, ["foo", ""], ["bar", "baz"] ]

transpose调用会翻转,以便您拥有一个由标记列表和搜索文本列表组成的对:

[["ruby", "regex", "rails", "foo", "bar"], ["", "", "one line ", "", "baz"]]

然后,您可以随意对结果做任何事情。我可能会建议,例如

search_str = texts.join(' ').strip.gsub(/\s+/, ' ')

这会将搜索片段与单个空格连接起来,摆脱前导和尾随空格,并用单个空格替换多个空格的运行。

答案 1 :(得分:11)

'[ruby] regex'.scan(/\[(.*?)\](.*)/)

将返回

[["ruby", " regex"]]

你可以在这里阅读更多关于String#scan的内容:http://ruby-doc.org/core/classes/String.html#M000812(简而言之,它返回所有连续匹配的数组,在这种情况下,外部数组是匹配数组,内部是捕获组一场比赛)。

进行分配你可以像这样重写它(假设你在字符串中只有一个匹配):

tag, keyword = '[ruby] regex'.scan(/\[(.*?)\](.*)/).flatten

根据您想要完成的内容,您可能希望将正则表达式更改为

/^\s*\[(.*?)\]\s*(.+)\s*$/

匹配整个输入字符串,并修剪第二个捕获组中的一些空格。将模式锚定到开始和结束将使它更有效,并且它将避免在某些情况下获得错误或重复匹配(但这在很大程度上取决于输入) - 它还保证您可以安全地使用返回分配中的数组,因为它永远不会有多个匹配。

关于后续问题,这就是我要做的事情:

def tags_and_keyword(input)
  input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) do |match|
    tags = match[0].split(/\]\s*\[/)
    line = match[1]
    return tags, line
  end
end

tags, keyword = tags_and_keyword('[ruby] [regex] [rails] one line')
tags # => ["ruby", "regex", "rails"]
keyword # => "one line"

它可以在一行中重写,但我不会:

tags, keyword = catch(:match) { input.scan(/^\s*\[(.+)\]\s+(.+)\s*$/) { |match| throw :match, [match[0].split(/\]\s*\[/), match[1]] } }

我的解决方案假设所有标记都位于关键字之前,并且每个输入中只有一个标记/关键字表达式。第一个捕获全部变量标签,但后来我拆分了这个字符串,所以这是一个两步过程(正如@Tim在他的评论中写的那样,除非你有一个能够递归匹配的引擎,否则是必需的。)

答案 2 :(得分:0)

将其放入您的ApplicationHelper或您需要的其他地方

def element_id_for(f, element)
  matcher   = /id=(".*"|'.*')/
  el_string = f.hidden_field(element.to_sym)
  id_string = matcher.match(el_string)[0].gsub(/id="/, '').chomp('"')
  return    id_string
end

最后,您可以像这样使用此方法:

form_for :test_form do |f|
  my_id = element_id_for(f, :start_date)
  # => "text_form_start_date"
end