opscape

Text processing

tr

Translate, squeeze or delete characters from standard input; for case changes, line endings and stripping characters.

tr [OPTION...] SET1 [SET2]

15 of 15 examples run in Debian 13.7 on 2026-10-31 · coreutils 9.7-3 · manual: tr(1)

tr works on single characters, not words or patterns. With two sets it translates: each character of SET1 is replaced by the character at the same position in SET2, so tr abc xyz turns a into x, b into y and c into z. With -d it deletes every character in SET1, and with -s it squeezes runs of the same character down to one.

Sets can be written as ranges (a-z), escapes (\n, \t, \r) and classes ([:digit:], [:space:], [:upper:]). -c takes the complement of SET1, "every character not in this set", which is how you keep only certain characters.

Two things surprise people. tr reads only standard input; it takes no file name, so you write tr ... < file. And GNU tr works on bytes, so it cannot change the case of accented or other non-ASCII letters. For replacing strings or patterns, use sed.

Examples

Convert to upper case

tr a-z A-Z < todo.txt
explain
output
BUY MILK
FIX LOGIN BUG
WRITE TESTS
FIX LOGOUT BUG
DEPLOY V2.4

Each lower-case ASCII letter is mapped to the upper-case letter at the same position in the second range.

Use character classes

echo 'Mixed Case Text' | tr '[:upper:]' '[:lower:]'
explain
output
mixed case text

[:upper:] and [:lower:] name the letter classes; they are clearer than ranges and must be quoted so the shell leaves the brackets alone.

Remove Windows carriage returns

tr -d '\r' < data/windows.txt | od -c
explain
output
0000000   l   i   n   e       o   n   e  \n   l   i   n   e       t   w
0000020   o  \n
0000022

-d '\r' deletes every carriage return, converting CRLF line endings to Unix ones. od -c shows that only \n is left at each line end.

Squeeze repeated spaces

echo 'alpha   beta     gamma' | tr -s ' '
explain
output
alpha beta gamma

-s ' ' collapses each run of spaces into one, which makes the text safe to split with cut -d ' '.

Remove blank lines

tr -s '\n' < data/settings.conf
explain
output
# demo service settings
host = 0.0.0.0
port = 8080
# logging
log_level = info
log_file = /var/log/demo/app.log
# max_connections = 100
timeout = 30

Squeezing newlines turns \n\n into \n, which deletes the empty line in the middle of the file (a blank line at the very start would survive, since it is a single newline).

One PATH entry per line

echo "$PATH" | tr : '\n'
explain
output
/usr/local/bin
/usr/bin
/bin

Translating every : into a newline lists each directory of $PATH on its own line.

Change a delimiter

tr , '\t' < data/sales.csv | head -n 3
explain
output
date	region	product	units	price
2026-03-01	north	widget	12	4.50
2026-03-01	south	gadget	3	19.99

Every comma becomes a tab. This is only safe when no field contains a comma of its own.

Keep only certain characters

echo 'Phone: (555) 123-4567' | tr -cd '0-9'; echo
explain
output
5551234567

-c complements the set, so -cd '0-9' deletes everything that is not a digit, including the newline; the trailing echo adds it back.

Replace everything outside a set

echo 'My File (final).txt' | tr -c 'A-Za-z0-9.\n' _
explain
output
My_File__final_.txt

Every character that is not a letter, digit, dot or newline becomes _, a quick way to make a safe file name. The newline is in the set so the line ending survives.

ROT13 in one line

echo 'Hello, World' | tr 'A-Za-z' 'N-ZA-Mn-za-m'
explain
output
Uryyb, Jbeyq

Each letter is mapped 13 places along the alphabet; running the same command again restores the original.

When SET2 is shorter than SET1

echo abcdef | tr abcdef xy
echo abcdef | tr -t abcdef xy
output
xyyyyy
xycdef

GNU tr pads a short SET2 by repeating its last character, so c to f all become y. With -t SET1 is truncated to SET2's length instead, and only a and b change.

Random characters from /dev/urandom

tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16; echo
explain
output
33opkcQU27VZE1DL

tr deletes every byte that is not a letter or digit, and head stops after 16 of the survivors. The result is different on every run.

Trap: non-ASCII letters are not converted

echo 'café naïve' | tr '[:lower:]' '[:upper:]'
explain
output
CAFé NAïVE

GNU tr handles bytes, so the two-byte letters é and ï pass through unchanged: CAFé NAïVE. Use sed 's/.*/\U&/' or awk '{print toupper($0)}' in a UTF-8 locale instead.

Trap: unquoted brackets are shell globs

touch a
echo banana | tr [a-z] [A-Z]
output
b[n[n[

Unquoted, [a-z] is a glob, and the file a matches it, so the shell runs tr a '[A-Z]'. a is mapped to [ and the rest of the set is ignored: b[n[n[. Always quote tr sets.

Error: tr does not take a file name

tr a-z A-Z todo.txt
explain
outputexit 1
tr: extra operand ‘todo.txt’
Try 'tr --help' for more information.

tr only reads standard input, so todo.txt is an extra operand. Write tr a-z A-Z < todo.txt.

Common pitfalls

  • Quote the sets: tr [a-z] [A-Z] lets the shell expand [a-z] as a glob if a one-letter file name exists in the current directory.

  • tr takes no file arguments. Use tr ... < file or a pipe.

  • tr replaces characters, not strings: tr 'cat' 'dog' maps c→d, a→o, t→g everywhere. Use sed 's/cat/dog/g' for strings.

  • GNU tr is byte-based: it does not change the case of é, ß or other multi-byte characters, and a set containing them can corrupt UTF-8 text.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • When SET2 is shorter than SET1, GNU pads it with its last character; POSIX leaves this unspecified and other implementations may differ. Make the sets the same length to be portable.

  • BSD/macOS tr is multibyte-aware in UTF-8 locales, so tr '[:lower:]' '[:upper:]' does convert é there; GNU tr does not.

Options worth knowing

A selection, described in our own words. For the full list, see tr(1) or tr --help.

OptionWhat it does
-d, --deleteDelete the characters in SET1 instead of translating.
-s, --squeeze-repeatsReplace runs of a repeated character from the last set with one occurrence.
-c, --complementUse every character not in SET1.
-CSame as -c.
-t, --truncate-set1Shorten SET1 to the length of SET2 before translating.

Related