rubyguides

String#slice

str.slice(index) -> string or nil
str.slice(start, length) -> string or nil
str.slice(range) -> string or nil
str.slice(regexp) -> string or nil
str.slice(regexp, capture) -> string or nil

The .slice method (and its alias []) pulls a substring out by index, range, or regular expression. It returns nil when the requested piece is missing, so you can use it without extra bounds checks. That keeps the call site short and makes the extraction rule easy to read in place.

Syntax

str.slice(index)           # Single integer index
str.slice(start, length)   # Start index and length
str.slice(range)           # Range of indices
str.slice(regexp)          # Regular expression match
str.slice(regexp, capture) # Regex with capture group

# The [] alias works identically
str[index]
str[start, length]
str[start..end]
str[regexp]
str[regexp, capture]

Parameters

ParameterTypeDescription
indexIntegerZero-based position of a single character
startIntegerStarting position for substring extraction
lengthIntegerNumber of characters to extract
rangeRangeA range of indices (inclusive or exclusive)
regexpRegexpA regular expression to match against
captureInteger or SymbolCapture group index (0 for full match) or name

Worked examples

Example 1: integer and range indexing

str = "Hello, World!"

# Single character at index
puts str.slice(0)      # => "H"
puts str.slice(7)      # => "W"
puts str.slice(20)     # => nil (out of bounds)

# Start and length
puts str.slice(0, 5)   # => "Hello"
puts str.slice(7, 5)   # => "World"
puts str.slice(7, 100)  # => "World!" (length exceeds string)

# Range (inclusive)
puts str.slice(0..4)   # => "Hello"
puts str.slice(7..12)  # => "World"

# Range (exclusive)
puts str.slice(0...5)  # => "Hello"
puts str.slice(7...12) # => "World"

These small index-based forms are the easiest to scan because the input and the output line up directly. When the string position is already known, the call can stay short and avoid extra helper code.

That style works best when the extraction rule is fixed and obvious. The method then reads like a direct map from a position or range to the piece you want, which keeps the code easy to review and avoids extra helper methods for a one-off lookup.

When the input layout is stable, this is often the cleanest expression of the rule. The call says exactly what part of the value matters, and the reader does not need a separate helper to decode the offset.

Example 2: regex and capture groups

str = "The price is $42.99"

# Match with regex
puts str.slice(/\d+/)          # => "42"
puts str.slice(/[A-Z]+/)        # => "The"

# Capture groups
puts str.slice(/(\d+)\.(\d+)/, 1)  # => "42" (first capture)
puts str.slice(/(\d+)\.(\d+)/, 2)  # => "99" (second capture)

# Named capture groups
text = "Date: 2024-01-15"
puts text.slice(/(?<year>\d{4})/, :year)  # => "2024"

# Regex that doesn't match
puts str.slice(/xyz/)           # => nil

Regular expressions are useful when the value has structure but not a fixed offset. The capture form is especially handy when you need one piece from a larger pattern and do not want to scan the same text twice.

That makes regex extraction a good fit for log lines, dates, and other content that has a pattern but not a predictable character index. The call stays short while still expressing the rule directly, which is the main reason it reads so well in parsing code.

That directness also keeps the parsing rule close to the data shape. When the pattern is visible in the call, it is easier to change later without hunting through a larger parser.

Example 3: negative indices

str = "Hello"

# Negative indices count from the end
puts str.slice(-1)      # => "o"
puts str.slice(-5)      # => "H"
puts str.slice(-6)      # => nil (out of bounds)

# Negative ranges
puts str.slice(-5..-1)  # => "Hello"
puts str.slice(-4...-1) # => "ello"
puts str.slice(-6..-1)  # => nil

Negative indexes are a convenient reminder that Ruby can count from either end of the string. That makes boundary checks a little easier when you are trimming suffixes or reading from the tail of a value.

It is also a nice fit when you want to peel off a suffix without first calculating the string length. The range still says exactly what part of the value matters, which keeps the code readable and avoids a separate length variable just for slicing.

Negative indexes are also a good reminder that slice rules can express intent without extra arithmetic. If the tail of the value matters more than the exact character count, the method lets you say that directly.

Common patterns

safe string extraction with defaults

def get_code(input, default = "UNKNOWN")
  code = input.slice(/[A-Z]{3}/)
  code || default
end

get_code("ABC-123")    # => "ABC"
get_code("no-match")   # => "UNKNOWN"

This pattern keeps the fallback close to the extraction rule. If the match is missing, the caller gets a predictable default instead of a crash or a nil check in every caller.

That keeps the default visible in the same place as the pattern. A reader can see both the rule and the fallback without jumping to another helper method.

parsing structured data

line = "user:alice:admin"

username = line.slice(/:(.*):/, 1)
role = line.slice(/:([^:]+)$/, 1)

puts username  # => "alice"
puts role      # => "admin"

Regex captures are useful when the input already has a separator pattern, because you can pull out just the pieces you need without writing a custom parser. The extraction stays short even when the text format is a little irregular.

This is a common fit for lines that have a delimiter but still need a bit of cleanup. The regular expression stays small, and the post-processing can stay focused on the captured text.

truncating text

def truncate(text, max_length)
  return text if text.length <= max_length
  text.slice(0, max_length) + "..."
end

truncate("Hello, world!", 8)  # => "Hello, w..."

Truncation is a common use case because the slice tells you exactly how much of the original value is still relevant. The follow-up ellipsis is then just a presentation detail on top of the extracted prefix.

That approach works well when the UI only needs a preview. You can keep the extraction rule simple and let the display layer decide how to decorate the shortened text.

character-by-character processing

str = "ruby"
5.times do |i|
  char = str.slice(i)
  break unless char
  puts "Char #{i}: #{char}"
end

# Output:
# Char 0: r
# Char 1: u
# Char 2: b
# Char 3: y

Stepping through one character at a time is useful when the next decision depends on the current position. In that case, slice stays lightweight while still making the loop readable.

That can be a better fit than pulling the whole value apart first. The loop can ask for exactly one character at a time and stop as soon as it has what it needs.

extracting numbers from strings

phone = "Call me at 555-123-4567"

digits = phone.scan(/\d/).join
puts digits  # => "5551234567"

area_code = phone.slice(/\d{3}/)
puts area_code  # => "555"

The extraction and the follow-up work stay separate here, which makes the code easier to change later. If the input format shifts, you can adjust the slice rule without rewriting the rest of the processing.

That separation is especially helpful when the string format is controlled by another system. You can update the extraction rule without touching the code that stores or displays the result.

choosing the right slice form

slice is most helpful when the extraction rule is simple enough to state directly in the call. An index, range, or pattern can each make the intent obvious without a separate helper. That makes the method a nice fit for parsing and text cleanup, where the code often needs only a small piece of a larger string. The important part is to choose the form that best matches the data shape so the reader can understand the rule without chasing extra logic.

It also helps to keep the extraction and the follow-up work separate. Once the substring has been pulled out, the next step can trim it, convert it, or compare it without making the slice itself carry extra responsibilities. That separation keeps the code flexible and easier to test. A small string method call is often the clearest place to express what part of the input matters most.

The examples above move from fixed offsets to pattern-based matching and then to suffix handling, which shows the range of problems slice can solve without changing the method name. That consistency is useful because the reader only has to learn one API and then choose the form that matches the data.

See Also