Subprocess 모듈 사용법 – 파이썬에서 서브 프로세스를 생성하기

Subprocess이란?

subprocess 모듈은 파이썬 프로그램 내에서 새로운 프로세스를 스폰하고 여기에 입출력 파이프를 연결하며 리턴코드를 획득할 수 있도록 하는 모듈로, 다른 언어로 만들어진 프로그램을 통합, 제어할 수 있게 만드는 모듈이다. 이 모듈은 기존에 오랜된 몇몇 모듈과 함수(os.system, os.spawn*)들을 대체하기 위해 만들어졌다. (혹은 os.popen 같은 함수도…)

사용방법

새로운 서브 프로세스를 만들기 위해 권장되는 방법은 다음의 편의 함수들을 사용하는 것이다. 보다 세부적인 제어를 위해서는 Popen 인터페이스를 직접 사용한다.

subprocess.call()

인자값들을 사용하여 바로 명령을 실행한다. 서브프로세스가 동작을 시작하면 이를 기다렸다가 리턴코드를 반환한다.

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

args는 명령행에 입력될 인자들을 공백으로 자른 리스트이다. 이 call 함수는 Popen의 컨스트럭터와 비슷한 시그니쳐를 가지고 있다. 또한 비슷하게 타임아웃이 주어진경우, 지정한 시간만큼 기다렸다가 그보다 시간이 오래 걸리는 경우 TimeoutExpired 예외를 일으킨다.

>>> subprocess.call(["ls", "-l"])
0
>>> subprocess.all("exit 1", shell=True)
1

이 함수 내에서는 stdout=PIPEstderr=PIPE를 사용해서는 안된다. 만약 서브프로세스가 OS의 파이프 버퍼를 가득채울만큼 많은 출력을 만들어내는 경우, 이 함수는 중간에 파이프를 읽어들이지 않기 때문에 멈추게 된다.

subprocess.check_call()

.call()과 비슷하게 동작하나, 리턴코드가 0이 아닌 경우 CalledProcessError 예외를 일으킨다. 이 예외 객체는 리턴코드값을 returncode 속성으로 가지고 있다.

subprocess.check_output()

서브프로세스를 실행하고 그 출력 문자열을 리턴한다.

subprocess.check_output(args, *, input=None, output=None, stderr=None, shell=False, universal_newlines=False, timeout=None)

리턴코드가 0이 아닌 경우 CalledProcessError예외를 일으킨다. 이 예외에는 returncode로 리턴코드를 얻을 수 있고 output 속성으로 출력 문자열을 얻을 수 있다.

input 파라미터는 Popen.communicate() 로 전달되어 서브프로세스의 표준입력이 된다. 전달되는 값은 바이트 시퀀스여야 하며, universal_newline=True인 경우 문자열일 수 있다.

>>> subprocess.check_output(["echo", "Hello World!"])
b'Hello World!n'

>>> subprocess.check_output(["echo", "Hello World!"], universal_newlines=True)
'Hello World!n'

>>> subprocess.check_output(["sed", "-e", "s/foo/bar"],
...                input=b"when in the course of fooman eventsn")
b'when in the course of barman eventsn'

>>> subprocess.check_output("exit 1", shell=True)
Traceback ...
...
subprocess.CalledProcessError: command 'exit 1' returned non-zero exit status 1

기본적으로 리턴되는 출력 데이터는 인코딩된 바이트 스트림이다. 인코딩은 서브프로세스 프로그램에 의존하며, 따라서 이를 텍스트로 변환하기 위해서는 디코딩이 필요하다. 이 동작은 universal_newline=True이면 오버라이드 된다.

에러 출력을 잡기 위해서는 stderr=subprocess.PIPE라는 인자를 정의한다. 서브프로세스 상에서 에러메시지로 출력된 내용을 받아오게 된다.

>>> subprocess.check_output(
...    "ls non_existent_file; exit 0;",
...    stderr=subprocess.STDOUT,
...    shell=True)
'ls: non_existent_file: No such file or directoryn'

그외 상수들

  • subprocess.DEVNULL
  • subprocess.PIPE
  • subprocess.STDOUT

예외들

  • subprocess.SubprocessError
  • subprocess.TimeoutExpired
  • subprocess.CalledProcessError

공통 인자들

이 모듈에서 제공하는 많은 함수들은 대게 Popen 컨스트럭터와 비슷한, 그리고 많은 인자들을 가진다. 대부분의 경우에 이들은 안전한 디폴트값을 갖지만, 경우에 따라서는 변경될 필요가 있다. 그 중 많이 쓰이는 몇가지를 살펴보면

  • args: 프로그램 실행에 필요한 인자들이다. 통상 프로그램 명 외에 인자를 함께 전달하는 경우라면 이들은 공백으로 구분된 문자열의 리스트로 전해진다. 단, shell=True인 경우엔 그냥 단일 문자열로 보내지거나 혹은 프로그램 실행 외에 다른 인자를 주지 않는다면 단일 문자열로 전달해도 문제 없다.
  • stdin, stdout, stderr: 프로그램의 표준입출력이 될 파일 핸들을 지정한다. 가능한 값으로는 PIPE, DEVNULL, 파일핸들디스크립터, 파이썬 파일객체나 None이 될 수 있다. 이 값이 PIPE가 되면 새로운 파이프가 만들어진다는 것을 의미한다. None인 경우에는 부모 프로세스의 표준 입출력을 그대로 상속받는다.
  • universal_newlines: 이 값이 True이면 표준입출력으로 바이트스트림이 아닌 문자열이 오가게 되며, 개행문자 변환이 자동으로 이루어진다.
  • shell: 이 값은 명령이 쉘을 통해서 이루어지도록 한다. 이를 이용하면 쉘 파이프나, 파일명 와일드카드, 환경변수 확장, ~디렉토리 확장 등 쉘의 유용한 기능들을 더불어 쓸 수 있게 된다.

Popen 클래스

프로세스 생성과 관리 모듈은 내부적으로 Popen 클래스를 통해서 관리한다. 실질적으로 call-*류의 함수들은 이 클래스를 기반으로 하고 있다. Popen은 많은 유연성을 제공하기 때문에 기본적으로 제공되는 편의 함수들만으로 다룰 수 없는 케이스를 커버할 때, 세밀한 옵션들에 대해서 직접 제어하고자 할 때 사용할 수 있다.

class subprocess.Popen

Popen 클래스는 인스턴스 초기화시에 여러가지 옵션들을 인자로 받을 수 있다.  물론 여기서는 이걸 다 설명할 수 없다. 전체 내용은 파이썬 공식 문서를 참고하도록 하자.

class subprocess.Popen(args, bufsize=-1, excutable=None, 
                       stdin=None, stdout=None, stderr=None, 
                       preexec_fn=None, close_fds=None, shell=False, 
                       cwd=None, env=None, universal_newlines=False, 
                       startupinfo=None, creationflags=0, restoreflags=0, 
                       restore_signals=True, start_new_session=False, pass_fds=()
                      )

args는 프로그램 인자의 리스트이거나, 단일 문자열이다(만약 shell값이 True라면 단일 문자열이어도 된다.)

서브 프로세스의 입출력

stdin, stdout, stderr은 서브 프로세스의 표준 입출력 핸들러를 가리킨다. 이에 유효한 값은 PIPEDEVNULL 혹은 이미 존재하는 다른 파일 핸들러 및 파일 객체일 수 있다. PIPE를 넘기는 경우, 자식 프로세스에게 새로운 파이프가 생성되어 연결된다. 디폴트값은 None이며 이 경우 리다이렉션이 일어나지 않고, 부모 프로세스(파이썬 프로그램 자신)의 입출력을 상속받는다. 참고로 stderr에는 STDOUT을 줄 수 있는데, 이렇게 하여 에러 문구를 표준 출력으로 받을 수 있다.

문자열 출력에 대한 변환

universal_newlines는 라인엔딩 변환을 하느냐 하지 않느냐의 차이를 보이는데, 이 값이 True이면 문자열을 주고 받게 되므로, 파일 객체를 파이프연결한 경우 이 값은 False가 되어야 한다. 개행 문자는 플랫폼 마다 다른데, 이 값이 True로 넘어가는 경우, os.linesep을 사용하여 개행 문자를 변환하게 된다.

shell 파라미터 값이 True인 경우, 주어진 명령을 쉘을 통해서 처리한다. 이는 쉘 스크립트 대신 파이썬 스크립트를 통해서 배치 명령의 흐름을 제어하고자 할 때 유용하다. 이 기능을 사용하면 파일 이름 와일드카드나, 환경변수 확장 등의 쉘 기능을 이용할 수 있다. 단, 파이썬 역시 왠만한 쉘 관련 기능들을 제공한다. glob, fnmatch, os.walk(), os.path.expandvars(), os.path.expanduser(), shutil 등의 모듈, 함수를 참고할 것.

컨텍스트 매니저

 

Popen 객체는 컨텍스트 매니저 프로토콜을 구현하고 있어서 with 구문에 사용될 수 있다. 빠져나오는 경우 표준 파일 디스크립터는 닫히게 되고, 프로세스를 기다리게 된다.

with Popen(["ifconfig"], stdout=PIPE) as proc:
    log.write(proc.stdout.read())

Popen 객체의 메소드들

다음 메소드들은 Popen 생성자를 이용해서 만든 객체가 제공하는 메소드들이다.

  • .poll(): 자식 프로세스가 종료되었는지를 확인한다.
  • .wait(timeout=None): 자식 프로세스가 종료되길 기다린다. 자식 프로세스의 리턴 코드를 돌려준다.
  • .communicate(input=None, timeout=None): 자식 프로세스의 표준 입력으로 데이터를 보낸다음, 표준 출력의 EOF르 만날 때까지 이를 읽어온다. (프로세스가 끝날 때까지 기다린다.) 이 함수는 (stdout_data, stderr_data)의 튜플을 리턴한다. 이 함수를 사용하려면 stdin=PIPE 옵션으로 자식 프로세스를 시작해야 한다.
  • Popen.send_signal(signal): 자식 프로세스에 시그널을 보낸다.
  • Popen.terminate(): 자식 프로세스에 종료 시그널을 보낸다.
  • Popen.kill(): 자식 프로세스를 강제로 죽인다.
  • Popen.args: 주어진 args 를 리턴한다.
  • Popen.stdin: 만약 PIPE라면 이 객체는 읽고 쓸 수 있는 스트림 객체이다.
  • Popen.stdout: PIPE라면 읽을 수 있는 객체이다.
  • Popen.stderr: PIPE라면 읽을 수 있는 객체이다.
  • Popen.pid: 자식 프로세스의 pid값
  • Popen.returncode: 자식 프로세스가 종료되었을 때 리턴코드 값을 가진다.