Redirect: 파일 디스크립터를 바꿔 입출력 방향을 제어하는 셸 기능
리다이렉션(Redirection)은 프로세스의 표준 입력·출력·에러가 향하는 대상을 키보드·화면에서 파일이나 다른 디스크립터로 바꾸는 셸 기능이다. 셸이 명령 실행 전에 파일 디스크립터(File Descriptor)를 `open`, `dup2` 시스템 콜로 재배치하는 방식으로 동작한다. `>`, `>>`, `<`, `2>`, `2>&1`의 의미와 순서 규칙
By 송민성
1. 개념
리다이렉션(Redirection)은 명령이 읽고 쓰는 표준 스트림(Standard Stream)의 연결 대상을 실행 전에 바꾸는 셸의 기능이다. 유닉스 계열 프로세스는 기본적으로 세 개의 스트림을 갖는다.
| 번호 | 이름 | 기본 연결 대상 |
|---|---|---|
| 0 | 표준 입력(stdin) | 키보드(터미널) |
| 1 | 표준 출력(stdout) | 화면(터미널) |
| 2 | 표준 에러(stderr) | 화면(터미널) |
이 번호가 파일 디스크립터(File Descriptor, FD)다. 리다이렉션은 이 번호가 가리키는 대상을 파일, 장치, 다른 FD로 교체한다.
주요 연산자는 다음과 같다.
| 문법 | 의미 |
|---|---|
| cmd > file | stdout을 file로 보냄 (파일을 비우고 덮어씀) |
| cmd >> file | stdout을 file 끝에 추가(append) |
| cmd < file | file을 stdin으로 사용 |
| cmd 2> file | stderr를 file로 보냄 |
| cmd > file 2>&1 | stdout과 stderr를 모두 file로 보냄 |
| cmd &> file | 위와 같은 효과의 bash 축약형 |
| cmd << EOF | 히어 도큐먼트(Here Document) |
2. 왜 사용하는가
- 결과 저장: 명령 출력을 파일로 남겨 나중에 확인하거나 다른 도구에 넘긴다.
- 출력 분리: 정상 결과와 에러 메시지를 따로 저장해 문제를 추적하기 쉽게 한다.
- 자동화: 사용자 입력 대신 파일이나 히어 도큐먼트로 입력을 공급한다.
- 소음 제거: 필요 없는 출력을
/dev/null로 버린다.
프로그램 자체는 "FD 1에 쓴다"만 알고 있고, 그 FD가 어디로 연결되는지는 모른다. 그래서 프로그램을 수정하지 않고도 입출력 대상을 바꿀 수 있다.
3. 동작 원리
리다이렉션은 프로그램이 아니라 셸이 처리한다. 셸은 자식 프로세스를 fork한 뒤, exec로 명령을 실행하기 직전에 FD를 재배치한다.
ls > out.txt의 흐름은 다음과 같다.
- 셸이
fork로 자식 프로세스를 만든다. - 자식에서
open("out.txt", O_WRONLY | O_CREAT | O_TRUNC)로 파일을 연다. 새 FD(예: 3)를 얻는다. dup2(3, 1)로 FD 1이 out.txt를 가리키게 한다.- 불필요한 FD 3을
close한다. exec로ls를 실행한다.ls는 평소처럼 FD 1에 쓰지만 실제로는 파일에 기록된다.
exec는 열린 FD를 유지하므로 재배치된 상태가 새 프로그램에 그대로 넘어간다.
순서가 중요한 이유
리다이렉션은 왼쪽에서 오른쪽으로 차례대로 처리된다. 2>&1은 "FD 2를 *현재* FD 1이 가리키는 곳으로 복제"라는 뜻이다.
cmd > file 2>&1: 먼저 FD 1이 file을 가리키고, 그다음 FD 2가 FD 1의 대상(file)을 가리킨다. 둘 다 file로 간다.cmd 2>&1 > file: 먼저 FD 2가 FD 1의 현재 대상(터미널)을 가리키고, 그다음 FD 1만 file로 바뀐다. stderr는 여전히 터미널로 나온다.
4. 코드 예제
#!/usr/bin/env bash
# 실습용 임시 디렉터리에서 실행한다.
set -u
workdir=$(mktemp -d)
cd "$workdir"
# 1) 덮어쓰기와 추가
echo "first" > log.txt
echo "second" >> log.txt
echo "--- log.txt"
cat log.txt
# 2) stdout과 stderr 분리
# 존재하는 파일(log.txt)과 존재하지 않는 파일(nofile)을 함께 조회
ls log.txt nofile > out.txt 2> err.txt
echo "--- out.txt"; cat out.txt
echo "--- err.txt"; cat err.txt
# 3) 둘을 한 파일로 합치기
ls log.txt nofile > all.txt 2>&1
echo "--- all.txt"; cat all.txt
# 4) 순서에 따른 차이: stderr가 터미널에 그대로 출력된다
echo "--- 순서가 반대인 경우(에러가 화면에 보임)"
ls log.txt nofile 2>&1 > only_out.txt
echo "--- only_out.txt"; cat only_out.txt
# 5) 입력 리다이렉션
wc -l < log.txt
# 6) 히어 도큐먼트
cat << 'EOF'
여러 줄 입력을
스크립트 안에서 바로 전달한다.
EOF
# 7) 출력 버리기
ls nofile > /dev/null 2>&1
echo "exit code: $?"
# 8) 파이프와 함께 stderr 전달 (bash)
ls log.txt nofile 2>&1 | grep -c "No such file"
cd - > /dev/null
rm -rf "$workdir"5. 시간 복잡도 또는 성능 특성
리다이렉션 자체의 연산 비용은 실행당 open, dup2, close 같은 시스템 콜 몇 번 수준이며, 명령 실행 전체 비용에 비하면 작다. 성능에 영향을 주는 것은 주로 대상의 특성이다.
- 파일 대상: 쓰기는 커널 페이지 캐시(page cache)를 거치므로 보통 디스크 속도에 즉시 묶이지 않는다. 데이터가 디스크에 반영되는 시점은 커널이 결정하며, 확실히 반영하려면
sync계열 호출이 필요하다. - `/dev/null`: 쓰기를 모두 버리는 장치라 출력이 많은 명령의 출력 비용을 줄이는 데 쓸 수 있다.
- 터미널 대상: 화면 렌더링이 병목이 될 수 있어, 대량 출력은 파일로 돌리는 편이 대체로 빠르다.
6. 실무 사용 사례
- 크론(cron) 작업 로그:
0 3 * * * /opt/backup.sh >> /var/log/backup.log 2>&1 - 배포·빌드 스크립트: 정상 로그와 에러 로그를 분리해 CI 아티팩트로 보관한다.
- 백그라운드 실행:
nohup ./server > server.log 2>&1 & - 설정 파일 생성: 히어 도큐먼트로 설정 파일을 만든다.
- 권한이 필요한 파일에 쓰기:
sudo tee와 파이프를 조합한다.sudo echo x > /etc/file은 리다이렉션을 sudo가 아닌 현재 사용자의 셸이 처리하므로 권한 오류가 날 수 있다. 대신echo x | sudo tee /etc/file > /dev/null을 쓴다.
7. 주의할 점
- `>`는 기존 내용을 즉시 지운다. 명령이 실패해도 파일은 이미 비워진 상태다. 같은 파일을 읽으면서 쓰는
sort data.txt > data.txt는 데이터를 잃는다. 임시 파일에 쓴 뒤mv하거나sort -o처럼 도구가 제공하는 옵션을 쓴다. - `2>&1` 순서: 반드시
> file뒤에 둔다. - `&>`는 POSIX 표준이 아니다. bash 등 일부 셸의 확장이므로 이식성이 필요한
sh스크립트에서는> file 2>&1을 쓴다. - 덮어쓰기 방지: bash에서
set -o noclobber를 켜면>가 기존 파일을 덮어쓰지 못한다. 강제로 덮어쓰려면>|를 쓴다. - `sudo`와 리다이렉션: 위에서 설명한 대로 리다이렉션은 sudo 바깥의 셸이 수행한다.
- 버퍼링: 파일이나 파이프로 연결되면 많은 프로그램이 stdout을 터미널일 때와 달리 완전 버퍼링(full buffering)으로 바꾼다. 그래서 로그가 늦게 보일 수 있다.
8. 핵심 정리
- 리다이렉션은 프로세스의 FD 0(stdin), 1(stdout), 2(stderr)가 가리키는 대상을 바꾸는 셸 기능이다.
- 셸이
fork후exec직전에open과dup2로 FD를 재배치하므로 프로그램은 변경 없이 동작한다. >는 덮어쓰기,>>는 추가,<는 입력,2>는 에러 출력 변경이다.- 처리 순서는 왼쪽에서 오른쪽이므로
cmd > file 2>&1이 올바른 형태다. - 같은 파일을 읽으면서
>로 쓰지 않고, 권한이 필요한 파일에는sudo tee를 쓴다.