DEV Community

dpm_bush
dpm_bush

Posted on Originally published at sshflow.com

A Practical Guide to `tr`: Character-Level Text Processing in Linux

tr is a small command-line tool for changing text one character at a time. It can translate characters, delete them, or collapse repeated characters—but it does not replace words or edit files in place.

That makes it useful in pipelines: pass text to standard input, transform it, and send the result to the terminal or another command.

Start with a translation

The basic form is:

tr [OPTION]... SET1 [SET2]
Enter fullscreen mode Exit fullscreen mode

To map characters in SET1 to the corresponding characters in SET2, provide both sets. For example, convert lowercase letters to uppercase:

printf 'hello, linux\n' | tr '[:lower:]' '[:upper:]'
Enter fullscreen mode Exit fullscreen mode

Output:

HELLO, LINUX
Enter fullscreen mode Exit fullscreen mode

The POSIX character classes [:lower:] and [:upper:] express the intent more clearly than a range such as a-z. Quote sets so the shell passes them to tr as intended.

Translation is positional and character-by-character. This example maps a to 1, b to 2, and c to 3:

printf 'cab\n' | tr 'abc' '123'
Enter fullscreen mode Exit fullscreen mode

The result is 312. For replacing longer strings or matching patterns, use a tool designed for that job; tr only works on individual characters.

Read from a file without changing it

tr does not accept a filename as an input operand. Use shell input redirection to feed a file into it:

tr '[:upper:]' '[:lower:]' < input.txt
Enter fullscreen mode Exit fullscreen mode

This prints the transformed contents; input.txt stays unchanged. To save the result, redirect to a different file:

tr '[:upper:]' '[:lower:]' < input.txt > output.txt
Enter fullscreen mode Exit fullscreen mode

Do not redirect output to the same file you are reading. The shell can truncate the destination before tr gets a chance to read it. If you need a refresher on inspecting file contents or combining files, see this guide to the Linux cat command.

Delete or keep selected characters

Use -d to delete every character that belongs to a set. This removes digits but leaves punctuation and spaces in place:

printf 'order 58: item 204\n' | tr -d '[:digit:]'
Enter fullscreen mode Exit fullscreen mode

Output:

order : item
Enter fullscreen mode Exit fullscreen mode

To do the opposite—keep digits and remove everything else—combine -d with -c, which complements the set:

printf 'order 58: item 204\n' | tr -cd '[:digit:]\n'
Enter fullscreen mode Exit fullscreen mode

Output:

58204
Enter fullscreen mode Exit fullscreen mode

The newline is included in the set so the output retains a line ending. Here, [:digit:] matches individual digit characters, not a whole number as a unit.

Squeeze repeated characters

The -s option reduces each run of repeated characters in a set to one character. To collapse multiple spaces:

printf 'too    many spaces\n' | tr -s ' '
Enter fullscreen mode Exit fullscreen mode

Output:

too many spaces
Enter fullscreen mode Exit fullscreen mode

You can also translate tabs and spaces to a single space, then squeeze the result:

printf 'red\t\tblue\n' | tr -s '[:blank:]' ' '
Enter fullscreen mode Exit fullscreen mode

[:blank:] matches horizontal blanks such as spaces and tabs. With translation enabled, -s squeezes repeated characters from the translated set (SET2). The newline is not a blank, so it remains intact.

A quick way to choose an operation

Goal Pattern Effect
Translate tr 'abc' '123' Map characters by position
Delete tr -d '[:digit:]' Remove characters in the set
Keep only a set tr -cd '[:digit:]\n' Delete characters outside the set
Squeeze tr -s ' ' Collapse repeated spaces

These operations compose naturally with other filters. For example, you might normalize or remove characters before searching; the Linux grep guide covers the search step and common pattern-matching options.

Portability and text encoding notes

Common character classes include [:digit:], [:lower:], [:upper:], and [:blank:]. Quoted ranges such as 'a-z' are also common, but range ordering can depend on locale. Prefer character classes for case conversion when they express what you mean.

Behavior around locales, complements, and multibyte characters can differ between implementations. GNU tr provides -C to complement characters rather than bytes; do not assume that option is available everywhere. If byte-versus-character behavior matters, check the local tr manual page.

For predictable ASCII-oriented processing, you can run a command under the C locale:

LC_ALL=C tr '[:lower:]' '[:upper:]'
Enter fullscreen mode Exit fullscreen mode

That changes how the utility interprets input, so use it when ASCII-oriented behavior is appropriate—not as a universal fix for arbitrary text.

One final distinction is worth remembering: tr writes transformed output to standard output. It is a filter, not an in-place editor. Use it in a pipeline or redirect its output deliberately, and choose a separate destination file when preserving the original matters.

I originally published a more detailed version of this guide on the SSHFlow blog.

I'm also building SSHFlow — an SSH client where every server gets its own workspace for terminals, SFTP, code, and databases.

Top comments (0)