String Manipulation in Ruby
Strings are one of the most used data types in Ruby. Whether you’re processing user input, building formatted output, or sanitizing data, you’ll work with strings constantly. This guide covers the essential techniques for creating, accessing, modifying, and formatting strings in Ruby.
Creating Strings
Ruby offers several ways to create strings:
# Double quotes — allows interpolation
name = "Alice"
greeting = "Hello, #{name}!" # => "Hello, Alice!"
# Single quotes — literal, no interpolation
path = 'C:\Users\Alice' # => "C:\\Users\\Alice"
# %q and %Q syntax
message = %Q(Multi-line string
with "quotes" and #{name})
heredoc = <<~TEXT
Multi-line string
without escaping
TEXT
# String.new (rarely needed)
empty = String.new
The #{} interpolation syntax inside double quotes lets you embed Ruby expressions directly into a string. Single quotes treat everything literally, which is useful for paths on Windows or strings containing #{.
String Indexing
Ruby strings are sequences of characters, and each character has a numeric index:
str = "hello"
str[0] # => "h"
str[-1] # => "o" (negative indices count from the end)
str[1] # => "e"
str[10] # => nil (out of bounds returns nil)
Ruby 3.0 introduced a significant change: indexing a string now returns a String instead of an Integer (the character code). This aligns with the principle that str[i] and str[i, 1] should behave the same way:
# Ruby 3.0+
str = "hello"
str[0] # => "h" (was 104 in Ruby 2.x)
str[0, 1] # => "h" (substring of length 1)
str[0..2] # => "hel"
Slicing with []
The [] method supports several argument types for slicing:
str = "hello world"
str[6, 5] # => "world" (start index, length)
str[6..10] # => "world" (inclusive range)
str[6...11] # => "world" (exclusive range)
str["world"] # => "world" (substring search — returns nil if not found)
str[/wo+/] # => "wo" (regex match)
Ruby also provides slice and slice! (destructive):
str = "hello"
str.slice(0) # => "h" (returns a new string)
str # => "hello" (original unchanged)
str.slice!(0) # => "h"
str # => "ello" (original mutated)
Substitution: gsub and sub
Ruby gives you two main tools for find-and-replace operations on strings.
gsub — Global Substitution
gsub replaces all occurrences of a pattern:
text = "foo bar foo baz foo"
text.gsub("foo", "qux") # => "qux bar qux baz qux"
text.gsub("foo", "qux").object_id == text.object_id # => false (returns new string)
You can also use a hash to map multiple replacements at once:
replacements = { "foo" => "qux", "bar" => "quux" }
text.gsub(/\w+/, replacements) # => "qux quux qux baz qux"
With a block, the matched string is passed in and you can transform it:
"hello world".gsub(/\w+/) { |word| word.capitalize }
# => "Hello World"
sub — Single Substitution
sub replaces only the first occurrence:
"foo bar foo".sub("foo", "baz") # => "baz bar foo"
Destructive Variants
Both gsub! and sub! modify the string in place and return the number of substitutions made (or nil if nothing was replaced):
str = "hello world"
str.gsub!("world", "ruby")
str # => "hello ruby"
str = "foo bar foo"
str.sub!("foo", "baz") # => "baz bar foo"
str # => "baz bar foo"
Whitespace Handling: chomp and strip
chomp
Removes trailing record separators (usually newlines) from a string:
"hello\n".chomp # => "hello"
"hello\r\n".chomp # => "hello"
"hello".chomp # => "hello" (no trailing newline — no change)
You can also specify a custom separator:
"hello!".chomp("!") # => "hello"
The destructive variant chomp! modifies in place.
strip
Removes leading and trailing whitespace:
" hello ".strip # => "hello"
"\thello\r\n".strip # => "hello"
Ruby also provides lstrip and rstrip for one-sided trimming:
" hello ".lstrip # => "hello "
" hello ".rstrip # => " hello"
Case Conversion
Ruby provides four case-conversion methods:
"Hello World".upcase # => "HELLO WORLD"
"HELLO world".downcase # => "hello world"
"hello world".capitalize # => "Hello world" (first char up, rest down)
"Hello World".swapcase # => "hELLO wORLD"
All have destructive variants (upcase!, downcase!, etc.) that modify in place:
str = "Hello"
str.upcase!
str # => "HELLO"
Splitting and Joining
split
Breaks a string into an array of substrings:
"apple,banana,cherry".split(",")
# => ["apple", "banana", "cherry"]
"one two three".split(/\s+/)
# => ["one", "two", "three"]
"hello".split("")
# => ["h", "e", "l", "l", "o"]
You can limit the number of splits:
"a,b,c,d".split(",", 2)
# => ["a", "b,c,d"]
join
The inverse of split — combines an array into a single string:
["apple", "banana", "cherry"].join(",")
# => "apple,banana,cherry"
["hello", "world"].join(" ")
# => "hello world"
["a", "b", "c"].join
# => "abc"
A common idiom is split followed by map and join to transform every element:
"one two three".split.map(&:capitalize).join(", ")
# => "One, Two, Three"
String Interpolation
Interpolation lets you embed Ruby expressions directly inside a string. It only works with double quotes:
name = "Alice"
age = 30
"#{name} is #{age} years old"
# => "Alice is 30 years old"
'#{name} is #{age} years old'
# => "\#{name} is \#{age} years old" (literal, no interpolation)
You can call methods and use complex expressions inside #{}:
"I have #{[1, 2, 3].length} items"
# => "I have 3 items"
"Sum: #{1 + 2 + 3}"
# => "Sum: 6"
For cleaner output with numbers, Kernel#format or String#% is often preferable to inline arithmetic:
price = 19.99
format("Price: $%.2f", price)
# => "Price: $19.99"
"Price: $#{'%.2f' % price}"
# => "Price: $19.99"
Encoding
Ruby strings carry an encoding. Misaligned encodings are a common source of errors when working with non-ASCII text or file I/O.
Checking and Setting Encoding
str = "café"
str.encoding # => #<Encoding:UTF-8>
str.encode("ISO-8859-1")
# => "caf\xe9" (in ISO-8859-1 encoding)
Force Encoding
raw = "café".b # .b forces binary (ASCII-8BIT) encoding
raw.encoding # => #<Encoding:ASCII-8BIT>
Common Encoding Gotcha
Reading a file without specifying encoding can cause Encoding::CompatibilityError:
# Ruby 3.0+
File.read("data.txt", encoding: "UTF-8")
# or
File.read("data.txt", encoding: "r:UTF-8") # transcode from UTF-8
The Encoding.default_external setting controls the default for new strings. Set it in your application entry point:
Encoding.default_external = Encoding::UTF_8
Validating Encoding
"café".valid_encoding? # => true
"\xff".valid_encoding? # => false (invalid UTF-8 byte)
See Also
- /guides/ruby-working-with-strings/ — deeper look at working with strings
- /tutorials/ruby-strings/ — hands-on tutorial covering strings from the ground up
- /reference/string-methods/split/ — detailed reference for the
splitmethod - /reference/string-methods/gsub-bang/ — detailed reference for the destructive
gsub!method - /reference/encoding/ — Ruby encoding system reference