Home / Alt manpages / tr(1)

  • tr(1)
  • User command
  • linux

Translate, Delete and Clean Text Safely with tr

Half the shell one-liners floating around lean on tr to fix case, strip stray characters or collapse doubled separators. This guide gives you the small set of patterns that cover nearly all of it, in about ten minutes with just a shell and some input piped through standard input. This uses GNU tr from coreutils 9.4, the version installed here, and none of it needs elevated privileges or touches a file unless you explicitly redirect output over it.

1. Confirm the installed command

Check which executable the shell will actually run, and record its version:

$ command -v tr
/usr/bin/tr
$ tr --version | head -n 1
tr (GNU coreutils) 9.4

tr is a standard-input to standard-output filter: it translates, squeezes or deletes characters according to one or two character arrays. It never edits a named file in place, no matter how it's invoked.

Checkpoint

If command -v tr points somewhere unexpected, stop and check your PATH before this goes anywhere near a script. A different implementation can have different extensions or locale behaviour.

2. Translate one set of characters into another

Give tr two arrays and it maps characters from the first to the matching position in the second. This turns lower-case ASCII letters into upper-case:

$ printf '%s\n' 'server status: ok' | tr 'a-z' 'A-Z'
SERVER STATUS: OK

The shell hands the string to printf, the pipe hands it to tr, there's no output file yet. That makes it safe to experiment with: the original text is only ever the input to the pipeline.

  • A shorter second array gets stretched. GNU tr repeats its final character to make up the length. Excess characters in a longer second array are simply ignored. Don't rely on either behaviour by accident; write both arrays out in full when the mapping matters.
  • Character classes exist too. The installed manual documents [:lower:] and [:upper:] for case work, but locale rules can make class expansion broader than the plain ASCII ranges above. Stick to the ASCII form for protocol fields, identifiers, or anything else deliberately ASCII.

3. Delete characters you don't want

Add -d and give it only the array of characters to strip. This removes every digit:

$ printf '%s\n' 'job-1842 finished' | tr -d '0-9'
job- finished

Deletion is literal at the character-array level. 0-9 means the characters from 0 to 9 in order; it does not mean "find a number", and it doesn't touch surrounding whitespace.

For a small literal set, just put those characters in the array:

$ printf '%s\n' 'name: [email protected]' | tr -d ':'
name [email protected]

Tip

Single quotes stop the shell expanding characters in the array before tr ever sees them. A backslash, bracket expression or range can mean something to both the shell and to tr, so test the exact command against representative input before it goes into a longer pipeline.

4. Squeeze repeated characters

Use -s to collapse each run of a repeated character down to one occurrence, handy for making a separator predictable:

$ printf '%s\n' 'alpha:::beta::::gamma' | tr -s ':'
alpha:beta:gamma

With only -s, the array is the sole argument. It won't strip a leading or trailing separator, only shorten runs in the middle. Combine deletion and squeezing when you need both, in this order because squeezing happens after translation or deletion:

$ printf '%s\n' 'alpha   beta   gamma' | tr -d '\r' | tr -s ' '
alpha beta gamma

That ordering matters whenever a translation step creates new repeated characters. While debugging a pipeline, run each stage separately so you can see exactly which one changed the data.

5. Handle whitespace and newlines explicitly

The manual documents escapes like \n for newline, \t for tab and \r for carriage return. This converts tabs to spaces:

$ printf 'one\ttwo\n' | tr '\t' ' '
one two

Don't confuse an actual newline with how the shell happens to display a command. Removing newlines from small, controlled input looks like this:

$ printf 'one\ntwo\n' | tr -d '\n'
onetwo

Warning

Stripping record separators from arbitrary input joins records into one long value, and that's often irreversible once the pipeline has thrown away the boundaries. Keep a copy of the original and inspect the transformed output before it goes anywhere else, if line structure matters.

6. Avoid locale surprises

GNU tr only fully supports safe single-byte locales, where every possible input byte is one character, and the installed manual names the C locale as safe on GNU systems. For byte-oriented work, pin it explicitly:

$ printf '%s\n' 'abc123' | LC_ALL=C tr -d '[:digit:]'
abc

Character classes such as [:alpha:], [:space:] and [:punct:] are locale-aware. That's useful for human text, but the same script can produce a different result under a different locale. Pick an explicit locale when reproducibility matters, and say so in the script.

Checkpoint

Test with non-ASCII input if your real data has any. tr is a character-array filter, not a Unicode normaliser, and a byte-oriented cleanup command should never be mistaken for one.

7. Write output without destroying the input

Redirect to a new path while you're still testing:

$ tr -s ' ' < raw.txt > cleaned.txt
$ cmp -- raw.txt cleaned.txt
raw.txt cleaned.txt differ: byte 6, line 1

That cmp result is expected if repeated spaces were present. Silence and a status of 0 means the files are byte-for-byte identical. Inspect the result with sed, od, or whatever consumes it downstream before you replace the original.

Warning

Shell redirection with > truncates its destination before tr even runs. Never write tr ... > raw.txt when raw.txt is also the input: use a temporary file in the same directory, verify it, and only replace the original if that's genuinely intended and recoverable from backup. tr has no undo. If you've overwritten a file with no backup or source copy, the removed characters are gone.

Done means

  • You confirmed the executable and GNU coreutils version the script will actually use.
  • You can tell translation, deletion and squeezing apart, and have tested their ordering together.
  • Ranges, escapes and character classes match the real data you're processing.
  • You picked an explicit locale for byte-sensitive or reproducible work.
  • Your test pipeline writes to a new path and never truncates its own input.