String#match
String#match searches a string for a pattern and returns a MatchData object if found, or nil if not.
It is one of the most direct ways to ask whether text contains a pattern and, if so, where that pattern begins. Because the return value includes captures and position data, it is often more useful than a simple boolean check.
Signature
str.match(pattern, pos = 0)
Parameters:
pattern— a Regexp or Stringpos— optional starting position (Integer)
Returns: MatchData, or nil if no match. The optional pos argument lets you begin the search from a specific character index, which is useful when you need to find matches after skipping a known prefix.
Basic Matching
Match a pattern and get back a MatchData object:
"hello".match(/o/) # => #<MatchData "o">
"hello".match(/x/) # => nil
The MatchData object contains the full match and any capture groups. When the pattern is a regexp, you also get access to named captures, quantifier information, and the starting and ending positions of each group. This makes match more powerful than a simple boolean check for cases where you need to extract structured data from text.
using a string as the pattern
You can pass a string — it gets converted to a Regexp:
"hello".match("o") # => #<MatchData "o">
"hello".match("l") # => #<MatchData "l">
Passing a string is convenient when the pattern is simple and you do not need full regexp syntax. Ruby still treats it like a pattern match, so the return value gives you the same MatchData object as the regexp version.
getting match details
The MatchData object gives you access to the match and its position:
m = "hello".match(/o/)
m[0] # => "o" (the full match)
m.begin(0) # => 4 (character position)
m = "hello".match(/(.)o/)
m[0] # => "lo" (full match)
m[1] # => "l" (first capture group)
m.begin(1) # => 3
MatchData is useful because it keeps the match context together. Instead of only knowing that something matched, you can inspect where it matched and what each capture group contained. The ability to query the beginning and ending offsets of each group is what makes regex-based parsers practical in Ruby.
capture groups
Access numbered and named capture groups:
m = "john doe".match(/(?<first>\w+) (?<last>\w+)/)
m[0] # => "john doe"
m[1] # => "john"
m[2] # => "doe"
m[:first] # => "john"
m[:last] # => "doe"
If no match, match returns nil — calling m[1] on nil raises an error.
That nil check is the main thing to remember when you plan to read captures. The API is expressive, but it still assumes the pattern succeeded before you access the captures. A guard clause like if m = str.match(/pattern/) keeps the match and the nil-check together, making the code both concise and safe.
pre-match and post-match
Get the text before and after the match:
m = "hello".match(/l+/)
m.pre_match # => "he"
m.post_match # => "o"
These helpers are handy when you need to split a string around the matched section without writing the slicing logic yourself. They are especially useful in parsers and text-rewrite code. The pre-match and post-match values together with the match itself form a complete partition of the original string, which makes replacement and extraction logic straightforward without manual index arithmetic. Together, the three segments cover every character in the input exactly once, so you can rebuild or transform the string piece by piece.
starting position
The second argument lets you start searching from a given position:
"hello".match(/o/) # => #<MatchData "o">
"hello".match(/o/, 0) # => #<MatchData "o">
"hello".match(/o/, 5) # => nil (past the end)
"hello".match(/o/, 3) # => #<MatchData "o"> (from char 3)
This is useful when you need to find matches after a certain point in the string.
Limiting the starting position can make searches cheaper and more precise. It is also a good way to skip over a known prefix when you only care about what appears later in the text.
using a block
When given a block, match returns the block’s return value:
"hello".match(/[aeiou]/) { |m| m[0].upcase } # => "E"
"hello".match(/x/) { |m| m[0] } # => nil
The block receives the MatchData object.
The block form is less common than the plain match form, but it can be useful when you want to transform the match immediately. That keeps the matching and the follow-up logic in one place.
match vs other methods
String#match is shorthand for calling Regexp#match on the string:
"hello".match(/o/)
# is equivalent to:
Regexp.new("o").match("hello")
Compare with String#=~ which returns the position (or nil). This operator is the most concise way to ask “does this pattern appear anywhere in the string, and if so, where?” It is common in one-liners and conditionals where a position index is enough for the next step.
"hello" =~ /o/ # => 4
"hello" =~ /x/ # => nil
And String#match? which returns true/false. This method is faster than match when you only need a yes-or-no answer because it does not allocate a MatchData object. Use it in tight loops and performance-sensitive code where the match details are irrelevant.
"hello".match?(/o/) # => true
"hello".match?(/x/) # => false
Use match? when you only want a boolean result. Use match when you need the actual match data, captures, or positions. The three methods form a clear hierarchy: match? for yes-or-no, =~ for position, and match for full match details. Pick the one that matches the calling code’s needs rather than always reaching for the most powerful option.
practical examples
safely accessing capture groups
Always check for nil before accessing groups:
def extract_year(text)
m = text.match(/(\d{4})/)
if m
m[1]
else
nil
end
end
extract_year("Version 2024") # => "2024"
extract_year("No year") # => nil
Checking for nil before reading the capture keeps the code from raising when the pattern does not match. That is the safest style when the input may or may not contain the data you want.
multiple patterns
Try multiple patterns in sequence:
def classify_input(input)
case input
when input.match?(/^\d+$/)
"integer"
when input.match?(/^\d+\.\d+$/)
"float"
when input.match?(/^[a-zA-Z]+$/)
"word"
else
"unknown"
end
end
classify_input("42") # => "integer"
classify_input("3.14") # => "float"
classify_input("hello") # => "word"
The case form reads well when you are classifying a few mutually exclusive patterns. Each branch stays focused on one rule, and the fallback case handles anything that does not fit.
See Also
- /reference/string-methods/match-p/ — returns true or false
- /reference/string-methods/scan/ — find all matches
- /reference/string-methods/sub/ — replace first match