subprocess
1. 개요
1. 개요
subprocess 모듈은 파이썬 표준 라이브러리에 포함된 모듈로, 새로운 프로세스를 생성하고 실행하는 기능을 제공한다. 이 모듈은 주로 외부 시스템 명령어나 다른 실행 파일을 호출하고, 그 프로세스와의 입출력 통신을 관리하며, 실행 결과에 대한 반환 코드를 확인하는 데 사용된다.
기존의 os.system()이나 os.popen() 함수를 대체하기 위해 파이썬 2.4 버전에서 처음 도입되었다. subprocess 모듈은 더욱 유연하고 안전한 방식으로 자식 프로세스를 제어할 수 있는 통합된 인터페이스를 제공하는 것을 목표로 한다. 이를 통해 쉘 파이프라인을 구성하거나 복잡한 자동화 스크립트를 작성하는 것이 가능해진다.
이 모듈은 시스템 프로그래밍과 프로세스 관리 작업에 필수적이며, 파이썬 소프트웨어 재단에 의해 개발 및 유지보수되고 있다. 주된 사용 사례로는 외부 컴파일러 호출, 파일 시스템 작업 명령 실행, 다른 프로그램과의 데이터 교환 등이 있다.
2. 기능
2. 기능
subprocess 모듈의 핵심 기능은 파이썬 프로그램 내부에서 새로운 프로세스를 생성하고 관리하는 것이다. 이 모듈을 사용하면 운영체제의 명령줄 인터페이스를 통해 실행할 수 있는 모든 외부 프로그램이나 시스템 명령어를 파이썬 코드에서 직접 호출하고 그 결과를 제어할 수 있다. 이는 파이썬만으로는 처리하기 어려운 시스템 수준의 작업이나 기존의 독립적인 실행 파일을 활용해야 하는 경우에 매우 유용하다.
주요 기능은 크게 세 가지로 구분된다. 첫째는 새로운 자식 프로세스를 생성하여 외부 명령을 실행하는 것이다. 둘째는 실행 중인 프로세스의 표준 입력, 표준 출력, 표준 오류 스트림에 접근하여 데이터를 주고받는 상호작용이 가능하다. 셋째는 프로세스의 실행이 종료된 후 그 반환 코드를 확인하여 성공 또는 실패 여부를 판단할 수 있게 해준다.
또한 subprocess는 복잡한 쉘 파이프라인이나 입출력 리디렉션을 직접 구현할 수 있는 기능을 제공한다. 이를 통해 한 프로세스의 출력을 다른 프로세스의 입력으로 연결하는 등 유닉스 철학에 기반한 강력한 자동화 스크립트를 작성할 수 있다. 이 모든 과정에서 보안을 고려해 쉘을 통하지 않고도 직접 인자를 전달하는 방식을 기본으로 지원한다.
3. 주요 모듈 및 함수
3. 주요 모듈 및 함수
3.1. subprocess.run()
3.1. subprocess.run()
subprocess.run() 함수는 파이썬 3.5 버전에서 도입된 고수준 API로, 외부 명령어를 실행하는 가장 권장되는 방법이다. 이 함수는 단일 호출로 명령어를 실행하고 완료될 때까지 기다린 후, 실행 결과를 담은 CompletedProcess 인스턴스를 반환한다. subprocess.run()은 내부적으로 더 복잡한 저수준 Popen 클래스를 사용하지만, 사용자에게는 더 간결하고 안전한 인터페이스를 제공한다.
함수의 주요 매개변수로는 실행할 명령어를 문자열 또는 문자열 시퀀스로 지정하는 args가 있다. shell=True를 설정하면 쉘을 통해 명령어를 해석하게 되지만, 보안상의 위험이 있어 가능하면 문자열 리스트 형태로 args를 전달하고 shell=False(기본값)를 사용하는 것이 좋다. capture_output=True를 설정하면 자식 프로세스의 표준 출력과 표준 에러를 캡처하여 반환 객체의 stdout과 stderr 속성에 저장한다. timeout 매개변수를 사용하면 지정된 시간(초)이 지난 후 프로세스를 강제 종료하고 TimeoutExpired 예외를 발생시킬 수 있다.
subprocess.run()은 명령어 실행이 완료된 후 CompletedProcess 객체를 반환한다. 이 객체의 returncode 속성을 확인하여 명령어의 성공(0) 또는 실패(0이 아닌 값) 여부를 판단할 수 있다. stdout과 stderr 속성에는 캡처된 출력이 바이트 문자열 또는 텍스트 문자열(인코딩 지정 시) 형태로 저장되어 있어, 이후 로깅이나 결과 분석에 활용할 수 있다. 이렇게 반환 코드와 출력을 한 번에 처리할 수 있는 구조는 스크립트 자동화 작업에 매우 효율적이다.
subprocess.run()은 check=True 옵션과 함께 사용될 때 특히 유용하다. 이 옵션을 설정하면 자식 프로세스가 0이 아닌 종료 코드로 종료될 경우 자동으로 CalledProcessError 예외를 발생시킨다. 이를 통해 명령어 실행 실패를 명시적으로 처리하는 에러 핸들링 코드를 쉽게 작성할 수 있으며, 기존의 subprocess.call()이나 subprocess.check_call() 함수의 기능을 대체하는 더 현대적인 방법으로 간주된다.
3.2. Popen 클래스
3.2. Popen 클래스
subprocess 모듈의 핵심은 subprocess.Popen 클래스이다. 이 클래스는 새로운 프로세스를 생성하고 실행하는 데 필요한 가장 세밀한 제어를 제공한다. subprocess.run() 함수가 단순한 명령 실행에 편리한 고수준 인터페이스라면, Popen 클래스는 프로세스의 표준 입력(stdin), 표준 출력(stdout), 표준 오류(stderr) 스트림에 대한 실시간 상호작용, 백그라운드 실행, 복잡한 파이프라인 구성 등 저수준 작업을 가능하게 한다.
Popen 생성자는 실행할 명령어와 다양한 인자를 받는다. 주요 인자로는 args(실행할 명령 리스트), stdin, stdout, stderr(각각 subprocess.PIPE, 파일 객체, subprocess.DEVNULL 등으로 리다이렉션 설정), shell(쉘을 통해 실행할지 여부), cwd(작업 디렉토리), env(환경 변수) 등이 있다. 생성 직후 프로세스가 시작되며, 호출자는 생성된 Popen 객체를 통해 프로세스를 관리한다.
이 객체의 메서드를 사용하면 실행 중인 프로세스와 다양한 방식으로 상호작용할 수 있다. poll() 메서드는 프로세스가 종료되었는지 즉시 확인하고, 종료되었다면 반환 코드를, 아니라면 None을 반환한다. wait(timeout=None) 메서드는 프로세스가 종료될 때까지 또는 지정된 타임아웃 시간이 경과할 때까지 대기한다. communicate(input=None, timeout=None) 메서드는 표준 입력에 데이터를 전송하고, 표준 출력과 표준 오류를 읽어 프로세스가 종료되기를 기다리는 가장 안전한 방법이다. 또한 terminate()나 kill() 메서드를 사용하여 프로세스에 시그널을 보낼 수 있다.
Popen 클래스는 장시간 실행되는 서버 프로세스를 띄우거나, 다른 프로그램과 지속적으로 데이터를 주고받아야 하는 자동화 스크립트, 또는 subprocess.run()으로는 구현하기 어려운 복잡한 실행 흐름이 필요할 때 주로 사용된다. 이 클래스를 통해 파이썬 프로그램은 운영체제의 프로세스 생성 및 관리 기능을 완전히 활용할 수 있게 된다.
3.3. 표준 입출력 처리
3.3. 표준 입출력 처리
subprocess 모듈을 사용할 때는 자식 프로세스의 표준 입력, 표준 출력, 표준 오류 스트림을 유연하게 제어할 수 있다. subprocess.run()이나 subprocess.Popen 생성자에서 stdin, stdout, stderr 매개변수를 사용하여 이 흐름을 관리한다. 기본적으로 이 값들은 모두 None으로 설정되어 있어, 자식 프로세스는 부모 프로세스(즉, 파이썬 스크립트)의 표준 스트림을 상속받는다.
보다 세밀한 제어를 위해, 이 매개변수에는 몇 가지 특수한 값을 전달할 수 있다. subprocess.PIPE를 사용하면 파이썬 프로그램과 자식 프로세스 사이에 새로운 파이프가 생성된다. 이를 통해 파이썬 코드에서 proc.communicate() 메서드를 호출하여 자식 프로세스에 데이터를 전송하거나(stdin), 그 출력을 읽어올 수 있다(stdout, stderr). 또 다른 옵션으로 subprocess.DEVNULL을 사용하면 운영체제의 null 장치(예: /dev/null, NUL)로 스트림을 리디렉션하여 출력을 완전히 버리거나 입력을 차단할 수 있다. 또한, 이미 열려 있는 파일 객체나 파일 디스크립터를 직접 전달하는 것도 가능하다.
표준 출력과 표준 오류를 하나의 스트림으로 합쳐서 처리해야 하는 경우도 있다. 예를 들어, stderr=subprocess.STDOUT으로 설정하면 표준 오류 메시지가 표준 출력과 동일한 파이프나 파일로 전송된다. 이는 두 출력을 함께 순서대로 읽어야 하거나, 일부 명령줄 인터페이스 도구들이 진단 정보를 표준 오류가 아닌 표준 출력으로 보내는 경우에 유용하다. 이러한 입출력 처리 기능은 스크립트 언어를 이용한 자동화 작업에서 외부 프로그램과의 복잡한 상호작용을 구현하는 데 핵심적이다.
4. 사용 예시
4. 사용 예시
4.1. 간단한 명령어 실행
4.1. 간단한 명령어 실행
subprocess 모듈을 사용하여 외부 명령어를 실행하는 가장 기본적인 방법은 subprocess.run() 함수를 활용하는 것이다. 이 함수는 파이썬 3.5에서 도입되었으며, 이전에 사용되던 call(), check_call(), check_output() 등의 함수를 통합한 고수준 인터페이스로 권장된다. run() 함수는 명령어 실행이 완료될 때까지 대기한 후, 실행 결과를 담은 CompletedProcess 객체를 반환한다. 이 객체를 통해 명령어의 종료 상태 코드나 표준 출력 결과를 쉽게 확인할 수 있다.
가장 간단한 형태로는 실행할 명령어와 인자를 문자열의 리스트 형태로 전달한다. 예를 들어, 윈도우에서는 dir 명령을, 유닉스 계열 운영 체제에서는 ls 명령을 실행하여 현재 디렉토리의 파일 목록을 얻을 수 있다. 이때 shell=True 인자를 사용하지 않고 리스트 형태로 명령을 전달하는 것이 보안상 안전하며, 명령어와 인자를 명확히 분리할 수 있다는 장점이 있다.
명령어 실행 후의 결과를 확인하려면 반환된 객체의 속성을 사용한다. returncode 속성은 프로세스의 종료 코드를 나타내며, 일반적으로 0은 성공을 의미한다. stdout 속성에는 명령어의 표준 출력이 담겨 있으며, subprocess.run() 호출 시 capture_output=True 또는 stdout=subprocess.PIPE 인자를 지정해야 이 값을 캡처할 수 있다. 캡처한 출력은 기본적으로 바이트열 형태이므로, 텍스트로 처리하기 위해서는 text=True 인자를 추가하거나 universal_newlines=True 인자를 사용하여 문자열로 디코딩할 수 있다.
다음은 subprocess.run()을 사용한 기본적인 예시 코드이다.
```python
import subprocess
# 'ls -l' 명령어 실행 (Unix/Linux/macOS)
result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(f"종료 코드: {result.returncode}")
print(f"표준 출력:\n{result.stdout}")
# 에러가 발생한 경우 stderr 확인
if result.returncode != 0:
print(f"표준 에러: {result.stderr}")
```
이 예제는 리눅스나 맥OS의 쉘에서 자주 사용하는 ls -l 명령을 실행하고, 그 출력과 종료 코드를 확인하는 방법을 보여준다. 윈도우 환경에서는 ['cmd', '/c', 'dir']과 같은 형식으로 명령을 구성하여 실행할 수 있다.
4.2. 파이프라인 사용
4.2. 파이프라인 사용
subprocess 모듈을 사용하면 유닉스 셸이나 윈도우 명령 프롬프트의 파이프라인 기능을 파이썬 코드 내에서 구현할 수 있다. 파이프라인은 한 프로세스의 표준 출력(stdout)을 다른 프로세스의 표준 입력(stdin)으로 연결하여 여러 명령어를 연쇄적으로 실행하는 방식을 말한다. 이를 통해 복잡한 데이터 처리 작업을 여러 단계로 나누어 효율적으로 수행할 수 있다.
subprocess.run() 함수나 subprocess.Popen 클래스를 사용할 때 stdout과 stdin 매개변수를 subprocess.PIPE로 설정하면 파이프라인을 구성할 수 있다. 예를 들어, ls -l 명령어의 결과를 grep 명령어의 입력으로 전달하는 과정은 두 개의 subprocess.Popen 객체를 생성하고, 첫 번째 프로세스의 출력을 두 번째 프로세스의 입력으로 연결하여 구현한다. 이 방식은 os.popen() 함수를 사용하는 것보다 더 세밀한 제어와 안전성을 제공한다.
파이프라인 사용 시 주의할 점은 데드락을 방지하는 것이다. 한 프로세스가 출력 파이프에 많은 데이터를 쓰고, 다른 프로세스가 이를 읽지 않으면 파이프 버퍼가 가득 차 프로세스가 멈출 수 있다. 이를 해결하기 위해 communicate() 메서드를 사용하거나, 출력을 파일 객체로 리디렉션하거나, 스레드를 활용하여 비동기적으로 입출력을 처리하는 방법이 있다.
이러한 파이프라인 기능은 로그 분석, 데이터 전처리, 시스템 모니터링과 같은 자동화 스크립트에서 외부 도구들을 조합하여 강력한 작업 흐름을 만들 때 유용하게 활용된다.
4.3. 타임아웃 설정
4.3. 타임아웃 설정
subprocess 모듈을 사용하여 외부 명령을 실행할 때, 명령이 무한정 실행되거나 응답하지 않는 상황을 방지하기 위해 타임아웃을 설정할 수 있다. subprocess.run() 함수의 timeout 매개변수를 사용하면 이를 간편하게 구현할 수 있다. 설정된 시간(초 단위) 내에 명령이 완료되지 않으면 subprocess.TimeoutExpired 예외가 발생하여 프로그램이 정상적으로 예외 처리를 할 수 있게 한다.
예를 들어, ping 명령어에 타임아웃을 5초로 설정하여 실행하는 코드는 다음과 같다. 명령이 5초 안에 끝나지 않으면 예외가 발생하고, try-except 블록을 통해 이를 처리할 수 있다. 이는 장시간 실행되는 배치 작업이나 사용자 입력을 기다리는 대화형 프로그램을 호출할 때 특히 유용하다.
subprocess.Popen 클래스를 사용하는 경우, timeout 매개변수가 직접 제공되지 않으므로 communicate() 메서드에 타임아웃 값을 전달하거나, wait() 메서드와 스레드 또는 시그널을 조합하여 유사한 기능을 구현해야 한다. 이는 더 세밀한 프로세스 제어가 필요할 때 사용되는 방법이다.
타임아웃 기능은 자동화 스크립트의 안정성을 높이는 핵심 요소 중 하나이다. 이를 통해 스크립트가 특정 작업에서 멈추는 것을 방지하고, 예상치 못한 지연에 대한 오류 처리 로직을 강화할 수 있다.
5. 보안 고려사항
5. 보안 고려사항
subprocess 모듈을 사용할 때는 보안 측면을 신중히 고려해야 한다. 가장 중요한 원칙은 사용자 입력이나 신뢰할 수 없는 외부 데이터를 그대로 쉘 명령어의 일부로 사용하지 않는 것이다. 만약 shell=True 매개변수를 설정하고 사용자 입력을 명령어 문자열에 포함시키면, 악의적인 사용자가 쉘 명력어 삽입(Shell Injection) 공격을 수행할 수 있다. 예를 들어, 사용자가 입력한 파일명에 ; rm -rf /와 같은 명령어가 포함되어 있다면, 의도치 않게 시스템 파일이 삭제되는 치명적인 결과를 초래할 수 있다.
이러한 위험을 방지하기 위해서는 shell=True의 사용을 가능한 한 피하고, 대신 subprocess.run()이나 subprocess.Popen()을 호출할 때 명령어와 인수를 리스트 형태로 분리하여 전달하는 것이 안전하다. 이 방법은 운영체제가 인수를 별도의 문자열로 처리하도록 하여, 사용자 입력이 추가적인 쉘 명령어로 해석되는 것을 근본적으로 차단한다. 또한, 실행할 프로그램의 경로를 하드코딩하거나 신뢰할 수 있는 출처에서만 가져와야 한다.
또 다른 보안 고려사항은 실행 중인 자식 프로세스의 권한 관리이다. 스크립트 자체가 높은 권한(Superuser)으로 실행 중이라면, subprocess를 통해 호출된 외부 프로그램도 동일한 권한을 상속받게 된다. 따라서 악의적인 프로그램이 실행되거나 예상치 못한 동작을 할 경우 그 피해가 커질 수 있다. 필요한 최소 권한 원칙(Principle of Least Privilege)에 따라, 프로세스는 작업을 수행하는 데 필요한 최소한의 권한만을 가져야 한다.
마지막으로, 외부 프로세스의 실행에 타임아웃을 설정하지 않으면, 프로세스가 무한정 대기하거나 응답하지 않을 경우 메인 애플리케이션이 함께 멈출 수 있다. subprocess.run()의 timeout 매개변수를 사용하여 적절한 제한 시간을 설정함으로써 서비스 거부(DoS) 공격이나 예기치 않은 장애로 인한 영향도를 줄일 수 있다.
6. 다른 모듈과의 비교
6. 다른 모듈과의 비교
6.1. os.system()
6.1. os.system()
os.system() 함수는 파이썬의 os 모듈에 포함된 함수로, 시스템 쉘을 통해 단일 명령어를 실행하는 데 사용된다. 이 함수는 명령어 문자열을 쉘에 전달하고, 해당 명령어의 실행이 완료될 때까지 호출한 프로그램을 블록킹한다. 실행이 끝나면 명령어의 종료 상태 코드를 반환하며, 명령어의 표준 출력은 호출한 프로그램의 표준 출력으로 직접 연결되어 콘솔에 출력된다.
subprocess 모듈과 비교할 때, os.system()은 기능이 매우 제한적이다. 이 함수는 실행 중인 명령어의 표준 출력이나 표준 에러를 캡처하여 프로그램에서 활용할 수 없으며, 명령어에 표준 입력을 전달하는 것도 불가능하다. 또한, 명령어 실행에 타임아웃을 설정하거나, 실행 중인 프로세스를 안전하게 종료시키는 세밀한 제어도 제공하지 않는다.
따라서 os.system()은 간단한 명령어를 실행하고 그 결과를 콘솔에서 확인만 하면 될 때 사용하기 적합하다. 그러나 외부 프로그램과의 상호작용이 필요하거나, 출력 결과를 파이썬 코드에서 처리해야 하거나, 보안 및 유연성이 중요한 경우에는 subprocess 모듈의 run()이나 Popen 클래스를 사용하는 것이 권장된다.
6.2. os.popen()
6.2. os.popen()
os.popen() 함수는 파이썬의 os 모듈에 포함된 구식 함수로, 서브프로세스를 생성하고 그 프로세스의 표준 입력(stdin)이나 표준 출력(stdout)에 연결된 파일 객체를 반환한다. 이 함수는 주로 쉘 명령어를 실행하고 그 결과를 파이썬 프로그램 내에서 읽어오는 데 사용되었다. 예를 들어, os.popen('ls -l').read()와 같은 방식으로 명령어의 출력을 문자열로 가져올 수 있다.
그러나 os.popen()은 기능이 제한적이며, 특히 프로세스의 종료 상태를 쉽게 얻거나 표준 에러 출력(stderr)을 별도로 처리하는 데 어려움이 있다. 또한 내부적으로 쉘을 호출하기 때문에, 사용자 입력을 제대로 검증하지 않을 경우 명령어 삽입과 같은 보안 위험이 존재할 수 있다. 이러한 이유로 파이썬 공식 문서에서는 이 함수의 사용을 권장하지 않는다.
subprocess 모듈이 도입되면서 os.popen()의 기능은 더욱 강력하고 안전한 subprocess.Popen 클래스나 subprocess.run() 함수로 대체되었다. subprocess 모듈을 사용하면 입출력 스트림을 세밀하게 제어하고, 타임아웃을 설정하며, 프로세스의 반환 코드를 쉽게 확인할 수 있어 현대적인 시스템 프로그래밍 및 자동화 스크립트 작성에 훨씬 적합하다.
7. 여담
7. 여담
subprocess 모듈은 파이썬 2.4 버전에서 도입되기 전까지는 시스템 명령어를 실행하는 방법이 여러 가지로 분산되어 있었습니다. 초기에는 os.system()이나 os.popen() 함수를 주로 사용했으며, 더 복잡한 작업을 위해 popen2 모듈이나 commands 모듈을 이용하기도 했습니다. 이러한 방식들은 기능이 제한적이거나 사용법이 일관되지 않아 개발자들에게 불편함을 주었습니다.
이러한 문제를 해결하기 위해 등장한 subprocess 모듈은 "하나의 통일된 인터페이스"를 제공하는 것을 목표로 설계되었습니다. 특히 subprocess.run() 함수는 파이썬 3.5에서 추가되어 가장 권장되는 고수준 API가 되었으며, 이전에 사용되던 call(), check_call(), check_output() 함수들의 기능을 대체하고 코드를 더 명확하게 만들어 줍니다. 이 모듈의 등장은 파이썬을 이용한 시스템 프로그래밍과 자동화 스크립트 작성의 표준을 크게 단순화시켰습니다.
subprocess를 사용할 때는 특히 보안 측면에서 주의가 필요합니다. 사용자 입력을 그대로 명령어에 포함시킬 경우, 쉘 인젝션 공격에 취약해질 수 있습니다. 따라서 shell=True 매개변수를 사용하는 것은 가능한 한 피하고, 명령어와 인수를 리스트 형태로 분리하여 전달하는 것이 안전한 방법입니다. 이 모듈은 운영 체제의 프로세스 생성 메커니즘을 직접 활용하므로, 윈도우, 리눅스, macOS 등 다양한 플랫폼에서 일관된 방식으로 외부 프로그램과 상호작용할 수 있는 강력한 도구입니다.
