tr (translate) works on characters, not words or lines. It replaces characters from one set with characters from another, deletes them, or squeezes repeats. It only reads standard input, so you always pipe or redirect into it.
tr SET1 SET2 < input
command | tr [options] SET1 [SET2]
Replace characters
echo "hello world" | tr 'a-z' 'A-Z' # HELLO WORLD
echo "hello world" | tr ' ' '_' # hello_world
echo "$PATH" | tr ':' '\n' # PATH, one entry per line
echo "a,b,c" | tr ',' '\t' # commas to tabs
Characters are matched by position: the first character of SET1 becomes the first of SET2, and so on. tr 'abc' 'xyz' turns a→x, b→y and c→z. It does not replace the string “abc”.
Character classes work too, and are safer with locales:
tr '[:lower:]' '[:upper:]' < file
Delete characters: -d
tr -d '\r' < windows.txt > unix.txt # remove Windows CR (fix ^M line endings)
echo "phone: +1 (555) 010-9999" | tr -d -c '0-9\n' # keep digits only
tr -d '[:space:]' < file # remove all whitespace, including newlines
echo "v1.2.3" | tr -d 'v' # 1.2.3
Stripping \r fixes shell scripts edited on Windows that fail with bad interpreter: /bin/bash^M. See cat -A to spot those characters.
Squeeze repeats: -s
echo "too many spaces" | tr -s ' ' # too many spaces
tr -s '\n' < file # remove blank lines
df -h | tr -s ' ' | cut -d' ' -f5 # make aligned output cut-friendly
Squeezing spaces lets cut work on whitespace-aligned command output.
Complement: -c
-c means “every character not in SET1”. It’s usually combined with -d or -s:
tr -cd '[:print:]\n' < file # strip non-printable characters
tr -cs '[:alnum:]' '\n' < text.txt | sort | uniq -c | sort -rn | head # word frequency
The second command turns every run of non-alphanumeric characters into a newline, giving one word per line. Then it counts the words. See sort and uniq.
Generate random strings
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32; echo
This is good for passwords and tokens. openssl rand -base64 24 is an alternative. Note the -dc: delete everything except the allowed characters.
Other handy tricks
echo "Hello" | tr 'A-Za-z' 'N-ZA-Mn-za-m' # ROT13
tr '\0' '\n' < /proc/1/environ # make NUL-separated data readable
tr '\n' ',' < list.txt | sed 's/,$/\n/' # join lines with commas
/proc/<pid>/environ and /proc/<pid>/cmdline separate entries with NUL bytes. tr '\0' '\n' makes them readable. That’s useful for checking which environment variables a running service actually has.
Things tr can’t do
- Replace whole strings — use
sed 's/old/new/g'. See sed. - Work on specific fields — use
awkorcut. - Edit files in place —
tronly reads stdin. Write to a new file and move it over the original:
tr -d '\r' < script.sh > script.tmp && mv script.tmp script.sh
Never redirect into the same file you’re reading (tr … < f > f). That empties it.
- Multibyte characters — GNU
trworks byte by byte. It mishandles UTF-8 characters outside ASCII, such as accented letters. Usesedorpython3for those.
Quick reference
| Task | Command |
|---|---|
| Uppercase | tr '[:lower:]' '[:upper:]' |
| Replace char | tr ' ' '_' |
| Delete char | tr -d '\r' |
| Keep only digits | tr -cd '0-9' |
| Squeeze spaces | tr -s ' ' |
| Split on colon | tr ':' '\n' |
| Random string | tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32 |