셸 리다이렉션은 프로그램의 입출력 스트림(표준입력·표준출력·표준에러)을 화면 대신 파일이나 다른 스트림으로 돌리는 문법이다. 핵심은 이 세 스트림이 각각 0, 1, 2라는 파일 디스크립터 번호를 가진다는 것, 그리고 >는 기본적으로 1번만 다룬다는 사실이다.
많은 사람이 2>&1을 부적처럼 붙이면서도 정확히 왜 그렇게 쓰는지는 모른다. 나도 한동안 그랬다. 순서를 바꿔 써서 로그가 통째로 날아간 뒤에야 원리를 제대로 팠다. 이 글은 그 원리를 정리한 것이다.
세 개의 스트림, 세 개의 번호
모든 유닉스 프로세스는 시작할 때 세 개의 표준 스트림을 물고 태어난다.
| FD | 이름 | 용도 |
|---|---|---|
| 0 | stdin | 입력 받기 |
| 1 | stdout | 정상 결과 출력 |
| 2 | stderr | 에러·진단 메시지 |
여기서 오해가 시작된다. ls foo > out.txt를 했는데 "그런 파일 없음" 에러는 여전히 화면에 뜬다. 왜? >는 1번(stdout)만 파일로 돌리기 때문이다. 에러는 2번(stderr)으로 나가서 그대로 터미널에 남는다. 정상 출력과 에러를 분리해 다루라고 유닉스가 일부러 채널을 나눠둔 것이다.
기본 리다이렉션 문법
| 문법 | 의미 |
|---|---|
> f | stdout을 f에 덮어쓰기 |
>> f | stdout을 f에 이어쓰기 |
2> f | stderr를 f로 |
< f | f를 stdin으로 |
2>&1 | stderr를 stdout이 가리키는 곳으로 |
&가 붙은 2>&1이 관건이다. "2번을, 1번이 지금 가리키는 곳으로 복제하라"는 뜻이다. 여기서 순서가 생명이다.
순서를 틀리면 로그가 샌다
# 올바름: 1을 먼저 파일로, 그다음 2를 1과 같은 곳으로
cmd > out.txt 2>&1
# 틀림: 2를 먼저 (아직 터미널인) 1로 복제, 그 후 1만 파일로
cmd 2>&1 > out.txt # ← stderr는 여전히 터미널로 샌다
리다이렉션은 왼쪽에서 오른쪽으로 순차 적용된다. 2>&1은 "그 시점에 1이 향하던 목적지"를 복사하는 것이라, 1을 아직 파일로 안 돌린 상태에서 이걸 쓰면 stderr가 터미널을 복제해버린다. 이 한 줄 때문에 나는 야간 배치의 에러 로그를 통째로 잃은 적이 있다.
Bash에는 이 실수를 막는 축약형이 있다.
cmd &> out.txt # stdout+stderr 둘 다 out.txt로 (덮어쓰기)
cmd &>> out.txt # 둘 다 이어쓰기
/dev/null과 스트림 분리
/dev/null은 무엇을 써도 삼켜버리는 블랙홀 장치다. 필요 없는 출력을 버릴 때 쓴다.
cmd 2> /dev/null # 에러만 버리고 정상 출력은 유지
cmd > /dev/null 2>&1 # 전부 버림 (조용히 실행)
cmd 1> out.log 2> err.log # 정상과 에러를 각각 다른 파일로
마지막 예시처럼 채널을 분리해두면, 배치 작업에서 "에러 파일 크기가 0이 아니면 알림"같은 감시가 아주 쉬워진다. stderr가 따로 존재하는 이유가 바로 이거다.
히어독과 히어스트링
입력 쪽 리다이렉션도 알아두면 스크립트가 깔끔해진다.
# 히어독: 여러 줄을 stdin으로
cat <<EOF > config.ini
port = 8080
host = localhost
EOF
# 히어스트링: 한 줄을 stdin으로
grep foo <<< "$LINE"
자주 묻는 질문
2>&1과 &> 는 완전히 같나요?
결과만 보면 둘 다 stdout과 stderr를 같은 곳으로 보냅니다. 다만 &>는 Bash/Zsh의 축약 문법이라 목적지가 파일일 때만 쓰고, POSIX sh 호환이 필요하면 > f 2>&1을 써야 합니다. 또 파이프로 넘길 땐 |& 또는 2>&1 |를 씁니다.
>와 2>&1의 순서가 왜 중요한가요?
리다이렉션은 왼쪽부터 순서대로 적용되고, 2>&1은 그 순간 1번이 가리키는 목적지를 복사하기 때문입니다. 1번을 파일로 돌리기 전에 2번을 복제하면 stderr가 여전히 터미널을 가리켜 파일에 안 들어갑니다. 반드시 > f를 먼저 쓰세요.
출력이 파일엔 잘 들어가는데 화면에도 동시에 보고 싶어요.
리다이렉션만으로는 한 곳만 갑니다. 파일과 화면에 동시에 뿌리려면 tee를 쓰세요. 예: cmd 2>&1 | tee out.log. tee는 stdin을 받아 화면과 파일 양쪽에 복제해 씁니다.
>로 덮어쓰는 실수가 무서운데 방지책이 있나요?
Bash에서 set -o noclobber를 켜면 >로 기존 파일을 덮어쓰려 할 때 거부합니다. 정말 덮어써야 할 땐 >|로 강제할 수 있습니다. 중요한 데이터 파일을 다루는 스크립트에 넣어두면 사고를 크게 줄입니다.

댓글 0