Subprocess 모듈 이란?
subprocess
모듈은 파이썬 프로그램 내에서 새로운 프로세스를 스폰하고 여기에 입출력 파이프를 연결하며 리턴코드를 획득할 수 있도록 하는 모듈로, 다른 언어로 만들어진 프로그램을 통합, 제어할 수 있게 만드는 모듈이다. 이 모듈은 기존에 오랜된 몇몇 모듈과 함수(os.system
, os.spawn*
)들을 대체하기 위해 만들어졌다. (혹은 os.popen
같은 함수도…)
사용방법
새로운 서브 프로세스를 만들기 위해 권장되는 방법은 다음의 편의 함수들을 사용하는 것이다. 보다 세부적인 제어를 위해서는 Popen
인터페이스를 직접 사용한다.
subprocess.call 함수 – 가장 간단한 서브 프로세스 실행
가장 기본적인 서브프로세스 호출명령이다. 인자값들을 사용하여 바로 명령을 실행한다. 이 함수는 블럭킹함수로 서브프로세스가 동작을 시작하면 이를 기다렸다가 서브 프로세스가 종료되면 해당 프로세스의 실행 리턴코드를 반환한다. 보통의 쉘에서 돌아가는 프로그램들은 정상 종료시 0을 리턴한다.
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
이 call
함수는 Popen
의 컨스트럭터와 비슷한 시그니쳐를 가지고 있다.(사실 subprocess 모듈의 거의 모든 함수들이 Popen의 인자중에서 일부를 갖는 형태로 디자인되어 있다.) 몇 가지 인자를 살펴보면 다음과 같다.
args
는 명령행에 문자열 혹은 입력될 인자들을 공백으로 자른 리스트이다.stdin
,stdout
,stderr
은 각각 표준입력, 표준출력, 표준에러의 표준입출력 리다이렉션에 대응한다. 파일디스크립터를 사용해서 파일의 내용을 표준입출력으로 대체할 수 있다. 보통은 생략되면 현재의 표준입출력이 그대로 적용된다.shell=True
가 되면 서브 프로세스로 주어진 명령을 바로 실행하는 것이 아니라, 별도의 서브 쉘을 실행하고 해당 쉘 위에서 명령을 실행하도록 한다. 이 값이 True가 되는 경우에는 args가 리스트 보다는 하나의 문자열인 쪽이 좋다고 한다. 참고로 쉘을 사용하면 쉘이 제공하는 파이프, 리다이렉션과 쉘의 문자열 확장 (예를 들어 FILES* 라고 쓰면 FILES1 FILES2 FILES3 … 이렇게 만들어지는 것)을 함께 사용할 수 있게 된다.- 기본적으로 이 함수는 서브프로세스가 종료될 때까지 기다리는데 timeout 값을 설정하면 주어진 시간(초단위)만큼 대기한 후, 대기 시간을 초과하면
TimeoutExpired
예외를 일으키게 된다.
>>> subprocess.call(["ls", "-l"])
...파일 목록이 출력되고...
0 ## 성공했다면 0이 리턴값으로 나오게 될 것이다
subprocess.check_call – 결과값을 강제로 체크하기
call() 함수는 서브 프로세스를 통해서 주어진 명령을 실행하고 리턴한다. 이 과정에서 리턴 코드를 받아오긴 하지만, 해당 명령이 처리에 실패하고 비정상 종료를 하는 경우에도 이후의 코드가 무사히 실행이 될 것이다. (물론 부지러한 사람이라면 이 리턴코드가 0인지 비교해볼 것이다.)
check_call()은 서브 프로세스에 의한 처리가 성공하는 것이 보장되어야 하는 경우에 쓰인다. 즉 서브 프로세스의 명령이 비정상 종료하였다면 그 리턴코드가 0이 아닐 것이다. 이 때 다른 리턴 코드 값을 내놓는게 아니라 그 즉시 CalledProcessError
예외를 일으킨다. (이 예외 객체내에 returncode라는 속성이 있고, 따라서 예외처리 구문에서 리턴 코드를 확인할 수 있다.)
subprocess.check_output – 출력되는 문자열이 필요할 때
서브프로세스를 실행하고 그 출력 문자열을 리턴한다. 따라서 실행되어 출력된 결과를 문자열로 받고 싶을 때 사용한다. 스폰된 프로세스의 리턴코드가 0이 아닌 경우 CalledProcessError
예외를 일으킨다. (그래서 이름에 check가 포함된다.) 이 버전은 Popen 객체를 사용함에 있어서 표준입출력을 문자열 데이터로 사용하는 버전에 가깝다. 즉 프로세스가 실행되고 종료되면 표준출력으로 넘어온 데이터를 읽어서 그 값을 리턴해주는 함수라 보면 되겠다. 프로세스스가 사용하는 입력의 경우에도 input=
인자를 통해서 미리 지정해 줄 수 있다.
universal_newlines=
파라미터는 사실 그 이름과 의미가 약간 모호한데, 서브 프로세스와의 파이프 통신을 문자열을 주고받는 것으로 처리한다. (기본적으로 False 값일 때에는 파이프를 통해서 바이트스트림이 오간다.) 다음은 사용예이다.
>>> subprocess.check_output(["echo", "Hello World!"])
b'Hello World!\n'
### universal_newline을 쓰면 바이트가 아닌 문자열로 받게된다.
>>> subprocess.check_output(["echo", "Hello World!"], universal_newlines=True)
'Hello World!\n'
## input을 쓰면 표준입력을 통해서 입력될 내용을 인자로 넘겨줄 수 있다.
>>> 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'
## check_...가 들어있기 때문에 정상종료하지 않으면 예외처리된다.
>>> subprocess.check_output("exit 1", shell=True) # 리턴코드가 1이다.
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'
Popen 클래스
프로세스 생성과 관리 모듈은 내부적으로 Popen
클래스를 통해서 관리한다. 실질적으로 call-*
류의 함수들은 이 클래스를 기반으로 하고 있다. Popen
은 많은 유연성을 제공하기 때문에 기본적으로 제공되는 편의 함수들만으로 다룰 수 없는 케이스를 커버할 때, 세밀한 옵션들에 대해서 직접 제어하고자 할 때 사용할 수 있다.
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
은 서브 프로세스의 표준 입출력 핸들러를 가리킨다. 이에 유효한 값은 PIPE
나 DEVNULL
혹은 이미 존재하는 다른 파일 핸들러 및 파일 객체일 수 있다. 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
: 자식 프로세스가 종료되었을 때 리턴코드 값을 가진다.