PEP 324 – subprocess - 새 프로세스 모듈
- Author:
- Peter Astrand <astrand at lysator.liu.se>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 19-Nov-2003
- Python-Version:
- 2.4
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 프로세스를 시작하고 통신하기 위한 새 모듈을 설명합니다.
동기
새 프로세스를 시작하는 것은 모든 프로그래밍 언어에서 일반적인 작업이며, Python과 같은 고급 언어에서는 특히 흔한 작업입니다. 다음과 같은 이유로 이 작업을 잘 지원해야 합니다.
- 프로세스를 시작하는 데 부적절한 함수를 사용하면 보안 위험이 발생할 수 있습니다. 셸을 통해 프로그램을 시작하고 인수에 셸 메타 문자가 포함되어 있으면 결과가 재앙적일 수 있습니다. [1]
- 이를 통해 Python은 지나치게 복잡한 셸 스크립트를 대체하는 더욱 나은 언어가 됩니다.
현재 Python에는 프로세스를 생성하기 위한 서로 다른 함수가 매우 많습니다. 이로 인해 개발자가 선택하기가 어렵습니다.
subprocess 모듈은 이전 함수에 비해 다음과 같은 개선 사항을 제공합니다.
- 하나의 “통합된” 모듈이 이전 함수의 모든 기능을 제공합니다.
- 프로세스 간 예외: 새 프로세스가 실행을 시작하기 전에 자식 프로세스에서 발생한 예외가 부모 프로세스에서 다시 발생합니다. 예를 들어
exec()실패를 쉽게 처리할 수 있다는 의미입니다. 예를 들어 popen2를 사용하면 실행이 실패했는지 감지할 수 없습니다. - fork와 exec 사이에서 사용자 지정 코드를 실행하기 위한 훅입니다. 예를 들어 uid를 변경하는 데 사용할 수 있습니다.
- /bin/sh를 암묵적으로 호출하지 않습니다. 따라서 위험한 셸 메타 문자를 이스케이프할 필요가 없습니다.
- 파일 디스크립터 리디렉션의 모든 조합을 사용할 수 있습니다. 예를 들어 “python-dialog” [2]은 프로세스를 생성하고 stderr를 리디렉션해야 하지만 stdout은 리디렉션하지 않아야 합니다. 임시 파일을 사용하지 않고는 현재 함수로 이를 수행할 수 없습니다.
- subprocess 모듈을 사용하면 새 프로그램을 실행하기 전에 열려 있는 모든 파일 디스크립터를 닫을지 제어할 수 있습니다.
- 여러 subprocess를 연결하는 기능(셸 “파이프”)을 지원합니다.
- 유니버설 줄바꿈을 지원합니다.
- 교착 상태 위험 없이 stdin 데이터를 보내고 stdout 및 stderr 데이터를 쉽게 읽을 수 있는
communicate()메서드를 제공합니다. 대부분의 사람은 자식 프로세스 통신과 관련된 흐름 제어 문제를 알고 있지만, 완전히 올바르고 교착 상태가 없는 select 루프를 작성할 인내심이나 기술을 모두 갖추고 있는 것은 아닙니다. 이로 인해 많은 Python 애플리케이션에 경쟁 조건이 포함됩니다. 표준 라이브러리의communicate()메서드가 이 문제를 해결합니다.
근거
다음 항목은 설계를 요약합니다.
- subprocess는 검증된 popen2를 기반으로 했습니다.
- popen2의 팩토리 함수는 삭제되었습니다. 클래스 생성자도 똑같이 사용하기 쉽다고 생각하기 때문입니다.
- popen2에는 서로 다른 리디렉션 조합을 위한 여러 팩토리 함수와 클래스가 있습니다. 그러나 subprocess에는 단 하나의 클래스만 있습니다. subprocess 모듈은 12가지 서로 다른 리디렉션 조합을 지원하므로, 각각에 클래스나 함수를 제공하는 것은 번거롭고 그다지 직관적이지 않습니다. popen2를 사용하더라도 이는 가독성 문제입니다. 예를 들어 많은 사람은 문서를 사용하지 않고는 popen2.popen2와 popen2.popen4의 차이를 구분하지 못합니다.
- 작은 유틸리티 함수 하나가 제공됩니다:
subprocess.call(). 이는 사용하기 매우 쉬우면서도os.system()을 개선한 기능을 제공하는 것을 목표로 합니다:- 제한 사항이 있는 표준 C 함수 system()을 사용하지 않습니다.
- 셸을 암묵적으로 호출하지 않습니다.
- 인용할 필요가 없으며, 인자 목록을 사용합니다.
- 반환값을 더 쉽게 처리할 수 있습니다.
call()유틸리티 함수는Popen클래스 생성자와 마찬가지로 ‘args’ 인자를 받습니다. 명령이 완료될 때까지 기다린 다음returncode특성을 반환합니다. 구현은 매우 간단합니다.:def call(*args, **kwargs): return Popen(*args, **kwargs).wait()
call()함수의 동기는 간단합니다. 프로세스를 시작하고 종료될 때까지 기다리는 일은 흔한 작업입니다.Popen은 다양한 옵션을 지원하지만, 많은 사용자의 요구 사항은 간단합니다. 많은 사람이 오늘날os.system()을 사용하는데, 주된 이유는 간단한 인터페이스를 제공하기 때문입니다. 다음 예를 고려하십시오.:os.system("stty sane -F " + device)
subprocess.call()을 사용하면 다음과 같이 작성할 수 있습니다.:subprocess.call(["stty", "sane", "-F", device])
또는 셸을 통해 실행하는 경우에는 다음과 같습니다.:
subprocess.call("stty sane -F " + device, shell=True)
- “preexec” 기능을 사용하면 fork와 exec 사이에서 임의의 코드를 실행할 수 있습니다. 환경과 현재 디렉터리를 설정하기 위한 특별한 인자는 있지만, 예를 들어 uid를 설정하기 위한 인자는 없는 이유가 무엇인지 질문할 수 있습니다. 답은 다음과 같습니다:
- 환경과 작업 디렉터리를 변경하는 일은 상당히 일반적인 것으로 간주됩니다.
spawn()과 같은 이전 함수는 “env” 인자를 지원합니다.- env와 cwd는 상당히 크로스 플랫폼적인 것으로 간주됩니다. Windows에서도 의미가 통하기 때문입니다.
- POSIX 플랫폼에서는 확장 모듈이 필요하지 않습니다. 이 모듈은
os.fork(),os.execvp()등을 사용합니다. - Windows 플랫폼에서는 Mark Hammond의 Windows 확장 [5], 또는 _subprocess라는 작은 확장 모듈이 필요합니다.
사양
이 모듈은 Popen이라는 하나의 클래스를 정의합니다.:
class Popen(args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False,
cwd=None, env=None, universal_newlines=False,
startupinfo=None, creationflags=0):
인자는 다음과 같습니다:
args는 문자열 또는 프로그램 인자의 시퀀스여야 합니다. 실행할 프로그램은 일반적으로 args 시퀀스 또는 문자열의 첫 번째 항목이지만, executable 인자를 사용하여 명시적으로 설정할 수도 있습니다.UNIX에서
shell=False(기본값)인 경우:Popen클래스는os.execvp()를 사용하여 자식 프로그램을 실행합니다.args는 일반적으로 시퀀스여야 합니다. 문자열은 해당 문자열만을 유일한 항목(실행할 프로그램)으로 포함하는 시퀀스로 취급됩니다.UNIX에서
shell=True인 경우:args가 문자열이면 셸을 통해 실행할 명령 문자열을 지정합니다.args가 시퀀스이면 첫 번째 항목이 명령 문자열을 지정하고, 추가 항목은 추가 셸 인자로 취급됩니다.Windows에서
Popen클래스는 문자열을 대상으로 동작하는CreateProcess()를 사용하여 자식 프로그램을 실행합니다.args가 시퀀스이면list2cmdline메서드를 사용하여 문자열로 변환됩니다. 모든 MS Windows 애플리케이션이 명령줄을 동일한 방식으로 해석하는 것은 아니라는 점에 유의하십시오.list2cmdline은 MS C 런타임과 동일한 규칙을 사용하는 애플리케이션을 위해 설계되었습니다.bufsize가 지정된 경우 내장open()함수의 해당 인자와 같은 의미를 가집니다. 0은 버퍼링하지 않음을, 1은 줄 단위 버퍼링을, 그 밖의 양의 값은 해당 크기(대략)의 버퍼를 사용함을 의미합니다. 음수bufsize는 시스템 기본값을 사용함을 의미하며, 일반적으로 완전한 버퍼링을 의미합니다.bufsize의 기본값은 0(버퍼링하지 않음)입니다.stdin,stdout및stderr는 실행된 프로그램의 표준 입력, 표준 출력 및 표준 오류 파일 핸들을 각각 지정합니다. 유효한 값은PIPE, 기존 파일 디스크립터(양의 정수), 기존 파일 객체 및None입니다.PIPE는 자식 프로세스에 연결할 새 파이프를 생성해야 함을 나타냅니다.None인 경우 리디렉션이 발생하지 않으며, 자식 프로세스의 파일 핸들은 부모 프로세스에서 상속됩니다. 또한stderr에는 STDOUT을 지정할 수 있으며, 이는 애플리케이션의 stderr 데이터를 stdout에 사용되는 것과 동일한 파일 핸들로 캡처함을 나타냅니다.preexec_fn이 호출 가능 객체로 설정되면 자식 프로세스가 실행되기 직전에 해당 객체가 자식 프로세스에서 호출됩니다.close_fds가 참이면 0, 1 및 2를 제외한 모든 파일 디스크립터가 자식 프로세스가 실행되기 전에 닫힙니다.shell이 참이면 지정된 명령이 셸을 통해 실행됩니다.cwd가None이 아니면 자식 프로세스가 실행되기 전에 현재 디렉터리가 cwd로 변경됩니다.env가None이 아니면 새 프로세스의 환경 변수를 정의합니다.universal_newlines가 참이면 stdout 및 stderr 파일 객체가 텍스트 파일로 열리지만, 줄은\n(Unix 줄 끝 규칙),\r(Macintosh 규칙) 또는\r\n(Windows 규칙) 중 어느 것으로든 끝날 수 있습니다. 이러한 모든 외부 표현은 Python 프로그램에서\n으로 인식됩니다. 참고: 이 기능은 Python이 범용 줄 바꿈 지원(기본값)을 사용하여 빌드된 경우에만 사용할 수 있습니다. 또한communicate()메서드는 stdout, stdin 및 stderr 파일 객체의 newlines 속성을 업데이트하지 않습니다.startupinfo및creationflags가 지정된 경우 기본CreateProcess()함수에 전달됩니다. 이러한 인자는 주 창의 모양이나 새 프로세스의 우선순위와 같은 항목을 지정할 수 있습니다. (Windows 전용)
이 모듈은 다음 두 가지 단축 함수도 정의합니다.
call(*args, **kwargs):- 인자와 함께 명령을 실행합니다. 명령이 완료될 때까지 기다린 다음
returncode속성을 반환합니다.인자는 Popen 생성자의 인자와 동일합니다. 예제:
retcode = call(["ls", "-l"])
예외
새 프로그램이 실행을 시작하기 전에 자식 프로세스에서 발생한 예외는 부모 프로세스에서 다시 발생합니다. 또한 예외 객체에는 ‘child_traceback’이라는 추가 속성이 하나 있으며, 이는 자식 프로세스의 관점에서 본 트레이스백 정보를 포함하는 문자열입니다.
가장 흔히 발생하는 예외는 OSError입니다. 예를 들어 존재하지 않는 파일을 실행하려고 할 때 발생합니다. 애플리케이션은 OSErrors에 대비해야 합니다.
Popen이 잘못된 인자로 호출되면 ValueError가 발생합니다.
보안
다른 일부 popen 함수와 달리 이 구현은 /bin/sh를 암묵적으로 호출하지 않습니다. 즉, 셸 메타 문자를 비롯한 모든 문자를 자식 프로세스에 안전하게 전달할 수 있습니다.
Popen 객체
Popen 클래스의 인스턴스에는 다음 메서드가 있습니다.
poll()- 자식 프로세스가 종료되었는지 확인합니다.
returncode속성을 반환합니다. wait()- 자식 프로세스가 종료될 때까지 기다립니다.
returncode속성을 반환합니다. communicate(input=None)- 프로세스와 상호 작용합니다. stdin으로 데이터를 보냅니다. 파일 끝에 도달할 때까지 stdout 및 stderr에서 데이터를 읽습니다. 프로세스가 종료될 때까지 기다립니다. 선택적 stdin 인자는 자식 프로세스에 보낼 문자열이어야 하며, 자식 프로세스에 보낼 데이터가 없으면
None이어야 합니다.communicate()는(stdout, stderr)튜플을 반환합니다.참고: 읽은 데이터는 메모리에 버퍼링되므로 데이터 크기가 크거나 제한되지 않은 경우 이 메서드를 사용하지 마십시오.
다음 속성도 사용할 수 있습니다.
stdinstdin인자가PIPE이면 이 속성은 자식 프로세스에 입력을 제공하는 파일 객체입니다. 그렇지 않으면None입니다.stdoutstdout인자가PIPE이면, 이 속성은 자식 프로세스의 출력을 제공하는 파일 객체입니다. 그렇지 않으면None입니다.stderrstderr인자가PIPE이면, 이 속성은 자식 프로세스의 오류 출력을 제공하는 파일 객체입니다. 그렇지 않으면None입니다.pid- 자식 프로세스의 프로세스 ID입니다.
returncode- 자식 프로세스의 반환 코드입니다.
None값은 프로세스가 아직 종료되지 않았음을 나타냅니다. 음수 값 -N은 자식 프로세스가 신호 N에 의해 종료되었음을 나타냅니다(UNIX 전용).
subprocess 모듈로 이전 함수 대체하기
이 절에서 “a ==> b”는 b를 a의 대체물로 사용할 수 있다는 의미입니다.
참고: 이 절의 모든 함수는 실행할 프로그램을 찾을 수 없으면 (다소) 조용히 실패하지만, 이 모듈은 OSError 예외를 발생시킵니다.
다음 예제에서는 subprocess 모듈을 from subprocess import *로 가져왔다고 가정합니다.
/bin/sh 셸 백쿼트 대체하기
output=`mycmd myarg`
==>
output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]
셸 파이프라인 대체하기
output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]
os.system() 대체하기
sts = os.system("mycmd" + " myarg")
==>
p = Popen("mycmd" + " myarg", shell=True)
sts = os.waitpid(p.pid, 0)
참고:
- 일반적으로 셸을 통해 프로그램을 호출할 필요는 없습니다.
- 종료 상태보다 returncode 속성을 확인하는 편이 더 쉽습니다.
보다 실제적인 예제는 다음과 같습니다.:
try:
retcode = call("mycmd" + " myarg", shell=True)
if retcode < 0:
print >>sys.stderr, "Child was terminated by signal", -retcode
else:
print >>sys.stderr, "Child returned", retcode
except OSError, e:
print >>sys.stderr, "Execution failed:", e
os.spawn* 대체하기
P_NOWAIT 예제:
pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
==>
pid = Popen(["/bin/mycmd", "myarg"]).pid
P_WAIT 예제:
retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
==>
retcode = call(["/bin/mycmd", "myarg"])
벡터 예제:
os.spawnvp(os.P_NOWAIT, path, args)
==>
Popen([path] + args[1:])
환경 예제:
os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env)
==>
Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})
os.popen* 대체하기
pipe = os.popen(cmd, mode='r', bufsize)
==>
pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
pipe = os.popen(cmd, mode='w', bufsize)
==>
pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
==>
p = Popen(cmd, shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdin, child_stdout) = (p.stdin, p.stdout)
(child_stdin,
child_stdout,
child_stderr) = os.popen3(cmd, mode, bufsize)
==>
p = Popen(cmd, shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
(child_stdin,
child_stdout,
child_stderr) = (p.stdin, p.stdout, p.stderr)
(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
==>
p = Popen(cmd, shell=True, bufsize=bufsize,
stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
popen2.* 대체하기
참고: popen2 함수의 cmd 인자가 문자열이면 명령은 /bin/sh을 통해 실행됩니다. 리스트이면 명령은 직접 실행됩니다.
(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
==>
p = Popen(["somestring"], shell=True, bufsize=bufsize
stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdout, child_stdin) = (p.stdout, p.stdin)
(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
==>
p = Popen(["mycmd", "myarg"], bufsize=bufsize,
stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdout, child_stdin) = (p.stdout, p.stdin)
popen2.Popen3과 popen3.Popen4는 다음을 제외하면 기본적으로 subprocess.Popen처럼 작동합니다:
- 실행이 실패하면
subprocess.Popen은 예외를 발생시킵니다. capturestderr인자는 stderr 인자로 대체됩니다.stdin=PIPE와stdout=PIPE를 지정해야 합니다.popen2는 기본적으로 모든 파일 디스크립터를 닫지만,subprocess.Popen에서는close_fds=True를 지정해야 합니다.
미해결 과제
일부 기능이 요청되었지만 아직 구현되지 않았습니다. 여기에는 다음이 포함됩니다:
- 서브프로세스 무리 전체를 관리하기 위한 지원
- “데몬” 프로세스를 관리하기 위한 지원
- 서브프로세스를 종료하기 위한 내장 메서드
이것들은 유용한 기능이지만, 나중에 문제없이 추가될 수 있을 것으로 예상됩니다.
- pty 지원을 포함한 expect와 유사한 기능.
pty 지원은 플랫폼 의존도가 매우 높으며, 이는 문제입니다. 또한, 이러한 종류의 기능을 제공하는 다른 모듈들이 이미 존재합니다 [6].
하위 호환성
이것은 새로운 모듈이므로, 주요한 하위 호환성 문제는 예상되지 않습니다. 모듈 이름 “subprocess”는 같은 이름을 가진 다른 이전 모듈들 [3]과 충돌할 수 있지만, “subprocess”라는 이름이 지금까지 제안된 것 중 가장 나은 이름으로 보입니다. 이 모듈의 첫 번째 이름은 “popen5”였지만, 이 이름은 너무 직관적이지 않다고 여겨졌습니다. 한동안 이 모듈은 “process”라고 불렸지만, 이 이름은 이미 Trent Mick의 모듈 [4]에서 사용하고 있습니다.
이 새 모듈이 대체하고자 하는 함수와 모듈들(os.system, os.spawn*, os.popen*, popen2.*, commands.*)은 하위 호환성을 유지하기 위해 앞으로도 오랫동안 향후 파이썬 버전에서 계속 사용할 수 있을 것으로 예상됩니다.
참조 구현
참조 구현은 http://www.lysator.liu.se/~astrand/popen5/ 에서 구할 수 있습니다.
참고 자료
Copyright
This document has been placed in the public domain.