ZetCode

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.

basic.pl
#!/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.

rot13.pl
#!/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.

flags.pl
#!/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.

rflag.pl
#!/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.

count.pl
#!/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.

rules.pl
#!/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

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

perlop: Quote-Like Operators

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Perl tutorials.