What the Regex?

Regexes, the go-to-mechanism for string matching, must not only be written, but also need to be applied. This episode acts as a reference with some style advice for working with regular expressions in Ruby. If you are looking for resources on writing the actual regexes, take a look at the link collection at the bottom.

What do you Want to Achieve?

1 - Task: Check if Regex Matches

1a) match?

This is the preferred way to check for a match since Ruby 2.4. It only returns true or false, but does not store any match data to get more performance:

"string".match? /1.3/ # => false
"123".match? /1.3/ # => true

1b) =~

This method is baked into Ruby's syntax, although its return value is rather special: It is the codepoint index in the string where the match occurred or nil otherwise. However, it is a wise choice to only use it for its truthy/falsey value and use the more self-explaining String#index method otherwise. Other than with the previous' match? approach, match data is set accordingly (this is the case with all other ways of matching) - see next section "Find First Occurrence" for ways to do so. Here is the example:

"string" =~ /1.3/ # => false
"123" =~ /1.3/ # => true

The match operator's sibling is !~ which negates the match result:

"string" !~ /1.3/ # => true
"123" !~ /1.3/ # => false

More complicated matching can involve capture groups. Depending on the reference style (named or numbered), the way you can access it differs:

Numbered: $1-$9

The Perlish special variables contain the matches:

"String with 42 things" =~ /(\d+) things/
$1 # => "42"

Named: $~

The match data object contains the matches:

"String with 42 things" =~ /(?<thing_count>\d+) things/
$~[:thing_count] # => "42"

Note that regex matching with named captures can implicitly create local variables. This is extremely confusing and you should rather use the above syntax which is clearer, yet still maintains conciseness.

1c) Case Compare

Regex' === operator is also mapped to matching strings (returns true or false). However, although it should not be used directly¹, it allows you to write very expressive and readable case statements²:

case variable = "string or number"
when /\A\d+\z/
  variable.to_i
when /\A\d+\.\d+\z/
  variable.to_f
else
  variable.to_s
end

¹ The reason: It depends on the order of both operands, regex must be first, which is rather unintuitive. String's === operator has a different semantic of just comparing two strings
² For more general documentation about equalness in Ruby, checkout Episode 55: Struggling Four Equality.

2 - Task: Find Single/First Occurrence

2a) String#[]

A very readable way to to return the match result of the string is:

"String with 42 things"[/\d\d/] # => "42"

You can also use capture groups here:

"String with 42 things"[/\d(\d)/, 1] # => "2"
"String with 42 things"[/(?<first>\d)\d/, :first] # => "4"

2b) =~ + $&

If you prefer the =~ syntax, you can retrieve the matched string with the special variable $&:

"String with 42 things" =~ /\d+/
$& # => "42"

2c) String#rindex

Worth mentioning is the special behavior of String.rindex. It will start the match process on the right side of string and return the first index, where a match is possible:

 "String with 42, sorry with 23 things".rindex /\d+/
 $& # => "3"

Note that it does not match "23", but "3". If you want to match an expression in relation to the end of the string you could use a positive-lookahead in combination with \z:

"String with 42, sorry with 23 things" =~ /\d+(?=\D*\z)/
$& # => "23"

3 - Task: Find All Occurrences

3) String#scan

Your friend is the scan method which returns an array of all results:

"String with 42, sorry with 23 things".scan /\d+/ # => ["42", "23"]

4 - Task: Replace

The usual string replacement tool is gsub (global substitution) which replaces all matching occurrences of the regex. Should you only want to replace the first occurrence, use the sub method instead.

4a) String#gsub with String Argument

"String with 42 things".gsub /\d+/, "23" # => "String with 23 things"

You can use back references in the replacement string.

4b) String#gsub with Block

"String with 42 things".gsub /\d+/ do
  $&.to_i + 1
end # => "String with 43 things"

You can use Perl-style regex globals in the replacement block.

Special Task: Split String Into Array

Splitting a string along a separator is the main way of converting it into a useful array:

array = "String with     42\nthings".split(/\s+/)
# => ["String", "with", "42", "things"]

Special Task: Filter Array of Strings

The Enumerable#grep method allows you to do so:

["String", "with", "42", "things"].grep(/\d/) # => ["42"]

Ther is also Enumerable#grep_v which returns all elements that do not match (think #reject):

["String", "with", "42", "things"].grep_v(/\d/) # => ["String", "with", "things"]

Special Task: Partition String

Ruby's String#partition divides a string into an array consisting of three elements:

parts = "String with 42 things".partition(/\d+/)
parts # => ["String with ", "42", " things"]

Note that you can get to the same result using the special pre- and post match variables:

"String with 42 things" =~ /\d+/
parts = [$`, $&, $'] # => ["String with ", "42", " things"]

Regex Resources

Also See

More Idiosyncratic Ruby