Perl tr///
last modified October 20, 2026
The tr/// operator replaces, deletes and counts characters in a
string. It is often confused with substitution, but it never uses regular
expressions. Instead it maps one list of characters to another, which makes it
the right tool for case conversion, simple ciphers, character cleanup, and
counting.
Counting and replacing characters
The tr/// operator, also written y///, transliterates
characters. Each character of the search list is replaced by the character at
the same position in the replacement list. Unlike s/// it does not
use regular expressions; it works on single characters and is fast. The return
value is the number of characters matched.
#!/usr/bin/perl use strict; use warnings; use v5.34.0; my $text = 'hello world'; (my $upper = $text) =~ tr/a-z/A-Z/; say $upper; my $count = ($text =~ tr/l//); say "letter l occurs $count times"; my $length = ($text =~ tr/a-z//); say "lowercase letters: $length"; say $text;
The operator changes the string in place, so the example copies it first with
the (my $upper = $text) =~ ... idiom. When the replacement list is
empty, the string is left unchanged and tr only counts. The text
contains three l characters and ten lowercase letters, since the
space is not in the range. The original string is untouched.
$ perl basic.pl HELLO WORLD letter l occurs 3 times lowercase letters: 10 hello world
Ranges and ROT13
Ranges such as a-z expand to all characters between the two ends.
Several ranges can be combined in one list, which makes classic substitution
ciphers a one-line operation.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
sub rot13 {
my ($s) = @_;
$s =~ tr/A-Za-z/N-ZA-Mn-za-m/;
return $s;
}
my $secret = rot13('Hello, World!');
say $secret;
say rot13($secret);
The search list A-Za-z is mapped to N-ZA-Mn-za-m, so
every letter moves 13 places in the alphabet and other characters are left
alone. Applying the same function twice restores the original text, which is the
property that makes ROT13 its own inverse.
$ perl rot13.pl Uryyb, Jbeyq! Hello, World!
The /d, /s and /c flags
Three flags change how the lists are used. With /d, characters in
the search list that have no counterpart in the replacement list are deleted.
With /s, runs of the same replaced character are squeezed into one.
With /c, the search list is complemented, so it matches every
character that is not listed.
#!/usr/bin/perl use strict; use warnings; use v5.34.0; my $s = 'a1b2c3'; (my $letters = $s) =~ tr/0-9//d; say "delete digits: $letters"; my $dup = 'aabbccddeeff'; (my $squeezed = $dup) =~ tr/a-z//s; say "squeeze: $squeezed"; my $mixed = 'Hello, World! 123'; (my $only = $mixed) =~ tr/a-zA-Z//cd; say "keep letters: $only"; (my $masked = $mixed) =~ tr/a-zA-Z/_/c; say "mask others: $masked"; my $spaces = 'too many spaces'; (my $one = $spaces) =~ tr/ //s; say "squeeze spaces: $one"; my $words = 'a--b---c'; (my $collapsed = $words) =~ tr/a-z/_/cs; say "complement and squeeze: $collapsed";
The first example deletes the digits, and the second one squeezes repeated
letters. Combining /c with /d keeps only the listed
characters, which is the usual way to strip everything else. With
/c and a replacement of _, every other character is
turned into an underscore. Using the same list for search and replacement with
/s, as in tr/ //s, collapses runs of spaces. The last
example combines /c and /s: each run of non-letters
becomes a single underscore.
$ perl flags.pl delete digits: abc squeeze: abcdef keep letters: HelloWorld mask others: Hello__World_____ squeeze spaces: too many spaces complement and squeeze: a_b_c
The /r flag
With the /r flag the operator leaves the original string alone and
returns the transliterated copy. This makes tr usable inside
expressions, in map, and in chains.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $dirty = 'Perl 5.34 - rocks!';
my $clean = $dirty =~ tr/a-zA-Z //cdr;
say "clean: $clean";
my @names = qw(alice bob carol);
my @caps = map { tr/a-z/A-Z/r } @names;
say "upper: @caps";
say "original: @names";
say "lowercased: ", 'ABC-123' =~ tr/A-Z/a-z/r;
The first call keeps only letters and spaces. Inside map the
/r flag is essential: without it the operator would modify
$_, which aliases the array element, and return a count. The
original array and strings remain unchanged. The /r flag needs Perl
5.14 or later.
$ perl rflag.pl clean: Perl rocks upper: ALICE BOB CAROL original: alice bob carol lowercased: abc-123
Counting character classes
Because tr returns the number of matched characters, it is the
fastest way to count characters or classes of characters in a string.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $dna = 'GATTACAGATTACA';
my %count;
$count{$_}++ for split //, $dna;
say join ', ', map { "$_=$count{$_}" } sort keys %count;
my $gc = ($dna =~ tr/GC//);
printf "GC content: %.1f%%\n", 100 * $gc / length $dna;
my $text = "Line one\nLine two\n\tTabbed";
say "newlines: ", ($text =~ tr/\n//);
say "whitespace: ", ($text =~ tr/ \t\n//);
my $complement = reverse($dna =~ tr/ACGT/TGCA/r);
say "reverse complement: $complement";
The nucleotide counts come from a hash filled by splitting the string into
characters. The GC content uses tr/GC//, which counts both letters
in one pass. Escape sequences such as \n and \t work
in the lists, so line breaks and whitespace can be counted too. The last line
builds the reverse complement of the DNA string by transliterating with
/r and reversing the result in scalar context.
$ perl count.pl A=6, C=2, G=2, T=4 GC content: 28.6% newlines: 2 whitespace: 5 reverse complement: TGTAATCTGTAATC
Rules and limitations
Two details surprise newcomers: how a short replacement list is extended, and the fact that the lists are not interpolated.
#!/usr/bin/perl
use strict;
use warnings;
use v5.34.0;
my $s = 'abcde';
(my $short = $s) =~ tr/a-e/xy/;
say "short replacement list: $short";
(my $deleted = $s) =~ tr/a-e/xy/d;
say "with /d: $deleted";
my ($from, $to) = ('a-e', 'A-E');
my $converted = $s;
eval "\$converted =~ tr/$from/$to/" or die $@;
say "variable ranges via eval: $converted";
my $var = 'ae';
(my $literal = 'var $x') =~ tr/$var/X/;
say "no interpolation: $literal";
When the replacement list is shorter than the search list and /d is
not used, its last character is repeated, so a becomes
x and all the remaining letters become y. With
/d the extra characters are deleted instead. Because variables are
not interpolated, tr/$var/X/ transliterates the literal characters
$, v, a and r. To build the
lists at runtime, put the whole operator in a string eval, and only
do this with trusted data.
$ perl rules.pl short replacement list: xyyyy with /d: xy variable ranges via eval: ABCDE no interpolation: XXX Xx
Best Practices
- Use tr for characters and s/// for patterns:
tris faster and clearer for single-character work. - Prefer /r in expressions: it avoids modifying the original and works in
mapand chains. - Count with an empty replacement:
tr/x//never modifies the string. - Do not expect interpolation: use
evalonly when the lists must be built at runtime, with trusted data. - Use /cd to whitelist characters: it deletes everything except the listed characters.
In this article we have covered the tr/// operator: replacing and
counting characters, ranges, the /d, /s,
/c and /r flags, and its rules about list length and
interpolation.
Source
Author
List all Perl tutorials.