표준 입출력 — stdin/stdout 리다이렉션
이 토픽을 마치면
stdin, stdout, stderr의 역할을 설명할 수 있고, 터미널에서 리다이렉션(>, <)과 파이프(|)를 사용해서 프로그램의 입출력을 연결할 수 있습니다.
세 개의 통로
프로그램이 실행되면 운영체제가 자동으로 세 개의 통로를 열어줍니다:
| 이름 | 방향 | 용도 | Python |
|---|---|---|---|
| stdin (표준 입력) | 외부 → 프로그램 | 키보드 입력 | input(), sys.stdin |
| stdout (표준 출력) | 프로그램 → 외부 | 정상 결과 | print(), sys.stdout |
| stderr (표준 에러) | 프로그램 → 외부 | 에러/경고 | sys.stderr |
기본적으로 stdin은 키보드, stdout과 stderr는 화면(터미널)에 연결됩니다.
print()는 stdout에 쓴다
# hello.pyprint("안녕하세요") # stdout으로 출력$ python hello.py안녕하세요print()는 내부적으로 sys.stdout.write()를 호출합니다. 직접 사용할 수도 있습니다:
import sys
sys.stdout.write("stdout으로 출력\n")sys.stderr.write("stderr로 출력\n")두 줄 모두 화면에 나타나지만, 서로 다른 통로를 통합니다. 이 차이는 리다이렉션에서 드러납니다.
리다이렉션 — 통로를 파일로 돌리기
터미널에서 >와 <를 사용하면 입출력 방향을 바꿀 수 있습니다.
stdout을 파일로 (>)
$ python hello.py > output.txt화면에는 아무것도 안 나옵니다. print()의 결과가 output.txt 파일로 들어갑니다.
$ cat output.txt안녕하세요>>는 기존 내용에 이어쓰기:
$ python hello.py >> output.txt # 기존 내용 뒤에 추가stdin을 파일에서 (<)
# count.pyimport syslines = sys.stdin.readlines()print(f"총 {len(lines)}줄")$ python count.py < data.txt총 42줄data.txt의 내용이 키보드 입력 대신 stdin으로 들어갑니다.
stderr만 분리
# process.pyimport sysprint("처리 결과: 성공")sys.stderr.write("경고: 일부 데이터 누락\n")$ python process.py > result.txt 2> error.txt>는 stdout만, 2>는 stderr만 리다이렉션합니다. 결과와 에러를 별도 파일로 분리할 수 있습니다.
파이프 — 프로그램을 연결하기
| (파이프)는 앞 프로그램의 stdout을 뒷 프로그램의 stdin에 연결합니다:
$ cat data.csv | python process.py | python report.py > final.txtcat → (stdout) → | → (stdin) → process.py → (stdout) → | → (stdin) → report.py → final.txt각 프로그램은 자기 앞에서 오는 데이터만 받아서 처리하고, 결과를 다음으로 넘깁니다. 작은 프로그램을 조합해서 복잡한 작업을 처리하는 Unix 철학의 핵심입니다.
Python에서 stdin 한 줄씩 읽기
# upper.py — 입력을 대문자로 변환import sys
for line in sys.stdin: print(line.strip().upper())$ echo "hello world" | python upper.pyHELLO WORLD
$ cat names.txt | python upper.pyALICEBOBCHARLIEsys.stdin은 반복 가능(iterable)합니다. for문으로 한 줄씩 읽으면 메모리를 효율적으로 사용할 수 있습니다.
input()과 stdin의 관계
name = input("이름: ") # 프롬프트 → stderr? 아니요, stdoutinput()은 내부적으로:
- 프롬프트 문자열을 stdout에 출력
- stdin에서 한 줄을 읽어서 반환
그래서 파이프와 함께 쓸 때 주의가 필요합니다:
$ echo "철수" | python -c "name = input(); print(f'안녕, {name}')"안녕, 철수파이프에서는 프롬프트가 의미 없으므로, CLI 도구를 만들 때는 input() 대신 sys.stdin을 직접 읽는 편이 낫습니다.
실무 활용 예시
# csv_filter.py — 특정 조건의 행만 출력import sysimport csv
reader = csv.reader(sys.stdin)header = next(reader)print(",".join(header))
for row in reader: if float(row[2]) > 100: # 3번째 컬럼이 100 초과 print(",".join(row))$ cat sales.csv | python csv_filter.py > filtered.csvstdin/stdout을 사용하면 파일 이름을 하드코딩하지 않아도 됩니다. 어떤 파일이든 파이프로 연결할 수 있으므로 재사용성이 높아집니다.
파이프 체이닝 — UNIX 철학
# 로그에서 ERROR만 찾아 빈도순으로 정렬$ cat server.log | grep "ERROR" | sort | uniq -c | sort -rn | head -5각 프로그램이 하나의 일만 잘 하고, 파이프로 연결합니다. Python 스크립트도 이 체인의 한 고리가 될 수 있습니다:
# word_count.py — 단어 빈도 계산import sysfrom collections import Counter
words = []for line in sys.stdin: words.extend(line.strip().split())
for word, count in Counter(words).most_common(10): print(f"{count:>5} {word}")$ cat article.txt | python word_count.py 42 the 31 and 28 tostderr로 진행 상황 출력
import sys
total = 1000for i in range(total): # 처리 로직... if i % 100 == 0: print(f"진행: {i}/{total}", file=sys.stderr)
print(f"결과: {i * 2}") # 실제 출력 → stdout$ python process.py > results.txt진행: 0/1000 ← 화면에 보임 (stderr)진행: 100/1000...stdout은 파일로 리다이렉트되어 results.txt에 저장되고, stderr는 여전히 화면에 표시됩니다. 진행 상황 메시지와 결과 데이터를 분리하는 실용적인 패턴입니다.
핵심 정리
| 문법 | 의미 |
|---|---|
> | stdout을 파일로 (덮어쓰기) |
>> | stdout을 파일로 (이어쓰기) |
< | 파일을 stdin으로 |
2> | stderr를 파일로 |
| | stdout → 다음 프로그램의 stdin |
2>&1 | stderr를 stdout에 합침 |
/dev/null — 출력 버리기
# stdout 버리기 (에러만 보기)$ python noisy_script.py > /dev/null
# stderr 버리기 (결과만 보기)$ python noisy_script.py 2> /dev/null
# 둘 다 버리기 (완전 무음)$ python noisy_script.py > /dev/null 2>&1/dev/null은 "쓰레기통"입니다. 자동화 스크립트에서 불필요한 출력을 억제할 때 사용합니다.
# Python에서도 동일한 패턴import os, sys
if os.environ.get("QUIET"): sys.stdout = open(os.devnull, "w")subprocess — Python에서 파이프 구성
import subprocess
# 외부 명령 실행 + stdout 캡처result = subprocess.run( ["ls", "-la"], capture_output=True, text=True)print(result.stdout)print(result.stderr)
# 파이프 체이닝p1 = subprocess.Popen(["cat", "data.txt"], stdout=subprocess.PIPE)p2 = subprocess.Popen(["grep", "ERROR"], stdin=p1.stdout, stdout=subprocess.PIPE)output = p2.communicate()[0].decode()Python 내에서 셸 명령의 stdin/stdout을 프로그래밍 방식으로 연결할 수 있습니다.
print()는 stdout, 에러 메시지는 stderr. 이 분리를 이해하면 리다이렉션과 파이프가 자연스러워집니다.