rubyguides

String#tr

str.tr(from_str, to_str) -> string

String#tr performs character‑by‑character translation: each character in the receiver that appears in from_str is replaced by the corresponding character in to_str. If to_str is shorter than from_str, its last character is repeated as needed. The translation is performed left‑to‑right, and the result is always a new string—the original is never modified.

The method is most useful when the replacement rule is simple and regular. You can clean up filenames, apply a small character cipher, or normalize a text stream without writing a longer regular expression. Because tr works one character at a time, it stays very predictable, and that predictability is often the real advantage.

Syntax

str.tr(from_str, to_str) -> new_string

Parameters

ParameterTypeDefaultDescription
from_strString(required)A set of characters to be replaced.
to_strString(required)A set of replacement characters. Each character in from_str is mapped to the character at the same position in to_str. If to_str is shorter, its last character is used for all extra characters in from_str.

Examples

Basic character replacement

"hello".tr("el", "ip")
# => "hippo"

Explanation: ei, lp.

Each character in from_str maps positionally to the character at the same index in to_str. Because both strings are the same length here, every character in from_str gets a unique replacement. When the replacement strings differ in length, Ruby applies a sensible fallback that is worth understanding.

Shorter to_str

"abcde".tr("bcd", "xy")
# => "axyxe"

Mapping: bx, cy, dy (last character of to_str repeats).

When to_str is shorter than from_str, the last character of to_str repeats for every unmatched character in from_str. This behaviour is predictable once you know the rule, and it means you can collapse many source characters into a single target without typing the replacement character over and over.

Range notation

"12345".tr("1-5", "a-e")
# => "abcde"

Ranges 1-5 and a-e expand to 12345 and abcde respectively.

Range notation is the feature that makes tr feel concise for common cleanup tasks. Instead of listing every letter of the alphabet, you can write a-z and let Ruby expand it for you. That keeps the call short even when the character set is large, which is especially helpful for case conversion and alphabet-wide substitutions.

Delete characters with empty to_str

"hello world".tr("aeiou", "")
# => "hll wrld"

Vowels are deleted because to_str is empty.

Passing an empty string as to_str is the idiomatic way to remove characters without reaching for a regular expression. It reads clearly at the call site and avoids the overhead of compiling a regex pattern for a simple character-stripping job.

Case‑insensitive translation

"Hello".tr("A-Z", "a-z")
# => "hello"

Uppercase letters are replaced by their lowercase equivalents.

Mapping A-Z to a-z is a classic tr one-liner that replaces a whole gsub invocation. Because the method works at the character level, it does not need to parse a pattern or track capture groups, so the call stays short and the intent stays clear.

Common Patterns

Simple cipher (Caesar shift)

def caesar(str, shift)
  alphabet = "abcdefghijklmnopqrstuvwxyz"
  shifted = alphabet.chars.rotate(shift).join
  str.tr(alphabet, shifted)
end

caesar("hello", 3)
# => "khoor"

A Caesar cipher is a neat demonstration of tr because the mapping is purely character-to-character with no pattern logic involved. Rotating the alphabet and feeding both strings into tr gives you a readable one-liner that would take several lines with a manual loop or a regex substitution.

Sanitising filenames

def sanitise_filename(name)
  name.tr("/\\:*?\"<>|", "_")
end

sanitise_filename("my/file:name.txt")
# => "my_file_name.txt"

Filename sanitisation is one of the most practical uses of tr because invalid filesystem characters map cleanly to a single replacement. The method handles every listed character in one pass, so you do not need to chain multiple gsub calls or write a character class inside a regex.

Converting spaces to _

"user profile".tr(" ", "_")
# => "user_profile"

Replacing a single space with an underline character is the simplest possible tr call, and it shows why the method is often preferred when the mapping is one-to-one. A regex can do the same job, but tr makes it obvious that every space becomes exactly one _ with no surprises.

Removing punctuation

"Hello, world!".tr(",.!?", "")
# => "Hello world"

The same pattern works for any set of punctuation characters you want to strip. You can list every symbol that is noise for your use case and pass an empty replacement string to remove them all in a single pass.

"Price: $19.99 (sale!)".tr("$,.!()", "")
# => "Price 1999 sale"

Removing punctuation with an empty to_str is a clean one-liner for text cleanup. The characters listed in from_str simply disappear from the output, which is exactly what you want when preparing text for tokenisation or display.

Complement mode with ^

The caret ^ at the start of from_str inverts the match, keeping only the listed characters:

"abc123def456".tr("^0-9", "")
# => "123456"

"hello world 42".tr("^a-z ", "")
# => "hello world "

This is a handy alternative to a regex negation class when you want to strip everything except a known set of characters. It reads clearly at the call site.

Squeezing repeated replacements

When tr produces consecutive identical characters, chain it with squeeze to clean up the result:

"abc123".tr("a-z", "x")
# => "xxx123"

"abc123".tr("a-z", "x").squeeze
# => "x123"

The first call replaces every letter with x, producing three consecutive copies. Squeezing afterwards collapses the run into a single character, which is useful when the replacement should act like a flag rather than a character-for-character mapping. This combination is a concise two-step pipeline.

# Digits also work with range notation
"a1b2c3".tr("0-9", "X")
# => "aXbXcX"

"hello".tr("a-z", "A-Z")
# => "HELLO"

Errors

  • ArgumentError: raised if from_str or to_str are not strings.
  • Encoding::CompatibilityError: raised if the encodings of the strings are incompatible.

Note: tr does not support regex patterns—use gsub for that.

That boundary is useful to remember because it keeps the method fast and straightforward. If the job is character mapping, tr is usually the direct answer. If the job is pattern matching, the extra power of gsub is the better fit. That split is also why tr works so well for cleanup steps that stay at the character level. It gives you a small, focused tool when you do not need the extra machinery of a regular expression.

See Also