Ruby Logo

Regular Expressions

Master pattern matching and text processing with Ruby regular expressions including capture groups and substitution.

Home Ruby Regular Expressions

Regular Expressions in Ruby

Why Learn Regular Expressions?

Regular expressions (regex) are powerful pattern-matching tools that solve critical text processing problems:

  • Data Validation: Verify email addresses, phone numbers, credit cards, passwords
  • Text Parsing: Extract specific information from logs, documents, or user input
  • Data Cleaning: Remove unwanted characters, normalize formats, fix inconsistencies
  • Search & Replace: Find and modify complex patterns in large text files
  • URL Routing: Match and extract parameters from web application routes

Problems Regular Expressions Solve

1. Input Validation

Instead of writing complex if-else logic to validate formats, regex provides a concise pattern-based approach. One regex can validate email formats that would require dozens of lines of manual string checking.

2. Data Extraction

Extract specific information from unstructured text like log files, CSV data, or user-generated content. Parse dates, URLs, phone numbers, or any structured data embedded in text.

3. Text Transformation

Replace complex patterns with new content, clean up formatting, or standardize data formats. Convert between different date formats, normalize phone numbers, or sanitize HTML.

Learning Path

1 Basic syntax and pattern creation
2 Matching and testing patterns
3 Search and replace operations
4 Capture groups and data extraction
5 Real-world applications and best practices

1. Creating Regular Expressions

Basic Syntax

Ruby provides two ways to create regular expressions. The literal syntax /pattern/ is most common, while Regexp.new() is useful when the pattern comes from a variable or needs dynamic construction.

# Literal syntax - most common
regex = /hello/

# Constructor syntax - useful for dynamic patterns
pattern = "hello"
regex = Regexp.new(pattern)

# With modifiers
regex = /hello/i          # case-insensitive
regex = /hello/m          # multiline mode
regex = /hello/x          # extended (ignore whitespace)

# Combining modifiers
regex = /hello/im         # case-insensitive AND multiline
💡 When to Use Each Approach
  • Literal syntax: When the pattern is known at code-writing time
  • Constructor: When building patterns from user input or configuration
  • Extended mode (/x): For complex patterns that need comments and formatting

2. Pattern Matching Methods

Different Ways to Test Patterns

text = "Hello, Ruby Developer!"

# 1. Match operator =~ (returns index or nil)
puts text =~ /Ruby/        # => 7 (position of match)
puts text =~ /Python/      # => nil (no match)

# 2. String#match method (returns MatchData object)
match_result = text.match(/Ruby/)
puts match_result          # => #<MatchData "Ruby">
puts match_result[0]       # => "Ruby" (the matched text)

# 3. String#match? method (returns boolean - Ruby 2.4+)
puts text.match?(/Ruby/)   # => true
puts text.match?(/Python/) # => false

# 4. Regex#=== method (useful in case statements)
case text
when /Ruby/
  puts "Found Ruby!"
when /Python/
  puts "Found Python!"
else
  puts "Unknown language"
end
⚠️ Performance Considerations

Use match? when you only need to test if a pattern exists (fastest). Use match when you need the matched text or capture groups. Use =~ when you need the position of the match.

3. Character Classes & Quantifiers

Character Classes - Matching Types of Characters

Character classes let you match categories of characters instead of specific letters. This makes patterns flexible and reusable.

# Predefined character classes
/\d/    # Any digit (0-9)
/\w/    # Any word character (letters, digits, underscore)
/\s/    # Any whitespace (space, tab, newline)
/./     # Any character except newline

# Negated classes (uppercase = opposite)
/\D/    # Any non-digit
/\W/    # Any non-word character
/\S/    # Any non-whitespace

# Custom character classes
/[aeiou]/      # Any vowel
/[a-z]/        # Any lowercase letter
/[A-Z]/        # Any uppercase letter
/[0-9]/        # Any digit (same as \d)
/[a-zA-Z0-9]/  # Any alphanumeric character
/[^0-9]/       # Any non-digit (^ means "not")

# Real-world examples
email = "user@example.com"
puts email.scan(/\w+/)     # => ["user", "example", "com"]

phone = "Call me at 123-456-7890"
puts phone.scan(/\d+/)     # => ["123", "456", "7890"]

Quantifiers - How Many Times?

Quantifiers specify how many times a pattern should match. They're essential for validating formats and extracting variable-length data.

# Basic quantifiers
/*        # Zero or more
/+        # One or more
/?        # Zero or one (optional)

# Specific counts
/{3}/     # Exactly 3 times
/{3,}/    # 3 or more times
/{3,7}/   # Between 3 and 7 times

# Practical examples

# Phone number: exactly 3 digits, dash, 3 digits, dash, 4 digits
phone_pattern = /\d{3}-\d{3}-\d{4}/
puts "123-456-7890".match?(phone_pattern)  # => true
puts "12-34-567".match?(phone_pattern)     # => false

# Password: at least 8 characters with letters and numbers
password_pattern = /^(?=.*[a-z])(?=.*\d)[a-z\d]{8,}$/i
puts "password123".match?(password_pattern) # => true
puts "pass".match?(password_pattern)        # => false

# Flexible name matching
name_pattern = /^[A-Z][a-z]+$/  # Capital letter followed by lowercase
puts "John".match?(name_pattern)     # => true
puts "JOHN".match?(name_pattern)     # => false
🎯 Greedy vs Non-Greedy Matching

By default, quantifiers are "greedy" - they match as much as possible. Add ? after a quantifier to make it "non-greedy" (match as little as possible).

html = "<div>content</div>"
puts html.match(/<.+>/)[0]   # => "<div>content</div>" (greedy)
puts html.match(/<.+?>/)[0]  # => "<div>" (non-greedy)

4. Search and Replace Operations

gsub and sub Methods

Ruby's gsub (global substitute) and sub (substitute once) methods are powerful tools for text transformation. They can replace simple strings or complex patterns with new content.

# Basic replacement
text = "Hello Ruby, Ruby is great!"
puts text.gsub(/Ruby/, "Python")  # => "Hello Python, Python is great!"
puts text.sub(/Ruby/, "Python")   # => "Hello Python, Ruby is great!" (only first)

# Case-insensitive replacement
text = "HELLO hello HeLLo"
puts text.gsub(/hello/i, "hi")    # => "hi hi hi"

# Using blocks for dynamic replacement
text = "I have 5 apples and 10 oranges"
result = text.gsub(/\d+/) do |number|
  (number.to_i * 2).to_s
end
puts result  # => "I have 10 apples and 20 oranges"

# Real-world examples

# Clean phone numbers
phone = "(123) 456-7890"
clean = phone.gsub(/[^\d]/, "")  # Remove non-digits
puts clean  # => "1234567890"

# Format currency
prices = "Price: $19.99, Sale: $15.50"
formatted = prices.gsub(/\$(\d+\.\d+)/, 'US Dollar \1')
puts formatted  # => "Price: US Dollar 19.99, Sale: US Dollar 15.50"

The scan Method - Finding All Matches

The scan method finds all matches of a pattern and returns them as an array. It's perfect for extracting data from text.

# Extract all numbers from text
text = "Order #123 for $45.99 due 2024-03-15"
numbers = text.scan(/\d+/)
puts numbers  # => ["123", "45", "99", "2024", "03", "15"]

# Extract email addresses
content = "Contact us at help@example.com or sales@company.org"
emails = content.scan(/\w+@\w+\.\w+/)
puts emails  # => ["help@example.com", "sales@company.org"]

# Extract URLs
html = 'Visit <a href="https://example.com">our site</a> or <a href="http://test.org">test</a>'
urls = html.scan(/https?:\/\/[^\s"]+/)
puts urls  # => ["https://example.com", "http://test.org"]

# Parse log entries
log = "2024-03-15 ERROR User login failed for user@domain.com"
parts = log.scan(/(\d{4}-\d{2}-\d{2}) (\w+) (.+)/)
puts parts  # => [["2024-03-15", "ERROR", "User login failed for user@domain.com"]]

5. Capture Groups and Data Extraction

Understanding Capture Groups

Capture groups (parentheses in regex) let you extract specific parts of a match. This is crucial for parsing structured data like dates, URLs, or formatted text.

# Basic capture groups
date_pattern = /(\d{4})-(\d{2})-(\d{2})/
date = "Today is 2024-03-15"
match = date.match(date_pattern)

puts match[0]  # => "2024-03-15" (full match)
puts match[1]  # => "2024" (first group - year)
puts match[2]  # => "03" (second group - month)
puts match[3]  # => "15" (third group - day)

# Named capture groups (Ruby 2.0+)
email_pattern = /(?<username>\w+)@(?<domain>\w+\.\w+)/
email = "Contact: john@example.com"
match = email.match(email_pattern)

puts match[:username]  # => "john"
puts match[:domain]    # => "example.com"
puts match['username'] # Also works with strings

# Parsing URLs
url_pattern = /^(?<protocol>https?):\/\/(?<host>[^\/]+)(?<path>\/.*)?$/
url = "https://example.com/users/123"
parts = url.match(url_pattern)

puts parts[:protocol]  # => "https"
puts parts[:host]      # => "example.com"
puts parts[:path]      # => "/users/123"
💡 Pro Tip: Non-Capturing Groups

Use (?:pattern) for grouping without capturing when you need parentheses for precedence but don't want to extract the content. This improves performance and keeps numbered groups clean.

Real-World Applications

Email Validation

# Simple email validation
email_regex = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i

def valid_email?(email)
  email.match?(email_regex)
end

puts valid_email?("user@example.com")     # true
puts valid_email?("invalid.email")       # false

Log File Parsing

# Parse Apache access logs
log_pattern = /^(?<ip>\S+) .+ \[(?<time>[^\]]+)\] "(?<method>\w+) (?<path>\S+) (?<version>[^"]+)" (?<status>\d+) (?<size>\d+)/

log = '192.168.1.1 - - [01/Jan/2024:12:00:00 +0000] "GET /api/users HTTP/1.1" 200 1234'
match = log.match(log_pattern)

puts match[:ip]      # "192.168.1.1"
puts match[:method]  # "GET"
puts match[:status]  # "200"

Data Cleaning

# Clean and normalize phone numbers
def clean_phone(phone)
  # Remove all non-digits
  cleaned = phone.gsub(/\D/, '')

  # Format as (XXX) XXX-XXXX
  if cleaned.length == 10
    cleaned.gsub(/(\d{3})(\d{3})(\d{4})/, '(\1) \2-\3')
  else
    "Invalid phone number"
  end
end

puts clean_phone("123-456-7890")    # "(123) 456-7890"
puts clean_phone("(555) 123.4567")  # "(555) 123-4567"

URL Parameter Extraction

# Extract parameters from RESTful URLs
route_pattern = /\/users\/(?<id>\d+)\/posts\/(?<post_id>\d+)/

url = "/users/123/posts/456"
params = url.match(route_pattern)

puts params[:id]      # "123"
puts params[:post_id] # "456"

# Build a simple router
def route_match(path, pattern)
  match = path.match(pattern)
  match ? match.named_captures : nil
end

Performance Tips & Best Practices

⚡ Performance Considerations

  • Compile once, use many: Store regex in constants or variables for reuse
  • Use anchors: ^ and $ prevent unnecessary backtracking
  • Be specific: [0-9] is faster than . when you need digits
  • Avoid nested quantifiers: Patterns like (a+)+ can cause exponential slowdown
  • Use non-capturing groups: (?:pattern) when you don't need the match

🎯 Best Practices

  • Test thoroughly: Regex can have edge cases; test with various inputs
  • Document complex patterns: Use comments with /x modifier for readability
  • Validate user input: Always sanitize regex patterns from user input
  • Consider alternatives: Sometimes string methods are simpler and faster
  • Use tools: Regex testers like rubular.com help build and debug patterns

Common Gotchas and Solutions

⚠️ Watch Out For

Escaping Special Characters

Characters like . + * ? ^ $ { } [ ] \ | ( ) have special meaning in regex.

/\./ matches literal dots, /\$/ matches dollar signs
Greedy Quantifiers

.* matches as much as possible, which might not be what you want.

Use .*? for non-greedy matching
Case Sensitivity

Regex is case-sensitive by default. Remember the /i modifier for case-insensitive matching.

🚀 Next Steps

Now that you understand regular expressions, you're ready to tackle complex text processing tasks. Continue with:

  • String Manipulation: Learn advanced string methods that work with regex
  • Text Parsing: Parse structured data formats and extract information
  • Data Validation: Build robust input validation for web applications
  • File Processing: Process large text files and logs efficiently

Quick Navigation

Related Topics

Video Tutorial

Watch and learn regular expressions

Pro Tip: After reading through the content above, watch this video to reinforce your understanding and see the concepts in action!