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

Fre*_*ind 29 ruby regex

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

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

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

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


UPDATE

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

[ruby] [regex] [rails] one line
Run Code Online (Sandbox Code Playgroud)

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

Ant*_*sky 45

你需要这个Regexp#match方法.如果你写/\[(.*?)\](.*)/.match('[ruby] regex'),这将返回一个MatchData对象.如果我们称之为该对象matches,那么,除其他外:

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

有更多的方法,对应于其他特殊变量等; 你可以检查一下MatchData文档了解更多.因此,在这种特定情况下,您需要编写的所有内容都是

tag, keyword = /\[(.*?)\](.*)/.match('[ruby] regex').captures
Run Code Online (Sandbox Code Playgroud)

编辑1:好的,对于你更难的任务,你会想要使用@Theo使用的String#scan方法; 但是,我们将使用不同的正则表达式.以下代码应该有效:

# 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
Run Code Online (Sandbox Code Playgroud)

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

[ ["ruby", ""], ["regex", ""], ["rails", "one line "]
, ["foo", ""], ["bar", "baz"] ]
Run Code Online (Sandbox Code Playgroud)

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

[["ruby", "regex", "rails", "foo", "bar"], ["", "", "one line ", "", "baz"]]
Run Code Online (Sandbox Code Playgroud)

然后,您可以根据结果执行任何操作.例如,我可能会建议

search_str = texts.join(' ').strip.gsub(/\s+/, ' ')
Run Code Online (Sandbox Code Playgroud)

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


The*_*heo 11

'[ruby] regex'.scan(/\[(.*?)\](.*)/)
Run Code Online (Sandbox Code Playgroud)

将返回

[["ruby", " regex"]]
Run Code Online (Sandbox Code Playgroud)

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

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

tag, keyword = '[ruby] regex'.scan(/\[(.*?)\](.*)/).flatten
Run Code Online (Sandbox Code Playgroud)

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

/^\s*\[(.*?)\]\s*(.+)\s*$/
Run Code Online (Sandbox Code Playgroud)

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

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

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"
Run Code Online (Sandbox Code Playgroud)

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

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

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