Finding a pattern, not an exact string

Free preview 3 min to read

"Does this look like an email?" is a different question from "is this that string?"

input = "[email protected]"
input == "[email protected]"   # true, but only for this one exact address

That comparison answers a question about one specific string. A signup form doesn't know the visitor's email address in advance. It needs to check "does this look like an email": a shape, not an exact value. == can't express "some characters, then an @, then more characters, then a dot, then a few letters". A regular expression (regex, for short) can.

A regex literal, written in Ruby

pattern = /gmail/
pattern.class
# Regexp

/gmail/ is a Regexp object, a Ruby object like any other, created with slash delimiters instead of Regexp.new. The simplest regex is plain text. /gmail/ matches any string that contains the characters g, m, a, i, l in that order, anywhere inside it.

=~: the first way to run one

"[email protected]" =~ /gmail/
# 4, the index where the match starts

"[email protected]" =~ /gmail/
# nil, no match anywhere in the string

=~ runs the regex against a string and returns the index where the first match starts, or nil if nothing matches. nil is falsy and every integer (including 0) is truthy, so =~ fits straight into an if:

email = "[email protected]"
if email =~ /gmail/
  puts "looks like a Gmail address"
end
# "looks like a Gmail address"

Watch one sharp edge. A match at index 0 is still a match, and 0 is truthy in Ruby (only nil and false are falsy), so this pattern is safe. It would break for a method that returned false on no match instead of nil. That's why =~'s "index or nil" contract matters.

Not every character means itself

., *, +, ?, [, ], (, ), {, }, |, \, ^ and $ all have special meanings inside a regex. This course spends time on several of them from lesson 5. For now: to match one of these characters as plain text, escape it with a backslash:

"3.14" =~ /3.14/    # 0, but "." here means "any character", so this also matches "3x14"
"3x14" =~ /3.14/    # 0, matched anyway, probably not what was intended
"3.14" =~ /3\.14/  # 0, "\." means a literal dot, and only a literal dot
"3x14" =~ /3\.14/  # nil, correctly rejected

Where this goes

This course builds the toolkit behind that email check. It covers pulling structured pieces out of a match, finding every occurrence instead of the first, and the anchors and character classes that keep a pattern from being too loose. Search-and-replace comes after that. The last lesson uses all of it to read a regular expression that runs in this app's models.

Key takeaways

  • == checks for one exact string; a regex checks for a shape that many different strings can share.
  • /pattern/ is a Regexp object; =~ runs it against a string and returns the index of the first match, or nil if there's no match.
  • 0 is a valid, truthy match index in Ruby, so if str =~ /pattern/ treats "matched at the start" as a match.
  • Characters like ., *, + and ? are special inside a regex; to match one as plain text, escape it: \. instead of ..
Lesson 1 of 7 · free

Keep going with lesson 2

MatchData or a plain Yes/No: match vs match?

Compares match, which returns MatchData, with match?, which returns a boolean and leaves $~ untouched.

A free account also gives you the quizzes, your progress and notes, and the discussion.

Type to start searching...