리눅스 tr 명령어 사용법 - 문자 치환·삭제·압축과 Illegal byte sequence 해결
이 명령어를 언제 쓰는가
줄 단위가 아니라 문자 단위로 바꾸거나 지울 때 쓴다. 윈도우에서 넘어온 파일의 CR 제거, 여러 줄을 한 줄로 합치기, 연속 공백을 하나로 누르기, 로그에 섞인 제어문자 털어내기가 대표적이다.
sed 로도 되지만 tr 은 정규식을 해석하지 않고 문자를 1:1로 대응시키기만 해서 훨씬 빠르고 안전하다. 수 GB 로그를 훑을 때 이 차이가 눈에 보인다. 반대로 "문자열"을 바꾸는 일은 tr 이 못 한다 — 그건 sed 몫이다.
기본 형식
tr [옵션] SET1 [SET2]
- SET1 의 n번째 문자를 SET2 의 n번째 문자로 바꾼다. 문자열이 아니라 문자 대응표다.
- tr 은 파일 인자를 받지 않는다. 반드시 표준 입력으로 넣어야 한다.
- 범위는
a-z, 클래스는[:digit:]같은 형태로 쓴다.
파일을 직접 넘기면 에러가 나므로 tr 'a' 'b' < in.txt > out.txt 처럼 리다이렉션을 쓴다.
자주 쓰는 옵션
| 옵션 | 의미 | 예시 |
|---|---|---|
| (없음) | SET1 을 SET2 로 치환한다 | tr 'a-z' 'A-Z' |
-d | SET1 에 속한 문자를 삭제한다 | tr -d '\r' |
-s | 연속으로 반복된 문자를 하나로 압축한다 | tr -s ' ' |
-c / -C | SET1 을 여집합으로 뒤집는다. "이것 말고 전부" | tr -cd '[:print:]\n' |
-t | SET1 을 SET2 길이에 맞춰 자른다 (GNU 전용) | tr -t 'abc' 'xy' |
자주 쓰는 클래스는 [:alpha:], [:digit:], [:alnum:], [:space:], [:punct:], [:print:], [:cntrl:] 정도다. GNU 는 -C와 -c가 같지만, BSD 는 -C가 문자 기준, -c가 바이트 기준이라는 미묘한 차이가 있다.
실전 예제
1) 윈도우에서 올라온 설정 파일의 CR 을 제거한다. \r 때문에 스크립트가 bad interpreter: /bin/bash^M 로 죽는 그 문제의 해결책이다.
tr -d '\r' < deploy.sh > deploy.unix.sh && mv deploy.unix.sh deploy.sh
같은 파일로 리다이렉션하면 파일이 비어 버리므로 반드시 다른 이름으로 받아서 바꿔치기한다.
2) 여러 줄을 한 줄로 합친다. IP 목록을 방화벽 설정에 넣을 콤마 구분 문자열로 만드는 상황이다.
sort -u ip.txt | tr '\n' ',' | sed 's/,$//'
tr 은 개행을 콤마로 바꾸기만 하므로 맨 끝에 콤마가 남는다. 그 뒤처리를 sed 가 한다.
3) 공백으로 자리를 맞춘 출력을 cut 이 먹을 수 있게 만든다. 연속 공백을 하나로 눌러 준다.
df -P | tr -s ' ' | cut -d' ' -f1,6
4) 로그에서 단어 빈도를 센다. 알파벳이 아닌 문자를 전부 개행으로 바꿔 단어를 한 줄에 하나씩 만든 뒤 집계한다.
tr -cs '[:alpha:]' '\n' < app.log | tr 'A-Z' 'a-z' | sort | uniq -c | sort -rn | head -n 20
5) 제어문자가 섞여 터미널을 망가뜨리는 로그를 정리한다. 출력 가능한 문자와 개행만 남긴다.
tr -cd '[:print:]\n' < broken.log | less
6) 임시 비밀번호나 토큰을 만든다. 난수 바이트에서 쓸 수 있는 문자만 걸러 낸다.
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 20; echo
함정과 주의점
tr: Illegal byte sequence 는 로케일 문제다. UTF-8 로케일에서 tr 이 UTF-8 로 해석되지 않는 바이트를 만나면 그대로 죽는다. /dev/urandom 처리나 깨진 로그 정리에서 반드시 만나는 에러다.
$ printf 'x\xffy\n' | tr -cd '[:alnum:]'
tr: Illegal byte sequence
$ printf 'x\xffy\n' | LC_ALL=C tr -cd '[:alnum:]'
xy
해법은 LC_ALL=C 를 앞에 붙여 바이트 단위로 처리하게 만드는 것이다. macOS 에서 특히 자주 터진다.
tr 은 멀티바이트 문자를 다루지 못한다. 한글을 tr 로 치환·삭제하려는 시도는 대부분 글자가 아니라 바이트를 건드려서 문자열을 깨뜨린다. 한글이 대상이면 sed 나 python 을 쓴다.
SET1 이 SET2 보다 길면 남는 문자는 SET2 의 마지막 문자로 몰린다. tr 'abcde' 'xy' 는 c, d, e 를 전부 y 로 바꾼다. 의도한 동작이 아니면 개수를 맞추거나 GNU 의 -t 를 쓴다.
입력 파일과 출력 파일을 같게 두면 안 된다. tr -d '\r' < a.txt > a.txt 는 셸이 리다이렉션을 먼저 처리하면서 a.txt 를 0바이트로 만든 뒤 tr 을 실행한다. 원본이 그대로 사라진다.
함께 보면 좋은 명령어
sed— 문자가 아니라 문자열·정규식 단위로 바꿔야 할 때 쓴다.cut— tr 로 구분자를 정리한 뒤 열을 뽑는 짝으로 자주 붙는다.iconv— 문자 인코딩 자체를 바꾸는 일은 tr 이 아니라 iconv 담당이다.