String#gsub
String#gsub replaces every occurrence of a pattern in a string. The g means global — it replaces all matches, not just the first one. The original text is never modified.
It is one of the most useful text-cleanup methods in Ruby because it covers several related cases without changing the source. You can pass a literal substring, a regular expression, a block, or even a hash of replacements, and the method always returns a new value with the substitutions applied.
Signature
str.gsub(pattern, replacement)
str.gsub(pattern) { |match| block }
Parameters:
pattern— a String or Regexpreplacement— a String, Hash, Symbol, or nil
Returns: String — a new copy with all replacements
The bang variant gsub! modifies in place and returns the number of replacements, or nil if nothing matched.
String pattern replacement
"hello world".gsub("o", "0") # => "hell0 w0rld"
"a b a b a b".gsub("a", "X") # => "X b X b X b"
Literal patterns match character-for-character — no regex features, just exact substring replacement.
Use this form when the replacement target is already known and there is no need for capture groups or pattern matching. It keeps the call short and makes the intent very clear.
Regex pattern replacement
"hello123world456".gsub(/\d+/, "NUMBER")
# => "helloNUMBERworldNUMBER"
"foo bar foo".gsub(/foo/, "baz")
# => "baz bar baz"
Regex patterns enable capture groups, character classes, and quantifiers.
This is the right choice when the string shape matters more than the exact text. It is also the version that lets you pull pieces out of the match and rebuild the replacement dynamically.
Block Form
Pass a block and each match gets replaced with the block’s return value:
"hello world".gsub(/\w+/) { |word| word.upcase }
# => "HELLO WORLD"
"123-456-7890".gsub(/\d+/) { |n| n.to_i * 2 }
# => "246-912-15780"
The block receives the matched string. Whatever it returns becomes the replacement.
The block form is useful when the replacement depends on the captured text itself, not just a fixed output. It keeps the transformation close to the match and avoids a separate pre-processing step.
Backreferences
Use \1 through \9 in the replacement string to reference captured groups:
"John Doe".gsub(/(\w+) (\w+)/, '\2, \1')
# => "Doe, John"
"hello".gsub(/(.)/, '[\1]')
# => "[h][e][l][l][o]"
Named captures use \k<name>:
Numbered backreferences work well for short patterns with one or two groups, but they become fragile when the pattern grows or when groups are added later. Named captures solve that problem by tying each reference to a label, so the replacement string stays readable even if the capture order changes. The tradeoff is a slightly longer regex, but the clarity gain usually pays for it in patterns with three or more groups or when the replacement is maintained by someone who did not write the original match.
"John Doe".gsub(/(?<first>\w+) (?<last>\w+)/, '\k<last>, \k<first>')
# => "Doe, John"
Named captures are a good fit when the replacement has to stay readable. They make the intent easier to follow than numbered backreferences once the pattern starts to grow.
Hash Replacement
Pass a Hash to substitute matches based on their content:
replacements = {
"hello" => "hi",
"world" => "earth",
"!" => "."
}
"hello world!".gsub(/\w+|!/, replacements)
# => "hi earth."
If a match has no key in the hash, it passes through unchanged.
That behavior makes hash replacement feel similar to a lookup table. It is especially handy when the source text is already tokenized into words or symbols that map cleanly to another set of values.
Symbol as replacement
A Symbol calls that method on the matched string:
"hello".gsub(/\w+/, :upcase) # => "HELLO"
"hello".gsub(/\w+/, :length) # => "5"
"hello world".gsub(/\w+/, :capitalize)
# => "Hello World"
The symbol form is compact and expressive when the replacement is just another string method. It works best for simple transforms where the matched text itself can be sent to the method directly. Compared to a block that calls the same method, the symbol form saves a few characters and makes the transformation read as a pipeline step rather than a custom closure.
Special replacement sequences
Some sequences in replacement strings have special meaning:
| Sequence | Becomes |
|---|---|
\\ | A literal backslash |
\n | Newline |
\t | Tab |
& | The entire matched string |
\` | The part before the match |
\' | The part after the match |
\+ | The last matched group |
"hello".gsub("ello", 'goodbye &')
# => "goodbye hello"
"world".gsub(/(world)/, 'hello, \\` & \\\'')
# => "hello, world"
These sequences are easy to forget, so it helps to treat them as a small vocabulary rather than memorize them all at once. Once you know the escape rules, you can build replacement strings that preserve the matched text or surrounding context.
The bang variant
gsub! modifies the string in place:
s = "hello world"
n = s.gsub!("o", "0")
s # => "hell0 w0rld"
n # => 2 (replacement count)
When nothing matches, gsub! returns nil:
The nil return on no match is the reason you should check the result before chaining methods on it. If you call .downcase on the return value and no substitution happened, you will get a NoMethodError because nil does not respond to downcase. The non-bang gsub avoids this entirely by always returning a string, so it is the safer choice when the match is uncertain.
s = "hello"
result = s.gsub!("x", "y")
result # => nil
Use gsub! when you need the replacement count or want to modify in place. Otherwise prefer gsub.
The bang version is convenient when you know the text should change and the caller wants the mutation directly. If the replacement is optional or the original value needs to stay intact, the non-bang form is easier to reason about.
Counting without allocating a new string
count = 0
"hello world".gsub(/\w+/) { count += 1 }
count # => 2
This trick uses gsub as a matcher instead of a transformer. It is a lightweight way to count occurrences when you already have a pattern and do not want to run a separate scan.
gsub vs sub
Choosing between gsub and sub depends on whether you need to replace every match or just the first. The table below summarizes the four variants, but the key rule is simple: gsub touches every occurrence, sub stops after one. When the input is known to contain many instances of the target, gsub is the natural default. When you only care about the leading match — like stripping a prefix — sub is more precise and slightly faster because it can stop after the first hit.
| Method | Matches replaced | Returns |
|---|---|---|
sub | First only | New string |
gsub | All | New string |
sub! | First only | String or nil |
gsub! | All | String or nil |
Common patterns
Normalizing Whitespace
"hello \n world".gsub(/\s+/, " ")
# => "hello world"
Redacting Words
Normalizing whitespace is a cleanup step, but gsub also handles selective content removal. When you have a list of terms to hide, you can combine a regex alternation with a hash lookup to replace each sensitive word while leaving non-sensitive text alone.
sensitive = ["password", "secret", "token"]
replacements = Hash[sensitive.product(["[REDACTED]"]).to_h]
"use your password here".gsub(/\w+/, replacements)
# => "use your [REDACTED] here"
This kind of redaction works well when the input is already made of predictable tokens. For free-form text, a regular expression with capture groups is usually easier to adapt because it can target only the parts you want to hide.
Converting snake case to camelCase
"hello_world".gsub(/_(\w)/) { $1.upcase }
# => "helloWorld"
$1 is set by gsub’s block form, referencing the first capture group.
That small pattern is enough for many naming conversions because it changes only the characters that matter and leaves the rest of the input untouched. It is also a good reminder that gsub can act as a formatting tool, not just a search-and-replace method.
See Also
- /reference/string-methods/sub/ — replace only the first occurrence
- /reference/string-methods/replace/ — replace all content with new content
- /reference/string-methods/match/ — check if a pattern matches