String#sub with Regex and Block Forms
String#sub replaces the first occurrence of a pattern in a string. It returns a new string and leaves the original unchanged.
Signature
str.sub(pattern, replacement)
str.sub(pattern) { |match| block }
Parameters:
pattern— a String or Regexp to search forreplacement— a String (optional), or a block that returns the replacement
Returns: A new string with the replacement made.
That return value is important because it keeps sub predictable in expressions. You get a new string back, so the caller can keep the original around for comparison, logging, or another pass.
Basic replacement
Replace one substring with another:
"hello".sub("l", "x") # => "hexlo"
"hello".sub("lo", "p") # => "help"
These examples show the simplest version of sub: one match is replaced, and the rest of the string is left alone. That makes it easy to reason about when you want a targeted edit instead of a full rewrite.
Only the first match is replaced:
"hello".sub("l", "x") # => "hexlo" (not "hexxo")
The follow-up example matters because it shows the limit of the method. sub is a surgical change, so it is best when you only want the first matching piece adjusted and do not want to touch later occurrences.
Regexp patterns
Use a regexp for more control:
"hello123".sub(/\d+/, "world") # => "helloworld"
"hello123".sub(/[aeiou]/, "*") # => "h*llo"
Regular expressions make sub much more flexible because the method can match a shape instead of a literal substring. That is useful when the text has a pattern you know, but the exact characters vary from one input to another.
Anchors work as expected:
"hello".sub(/^h/, "H") # => "Hello"
"hello".sub(/o$/, "O") # => "hellO"
Anchors are a good reminder that sub works with the same matching rules as the rest of Ruby’s regular expression engine. You can use them to limit the replacement to the beginning or end of the string without scanning the middle.
Capture groups
Capture groups let you reuse parts of the matched text:
"hello".sub(/(ello)/, "good-\1") # => "good-ello"
Capture groups let you keep part of the original text while replacing the rest. That is the main reason sub is so handy for format changes, because you can move pieces around instead of rebuilding the whole string.
Numbered groups work with backreferences:
"first second".sub(/(\w+) (\w+)/, '\2 \1') # => "second first"
Backreferences are especially useful when you want to swap or reorder pieces. The replacement string can stay short, and the regular expression does the work of remembering which part was which.
At this point the examples are starting to show a pattern: sub is strongest when the replacement depends on the shape of the match. That makes it a nice fit for formatting tasks, cleanup jobs, and small text migrations.
Named capture groups:
"john doe".sub(/(?<first>\w+) (?<last>\w+)/, '\k<last>, \k<first>')
# => "doe, john"
Named groups make the example easier to read when the match has a semantic meaning like first name and last name. They are often clearer than numbered groups once the pattern gets longer.
Using a block
When you pass a block instead of a replacement string, the block receives the match and returns the replacement:
"hello".sub(/[aeiou]/) { |v| v.upcase } # => "hEllo"
The block form is useful when the replacement needs to be computed rather than written as a fixed string. That keeps the code expressive when the change depends on the match itself.
Another useful block example is a transformation:
"price: 42".sub(/\d+/) { |n| "$#{n}" } # => "price: $42"
Each block receives the full match. Only the first match is replaced.
That makes the block version a nice middle ground between literal replacement and a bigger transformation pipeline. You can inspect the match, decide what to return, and still keep the change focused on one occurrence.
Special replacement patterns
Within a replacement string, these patterns have special meaning:
| Pattern | Meaning |
|---|---|
\& | The entire matched string |
\ | The part before the match |
\' | The part after the match |
\1 \2 | Numbered capture groups |
\\ | A literal backslash |
"hello".sub("l", "(&)") # => "he(l)lo"
"hello".sub("l", "$\`") # => "hehe lo"
"hello".sub("l", "$\'") # => "helo lo"
These replacement tokens are easy to overlook, but they are useful when you need to keep the parts before or after the match. They let you build a new string from the original without manually concatenating slices.
Deleting characters
Combine sub with an empty replacement to delete:
"hello!".sub("!", "") # => "hello"
" hello ".sub(" ", "") # => " hello " (only first space)
For deleting all occurrences, use gsub instead.
The empty-string replacement works well when you want to remove a prefix marker, a single separator, or one accidental character. If the goal is to strip every match, gsub is the better fit because it applies the change throughout the string.
Comparison with gsub
sub replaces only the first match. gsub replaces all matches:
"hello".sub("l", "x") # => "hexlo"
"hello".gsub("l", "x") # => "hexxo"
The comparison is simple, but it helps anchor the API in your head: sub changes one match, while gsub changes them all. When you only need a single replacement, sub keeps the intent tighter and the output more predictable.
See Also
- /reference/string-methods/gsub-bang/ — replace all matches in place
- /reference/string-methods/slice/ — extract substrings by position or pattern