Ruby Regular Expressions — =~ match scan and Named Captures Explained
In this tutorial, you will learn about Ruby Regular Expressions. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby regular expressions use =~ for quick matching with index returns, match for MatchData objects with captures, scan for all occurrences, and named captures for readable extraction.
What You'll Learn
- Creating and using regex patterns in Ruby
- =~ operator, match method, and MatchData
- scan for extracting all matches
- Named captures for readable extraction
- Common regex patterns and tricks
Why It Matters
Regular expressions are essential for text processing, validation, and extraction. Durga Antivirus Pro uses regex to match malware signatures against file contents. DodaZIP validates archive filenames with regex. Doda Browser parses URLs, HTML, and email addresses. Regex turns complex text Parsing into a single line of code.
Real-World Use
Rails validations use regex for email and phone number formats. Log parsers extract timestamps and error codes with regex. Data Pipelines validate CSV formats. Every Ruby developer needs regex skills.
flowchart LR
A["Regex"] --> B["=~ Operator"]
B --> C["match Method"]
C --> D["Named Captures"]
D --> E["scan & gsub"]
E --> F["File I/O"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
style F fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Creating Regex Patterns
# Literal syntax (preferred)
pattern = /ruby/
pattern = /ruby/i # Case insensitive
pattern = /ruby/m # Multiline mode
pattern = /ruby/x # Extended mode (allow comments)
# Regex constructor
pattern = Regexp.new("ruby")
pattern = Regexp.new("ruby", Regexp::IGNORECASE)
# Interpolation
lang = "Ruby"
pattern = /#{lang}/i # /Ruby/i
The =~ Operator
Returns the index of the match or nil:
text = "Hello, Ruby World!"
if /Ruby/ =~ text
puts "Found at position #{$~.begin(0)}"
end
# Found at position 7
# Also works in reverse
if text =~ /World/
puts "Found at position #{text =~ /World/}"
end
# Found at position 13
# In conditions
puts "Contains Ruby" if text =~ /Ruby/
# Contains Ruby
The match Method
Returns a MatchData object with more detail:
text = "Hello, Ruby World!"
match_data = /Ruby/.match(text)
puts match_data[0] # "Ruby" (the matched text)
puts match_data.begin(0) # 7 (start position)
puts match_data.end(0) # 11 (end position)
puts match_data.pre_match # "Hello, " (text before)
puts match_data.post_match # " World!" (text after)
Capturing Groups
text = "2026-06-28"
pattern = /(\d{4})-(\d{2})-(\d{2})/
match = pattern.match(text)
puts match[0] # "2026-06-28" (full match)
puts match[1] # "2026" (year)
puts match[2] # "06" (month)
puts match[3] # "28" (day)
# Captures method
puts match.captures.inspect # ["2026", "06", "28"]
Named Captures
Ruby 1.9+ supports named captures:
text = "2026-06-28"
pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
match = pattern.match(text)
puts match[:year] # "2026"
puts match[:month] # "06"
puts match[:day] # "28"
# Named captures also create local variables
if pattern =~ text
puts "#{year}-#{month}-#{day}" # 2026-06-28
end
Scan for All Matches
scan returns an array of all matches:
text = "Hello Ruby, welcome to Ruby programming"
# All matches
puts text.scan(/Ruby/).inspect
# ["Ruby", "Ruby"]
# With captures
dates = "2026-01-15, 2026-06-28, 2027-03-01"
matches = dates.scan(/(\d{4})-(\d{2})-(\d{2})/)
puts matches.inspect
# [["2026", "01", "15"], ["2026", "06", "28"], ["2027", "03", "01"]]
# With block
text.scan(/\b\w+\b/) { |word| puts word.length }
Gsub with Regex
text = "The quick brown fox"
# Replace vowels
puts text.gsub(/[aeiou]/, "*")
# Th* q**ck br*wn f*x
# With block
puts text.gsub(/\b\w/) { |match| match.upcase }
# The Quick Brown Fox
# Remove whitespace
puts text.gsub(/\s+/, "-")
# The-quick-brown-fox
Common Regex Patterns
# Email
email_pattern = /\A[\w.]+@\w+\.\w+\z/
puts "test@example.com" =~ email_pattern # 0 (match)
# URL
url_pattern = /https?:\/\/[\w.]+[\w\/.-]*/
puts "Visit https://example.com" =~ url_pattern # 6
# Phone (US)
phone_pattern = /\A\d{3}-\d{3}-\d{4}\z/
puts "555-123-4567" =~ phone_pattern # 0
# IP Address
ip_pattern = /\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/
puts "192.168.1.1" =~ ip_pattern # 0
# Extract hashtags
text = "Learning #Ruby and #Regex today"
hashtags = text.scan(/#\w+/)
puts hashtags.inspect # ["#Ruby", "#Regex"]
Regex Options
# /i — case insensitive
puts "RUBY" =~ /ruby/i # 0
# /m — multiline (. matches newlines)
text = "Line1\nLine2"
puts text.scan(/./m).inspect # ["L","i","n","e","1","\n","L",...]
# /x — extended (allow whitespace and comments)
pattern = /
\d{4} # Year
- # Separator
\d{2} # Month
- # Separator
\d{2} # Day
/x
Anchor Patterns
# \A — start of string
puts "Ruby" =~ /\AR/ # 0
puts "My Ruby" =~ /\AR/ # nil
# \z — end of string
puts "Ruby" =~ /y\z/ # 3
puts "Ruby!" =~ /y\z/ # nil
# ^ — start of line
# $ — end of line
# \b — word boundary
puts "cat" =~ /\bcat\b/ # 0
puts "catalog" =~ /\bcat\b/ # nil
Character Classes
# \d — digit (0-9)
# \w — word character (a-z, A-Z, 0-9, _)
# \s — whitespace (space, tab, newline)
# \D — non-digit
# \W — non-word
# \S — non-whitespace
# Custom classes
/[aeiou]/ # Any vowel
/[^aeiou]/ # Any consonant
/[a-z]/ # Lowercase letter
/[A-Za-z]/ # Any letter
/[0-5]/ # Digits 0-5
Quantifiers
# * — zero or more
# + — one or more
# ? — zero or one
# {n} — exactly n
# {n,} — n or more
# {n,m} — between n and m
puts "hello" =~ /l+/ # 2
puts "helllo" =~ /l+/ # 2
puts "heo" =~ /l+/ # nil
# Greedy vs lazy
text = "<b>bold</b> and <i>italic</i>"
puts text.scan(/<.+>/).inspect # ["<b>bold</b> and <i>italic</i>"]
puts text.scan(/<.+?>/).inspect # ["<b>", "</b>", "<i>", "</i>"]
Lookahead and Lookbehind
text = "Ruby Programming Language"
# Positive lookahead
puts text.scan(/\w+(?= Language)/) # ["Programming"]
# Negative lookahead
puts text.scan(/\b\w+\b(?! Language)/) # ["Ruby", "Language"]
# Positive lookbehind
puts text.scan(/(?<=Ruby )\w+/) # ["Programming"]
Regexp Methods on String
text = "Ruby 3.3.0"
# Match?
puts text.match?(/Ruby/) # true
# Index of match
puts text =~ /\d/ # 5
# All matches
puts text.scan(/\d+/).inspect # ["3", "3", "0"]
# Split by regex
puts "a1b2c3".split(/\d/).inspect # ["a", "b", "c"]
# Count matches
puts text.scan(/\d/).count # 4
Common Mistakes
1. Not Escaping Special Characters
# Wrong — . matches any character
"hello.com" =~ /hello.com/ # Matches "helloXcom" too!
# Right — escape the dot
"hello.com" =~ /hello\.com/
2. Using =~ in an if Block Without Care
# This works but can be confusing
if text =~ /pattern/
# ...
end
# == has higher precedence than =~
# Use match? in modern Ruby
if text.match?(/pattern/)
# ...
end
3. Forgetting ^ and $ Anchors
# Without anchors — partial match
"test@example.com." =~ /\A[\w.]+@\w+\.\w+\z/ # nil (trailing dot)
# Partial match
"test@example.com" =~ /[\w.]+@\w+\.\w+/ # 0 (but matches substring)
4. Greedy Quantifiers When Lazy Is Needed
text = "First: apple, Second: banana"
puts text.scan(/First: (.+),/).inspect
# [["apple, Second: banana"]] — too greedy!
# Use lazy quantifier
puts text.scan(/First: (.+?),/).inspect
# [["apple"]]
5. Not Using //x for Complex Patterns
# Hard to read
/^[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*(?:\s+Jr\.|\s+Sr\.|\s+III)?$/
# Readable with /x
/
^[A-Z][a-z]+ # First name
(?:\s+[A-Z][a-z]+)* # Middle names
(?:\s+Jr\.|\s+Sr\.|\s+III)? # Suffix (optional)
$
/x
Practice Questions
1. What does =~ return?
The index of the first match (an integer), or nil if no match. This makes it usable in if/unless conditions because nil is falsy and integers are truthy.
2. What's the difference between match and scan?
match finds the first match and returns a MatchData object. scan finds all matches and returns an array of matched strings (or arrays of captures).
3. How do you create a named capture?
Use (?<name>pattern) syntax. Named captures are accessible via match[:name] and even create local variables when used with =~.
4. What does the /x option do?
/x enables extended mode, allowing whitespace and comments in the regex pattern for readability. It's essential for complex, maintainable patterns.
Challenge: Write a method that extracts all URLs from a text string, handling both http and https protocols.
Solution
def extract_urls(text)
url_pattern = /https?:\/\/[\w.-]+(?:\/[\w.\/\-?=#%&@]*)?/
text.scan(url_pattern)
end
sample = "Visit us at https://example.com or http://test.org/page?q=ruby. Also check https://docs.ruby-lang.org"
urls = extract_urls(sample)
puts urls.inspect
# ["https://example.com", "http://test.org/page?q=ruby", "https://docs.ruby-lang.org"]
FAQ
{{< faq question="Should I use =~ or .match?" >}}
Use =~ for quick boolean checks and when you need the match index. Use .match when you need MatchData methods (captures, pre_match, post_match). Use .match? when you only need a boolean without creating a MatchData object (faster).
{{< /faq >}}
{{< faq question="How do I test if a string matches a pattern?" >}}
Use .match?(/pattern/) in Ruby 2.4+ for a fast boolean check. Use =~ or .match if you need the matched text. Avoid $~ global variable in modern code.
{{< /faq >}}
{{< faq question="Are Ruby regex patterns Perl-compatible?" >}} Ruby regex is similar to Perl but not identical. Ruby supports named captures, lookahead/lookbehind, atomic groups, and possessive quantifiers (in Onigmo engine, Ruby 2.0+). {{< /faq >}}
{{< faq question="What is the difference between . and \w in regex?" >}}
. matches any character except newline (with /m it matches newlines too). \w matches word characters: letters, digits, and underscore. Use . for any character and \w for identifier-style text.
{{< /faq >}}
{{< faq question="How do I match a literal dot?" >}}
Escape it with backslash: /\./. Unescaped . matches any character. This is one of the most common regex mistakes.
{{< /faq >}}
Try It Yourself
# regex_demo.rb
text = <<~DATA
User: alice@example.com
Date: 2026-06-28
IP: 192.168.1.100
Tags: ruby, regex, programming
DATA
# Extract email
email = text.match(/([\w.]+@\w+\.\w+)/)
puts "Email: #{email[1]}" if email
# Extract date
date = text.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
if date
puts "Date: #{date[:year]}/#{date[:month]}/#{date[:day]}"
end
# Extract IP
ip = text.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
puts "IP: #{ip[0]}" if ip
# Extract tags
tags = text.scan(/(\w+)/)
puts "Tags: #{tags.flatten.last(3).join(', ')}"
Expected output:
Email: alice@example.com
Date: 2026/06/28
IP: 192.168.1.100
Tags: ruby, regex, programming
What's Next
Now that you understand regex, learn about file I/O operations for reading and writing files, directories, and structured data formats.
| Topic | Description | Link |
|---|---|---|
| Ruby File I/O | File, IO, Dir, CSV, JSON | {{< ref "19-file-io" >}} |
| Ruby Exception Handling | begin/rescue, ensure, raise | {{< ref "20-exception-handling" >}} |
| Python Regex | Compare Python re module | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro