String#gsub!
str.gsub!(pattern, replacement) -> str or nil The gsub! method performs global substitution on a string, replacing all occurrences of a pattern with a replacement string or block result. The bang version modifies the string in place.
Basic Usage
# Simple string replacement
text = "hello world"
text.gsub!("world", "Ruby")
puts text # => "hello Ruby"
When the pattern is a plain string, gsub! performs a straightforward literal match against every occurrence in the target. This is the simplest path and works well for fixed replacements like normalising terminology or swapping known placeholder tokens. The method walks the string in a single left-to-right pass without backtracking, so each character position is considered exactly once and overlapping matches are not double-processed. For most substitution tasks, the plain-string form covers the common cases.
With regular expressions
# Regex replacement
text = "abc 123 def 456"
text.gsub!(/\d+/, "NUM")
puts text # => "abc NUM def NUM"
# Case insensitive
text = "HELLO hello"
text.gsub!(/hello/i, "Hi")
puts text # => "Hi hello"
When a regex carries the i flag, character classes and literals inside the pattern match without regard to case. This flag applies to the whole pattern; Ruby does not support inline case toggling within a single regex like some other engines do. If your pattern mixes case-sensitive and case-insensitive portions, split the substitution into separate calls instead of trying to encode both behaviours in one pass.
With Block
# Block for dynamic replacement
text = "hello world"
text.gsub!(/\w+/) { |word| word.upcase }
puts text # => "HELLO WORLD"
The block form receives each match as a string argument and its return value becomes the replacement text. This is where gsub! becomes more expressive than a simple find-and-replace: the block can compute the replacement dynamically based on the match itself. Common use cases include arithmetic on captured numbers, case transformations, and lookup-table substitutions where each match maps to a different value.
Return Value
# Returns modified string if changes made
result = "hello".gsub!(/x/, "y")
puts result # => nil (no changes)
# Returns nil if no changes (bang version)
result = "hello".gsub!("x", "y")
puts result # => nil
Practical Examples
The power of gsub! becomes most apparent when you move beyond simple textbook replacements and apply it to real-world text processing tasks. The examples below show common patterns for data cleanup, formatting, URL generation, and conditional substitution, each one leveraging a different aspect of the method: regex matching, block logic, or chained mutations.
Redacting sensitive data
text = "My phone is 555-1234 and email is test@example.com"
text.gsub!(/\d{3}-\d{4}/, "000-0000")
text.gsub!(/\S+@\S+\.\S+/, "[EMAIL]")
puts text # => "My phone is 000-0000 and email is [EMAIL]"
Chaining gsub! calls provides a kind of pipeline where each step handles one category of sensitive data. The first call scrubs phone numbers, the second removes email addresses. This sequential approach keeps each regex simple and focused on a single pattern, which is usually easier to test and maintain than a single monster regex that tries to capture every possible format at once. Each step also returns nil or the modified string, so intermediate checks can confirm which transformations actually ran.
Formatting
# Add commas to numbers
text = "1234567"
text.gsub!(/(\d)(?=(\d{3})+(?!\d))/, '\1,')
puts text # => "1,234,567"
The comma-insertion regex uses a lookahead to find positions in the string where a digit is followed by a group of three digits that is not itself followed by another digit. This pattern walks backward through the number and inserts a comma at each thousand boundary without needing to reverse the string or count from the right. The backreference \1 in the replacement refers to the digit captured by the first group, so each comma is placed directly after a digit.
Slug Generation
title = "Hello World! 2024"
slug = title.downcase.gsub!(/[^a-z0-9]+/, '-').gsub!(/^-|-$/, '')
puts slug # => "hello-world"
Chaining gsub! for slug generation is a common idiom because each step transforms a different aspect of the string. The first call normalises everything to lowercase and replaces non-alphanumeric runs with hyphens, while the second call trims any leading or trailing hyphens that result from punctuation at the edges. Each gsub! modifies the same string in place, so the slug variable holds an increasingly clean version with every call.
Conditional Replacement
text = "one TWO three FOUR"
text.gsub!(/\b[TWO|FOUR]\b/) { |m| m.downcase }
puts text # => "one two three four"
The conditional block pattern works because the block can inspect each match and decide case-by-case what to return. When the match is one of the words to downcase, the block returns m.downcase. When it is not, the block could return the match unchanged. This is a more flexible pattern than writing two separate substitutions because a single pass through the string covers all the rules without worrying about order of application.
Comparison with gsub (non-destructive)
# gsub returns new string
original = "hello"
modified = original.gsub("l", "r")
puts original # => "hello" (unchanged)
puts modified # => "herro"
# gsub! modifies in place
original = "hello"
original.gsub!("l", "r")
puts original # => "herro"
The non-bang gsub is the safer default when the original string should stay intact. Because gsub produces a new string, it fits naturally into functional-style code where methods return values rather than mutate their inputs. The bang version trades that immutability for memory efficiency when you work with large strings and want to avoid allocating extra copies. Both methods accept the same pattern and replacement arguments, so switching between them is mostly a decision about mutation versus capability.
Special replacement patterns
# \0 or \& - entire match
"hello".gsub("l", "+\0+") # => "he+l+l+o"
# \1, \2 - capture groups
"john@example.com".gsub(/(\w+)@(\w+)/, '[\1] at \2') # => "[john] at example"
Capture-group backreferences like \1 and \2 let you rearrange matched content in the replacement string. The groups are numbered by the order of opening parentheses in the regex, not by the order in which they match. This is especially useful when you want to extract parts of a match and reassemble them in a different arrangement, such as reformatting dates, swapping names, or wrapping specific sub-patterns in markup or punctuation.
Performance
For simple string replacements, tr may be faster:
text = "hello"
text.tr!("aeiou", "AEIOU") # => "hEllO"
Use gsub! when mutation is part of the design
gsub! makes sense when the code expects the original string to change and the caller wants that side effect on purpose. That can be handy in parsing pipelines, cleanup routines, and other places where the current string is just a working buffer. The method’s return value matters too: it returns nil when nothing changed, so the caller can tell whether the replacement actually happened. That behavior is useful when the code needs to branch after the replacement step.
Watch the replacement chain
Because gsub! mutates in place, chaining several calls can be concise, but the order matters. Each replacement sees the string after the previous one has already changed it, which can be exactly what you want or the source of a subtle bug. It helps to keep each step simple and to read the chain from left to right as a sequence of edits. When the transformation starts to feel hard to follow, splitting it into named steps often makes the logic easier to trust.
Choose the non-bang version when the source should stay stable
If the original string is meant to remain a reference value, gsub is often the better choice because it returns a new string instead of changing the input. That keeps the caller from having to remember which object was modified and which one was preserved. The distinction is small in syntax but large in meaning. In code that shares strings across several methods, using the non-bang version can keep the flow easier to reason about and the data safer to reuse.