파이썬 subprocess 완전 정복 ⚙️ — 외부 명령 실행·입출력 캡처·타임아웃
파이썬 subprocess 완전 정복 ⚙️
subprocess는 외부 프로그램 실행을 위한 표준 모듈입니다.
안전한 호출(shell=False), 출력 캡처, 타임아웃, 파이프, 환경/작업폴더 제어까지
운영에 필요한 기능을 모두 제공합니다.
1) 가장 쉬운 방법: subprocess.run
import subprocess
# 리스트 인자 + shell=False (기본) → 안전
result = subprocess.run(
["python", "--version"],
capture_output=True, text=True, check=False
)
print(result.stdout.strip()) # Python 3.x.x
print(result.returncode)
text=True로 자동 디코딩(기본 UTF-8)을 사용하고, 실패 시 예외를 원하면 check=True.
2) 표준출력/표준에러 캡처
import subprocess
res = subprocess.run(
["git", "status"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True
)
print("STDOUT:", res.stdout)
print("STDERR:", res.stderr)
3) 타임아웃과 에러 처리
import subprocess
try:
subprocess.run(["ping", "-c", "3", "example.com"], timeout=5, check=True)
except subprocess.TimeoutExpired as e:
print("타임아웃:", e)
except subprocess.CalledProcessError as e:
print("비정상 종료:", e.returncode, e.stderr)
팁: 타임아웃 시 하위 프로세스가 좀비로 남지 않도록 Popen을 쓰는 경우 proc.kill(); proc.communicate()로 버퍼를 비워 정리하세요.
4) 스트리밍/파이프: Popen
4-1. 실시간 출력 읽기
import subprocess
with subprocess.Popen(
["ping", "-c", "3", "example.com"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
) as proc:
for line in proc.stdout:
print(">>", line.strip())
code = proc.wait()
print("returncode:", code)
4-2. 두 명령 파이프 연결(UNIX 스타일)
import subprocess
p1 = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["grep", "python"], stdin=p1.stdout, stdout=subprocess.PIPE, text=True)
p1.stdout.close() # 파이프 종단 닫기(좀비 방지)
out, _ = p2.communicate()
print(out)
4-3. 입력을 프로그램에 전달
import subprocess
p = subprocess.Popen(["python", "-c", "print(input()[::-1])"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
out, _ = p.communicate("abcde\n")
print(out.strip()) # edcba
5) 환경변수/작업 디렉터리/권한
import os, subprocess
env = dict(os.environ)
env["API_TOKEN"] = "masked"
res = subprocess.run(
["python", "script.py"],
cwd="/path/to/project", # 작업 디렉터리
env=env, # 환경변수 오버라이드
capture_output=True, text=True
)
print(res.stdout)
6) Windows & POSIX 차이
- Windows는
dir, copy 같은 내장은 cmd 전용이므로 shell=True가 필요합니다. 대안: 파워셸 ["powershell","-Command","Get-ChildItem"].
- 백그라운드/콘솔 창 숨김은
creationflags=subprocess.CREATE_NO_WINDOW (Windows 전용)로 제어.
- POSIX에서 세션 분리는
preexec_fn=os.setsid 또는 start_new_session=True 사용.
7) 실전 레시피
7-1. JSON 출력 파싱
import subprocess, json
res = subprocess.run(["python","-c",'import json; print(json.dumps({"ok":1}))'],
capture_output=True, text=True, check=True)
payload = json.loads(res.stdout)
print(payload["ok"])
7-2. 안전한 사용자 입력 인자화
import subprocess
def grep_safe(pattern, path):
# 쉘 미사용, 리스트 인자화 → 쉘 인젝션 방지
return subprocess.run(["grep", "-R", pattern, path],
capture_output=True, text=True)
print(grep_safe("TODO", "."))
7-3. 타임아웃 + 강제 종료 정석 패턴
import subprocess
proc = subprocess.Popen(["sleep","10"])
try:
proc.wait(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait() # 버퍼 비우기/정리
7-4. 대용량 출력 안전 처리(메모리 폭주 방지)
import subprocess
# communicate는 전부 메모리에 적재될 수 있음 → 라인 스트리밍
with subprocess.Popen(["find","/","-maxdepth","1"], stdout=subprocess.PIPE, text=True) as p:
for line in p.stdout:
# 라인 단위 처리/저장/전송
pass
8) 보안 & 베스트 프랙티스
- shell=False를 기본으로. 정말 필요할 때만
shell=True (그리고 사용자 입력을 절대 그대로 넘기지 않기).
- 명령·인자는 리스트로 전달. 공백/특수문자 자동 이스케이프.
- 입력 데이터는
stdin으로 전달하고, 명령 인자에는 로직상 필요한 값만 주입.
- 타임아웃을 항상 고려하고, 예외 처리로 종료/정리 보장.
- 로그에 전체 명령행과 시크릿을 그대로 남기지 않기(마스킹).
요약
run은 간단 실행/캡처, Popen은 스트리밍·파이프 등 고급 제어.
- shell=False + 리스트 인자가 보안 기본값.
- 타임아웃/에러 처리/환경·작업폴더 제어로 운영 신뢰성 확보.
자주 묻는 질문(FAQ)
Q1. UTF-8이 아닌 출력은 어떻게 디코딩하나요?
A. text=True, encoding="cp949" 같이 인코딩을 지정하거나, res.stdout.encode().decode("cp949")로 변환하세요.
Q2. 비동기 병렬 실행은?
A. asyncio.create_subprocess_exec 또는 concurrent.futures.ThreadPoolExecutor로 Popen 호출을 병렬화하세요.
Q3. sudo가 필요한 명령은?
A. 권한 분리 원칙을 우선 적용하고, 필요한 경우에만 최소 범위로 승격하세요. 자동화 시엔 전용 사용자/권한을 분리하는 것이 안전합니다.
댓글
댓글 쓰기