Devin.KR
로그인

리눅스 tr 명령어 사용법 - 문자 치환·삭제·압축과 Illegal byte sequence 해결

개발자 조회 1

이 명령어를 언제 쓰는가

줄 단위가 아니라 문자 단위로 바꾸거나 지울 때 쓴다. 윈도우에서 넘어온 파일의 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'
-dSET1 에 속한 문자를 삭제한다tr -d '\r'
-s연속으로 반복된 문자를 하나로 압축한다tr -s ' '
-c / -CSET1 을 여집합으로 뒤집는다. "이것 말고 전부"tr -cd '[:print:]\n'
-tSET1 을 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 담당이다.