subprocess 모듈에서 특히 Popen class 를 주로 다룬다.
https://docs.python.org/ko/3.9/library/subprocess.html#subprocess.Popen
proc=subprocess.Popen(CMD,shell=True,executable="/bin/bash")
예제1.
pstree -ps $$
명령 수행subprocess.Popen("pstree -ps $$",shell=True,executable="bin/bash")
으로 pstree실행stdout=subprocess.PIPE
: stdout 활용sys.stdout
으로 설정했는데, 그럼 바로 내 쉘에 보이지만 이를 문자열로 잡아서 활용할 수는 없다.stdout = subprocess.PIPE
text=True
(stdout을 str로 처리, 이 옵션이 False
시 'b'(바이너리)로 처리됨)유효한 값 | Description |
---|---|
PIPE | 자식에 대한 새 파이프를 만들어낸다. ex) stdout=subprocess.PIPE |
DEVNULL | os.devnull이 사용될 것임 말그대로 stdout 등이 필요없을 때 혹은 출력하고 싶지 않을 때 사용 ./command >> /dev/null 과 동일 |
bufsize
bufsize
: 버퍼링 할 사이즈stdin/stdout/stderr
파이프 파일 객체를 만들 때 open()
함수에 해당 인자로 제공된다.bufsize=0
: 버퍼링 되지 않음bufsize=1
: 줄 버퍼링 의미(universal_newlines=True
인 경우, 텍스트 모드에서만 사용할 수 있다.)bufsize=다른 양수 값
: 대략 그 크기의 버퍼를bufsize=음수
: 시스템 기본 값 io.DEFAULT_BUFFER_SIZE
가 사용됨 의미executable
executable
: 실행할 대체 프로그램 지정shell=False
일 때, executable은 args에 의해 지정된 프로그램을 대체shell=True
이면, POSIX에서 executable 인자는 기본 /bin/sh
의 대체 셸 지정stdin
,stdout
,stderr
의 파일 객체는 지정된 encoding
과 errors
또는 io.TextIOWrapper 기본 값을 사용하여 텍스트 모드로 열린다.universal_newlines
인자는 text
와 동등하고 이전 버전과의 호환성을 위해 제공된다.python 3.6.x
import subprocess
subprocess.call('/bin/bash -c "$GREPDB"', shell=True, env={'GREPDB': 'echo 123'})
1. suprocess.Popen Class의 instance create
proc=subprocess.Popen(args...)
proc.poll()
Popen.poll()
# Create Popen instance
proc=subprocess.Popen(...)
var=proc.poll()
print(var)
return value | description |
---|---|
None | instance 실행 중 |
return code | instance가 종료되었고 리턴코드를 가져옴 |
Popen.wait()
# Create Popen instance
proc=subprocess.Popen(...)
try:
ret=proc.wait(timeout=None) # Wait...
except subprocess.TimeoutExpired:
Exception occured!
else:
print(ret)
input args | description |
---|---|
timeout=None | 이 함수가 기다릴 time 지정 |
return value | description |
---|---|
return code | timeout내 종료된 Popen instance가 return 하는 값 |
except | description |
---|---|
TimeoutExpired | stdout=PIPE 나 stderr=PIPE 를 사용하고 자식 프로세스가 파이프에 너무 많은 출력을 보내 OS 파이프 버퍼가 더 많은 데이터를 받아들일 때까지 기다리느라 블록하면 교착 상태에 빠진다. 파이프를 사용할 때 이를 피하려면 Popen.communicate() 를 사용 해라. |
Popen.communicate()
# Create Popen instance
proc=subprocess.Popen(...)
try:
ret, err = proc.commiunicate(input=None, timeout=10.0f)
except subprocess.TimeoutExpired:
Exception occured!
else:
...
stdin=PIPE
를 사용해 Popen 객체를 만들어야함)input args | description |
---|---|
input=None | 자식 프로세스로 전송될 데이터, 자식으로 데이터를 보내지 않으면 None 설정스트림이 텍스트 모드로 열렸으면, input 은 문자열이어야 함스트림이 텍스트 모드로 열리지 않았으면, input 은 바이트열 |
timeout=None | 이 함수가 기다릴 time 지정 |
return value | description |
---|---|
(stdout_data, stderr_data) | 스트림이 텍스트 모드로 열렸으면 데이터는 문자열 스트림이 비 텍스트 모드로 열렸으면 데이터는 바이트열 결과 튜플에서 None 이외의 값을 얻으려면 Popen 객체 생성 시,stdout=PIPE, stderr=PIPE 로 생성해야함 |
exception | description |
---|---|
TimeoutExpired | timeout내에 Popen instance가 종료되지 않았을 때 발생하는 익셉션stdout=PIPE 나 stderr=PIPE 를 사용하고 자식 프로세스가 파이프에 너무 많은 출력을 보내 OS 파이프 버퍼가 더 많은 데이터를 받아들일 때까지 기다리느라 블록하면 교착 상태에 빠진다. 파이프를 사용할 때 이를 피하려면 Popen.communicate() 를 사용 해라. 이 예외를 잡고 통신을 다시 시도해도 출력이 손실되지 않는다. 시간제한이 만료되면 자식 프로세스를 죽이지 않는다. 올바르게 하려면 프로세스를 죽이고 통신을 완료해야한다. |
cmd = "ps -al"
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
cmd_output, smd_stderr = p.communicate()
cmd_output = cmd_output.decode("utf-8")
cmd_stderr = cmd_stderr.decode("utf-8")
if p.returncode or cmd_stderr:
# Error Catch
print(cmd_output)
print(std_err)
Popen.send_signal(signal)
Popen.terminate()
TerminatedProcess()
를 보낸다.Popen.kill()
kill()
은 terminate()
의 별칭Popen.args
Popen.stdin
stdin=PIPE
면 this attribute는 open()
이 반환한 쓰기 가능한 스트림 객체encoding
, errors
인자가 지정되었거나 universal_newlines
인자가 True
이면, 스트림은 텍스트 스트림이고, 그렇지 않으면 바이트 스트림이다.stdin!=PIPE
이면, this attribute는 None
Popen.stdout
stdout=PIPE
이면, this attribute는 open()
이 반환한 읽기 가능한 스트림 객체encoding
, errors
인자가 지정되었거나 universal_newlines
인자가 True
이면, 스트림은 텍스트 스트림이고, 그렇지 않으면 바이트 스트림이다.stdout!=PIPE
면, this attribute는 None
Popen.stderr
stderr=PIPE
이면, this attribute는 open()
이 반환한 읽기 가능한 스트림 객체이다.encoing
, errors
인자가 지정되었거나 universal_newlines
인자가 True
이면, 스트림은 텍스트 스트림이고, 그렇지 않으면 바이트 스트림이다.stderr!=PIPE
이면, this attribute는 None
다른 OS PIPE 버퍼가 차고 자식 프로세스를 blocking하는 것으로 인한 교착 상태를 피하려면 .stdin.write
, stdout.read
, stderr.read
대신 communicate()
를 사용하라
Popen.pid
True
로 설정하면, 이것은 생성된 셸의 프로세스 IDPopen.returncode
poll()
과 wait()
그리고 communicate()
에 의해 설정된 자식 반환 코드None
값은 프로세스가 아직 종료되지 않았음을 나타냄-N
은 자식이 시그널 N에 의해 종료되었음 나타냄(POSIX)#!/usr/bin/env python3
import subprocess
import signal
import errno
def subprocess_setup():
# Python installs a SIGPIPE handler by default. This is usually not what
# non-Python subprocesses expect.
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
class CmdError(RuntimeError):
def __init__(self, command, msg=None):
self.command = command
self.msg = msg
def __str__(self):
if not isinstance(self.command, str):
cmd = subprocess.list2cmdline(self.command)
else:
cmd = self.command
msg = "Execution of '%s' failed" % cmd
if self.msg:
msg += ': %s' % self.msg
return msg
class NotFoundError(CmdError):
def __str__(self):
return CmdError.__str__(self) + ": command not found"
class ExecutionError(CmdError):
def __init__(self, command, exitcode, stdout = None, stderr = None):
CmdError.__init__(self, command)
self.exitcode = exitcode
self.stdout = stdout
self.stderr = stderr
self.extra_message = None
def __str__(self):
message = ""
if self.stderr:
message += self.stderr
if self.stdout:
message += self.stdout
if message:
message = ":\n" + message
return (CmdError.__str__(self) +
" with exit code %s" % self.exitcode + message + (self.extra_message or ""))
class Popen(subprocess.Popen):
defaults = {
"close_fds": True,
"preexec_fn": subprocess_setup,
"stdout": subprocess.PIPE,
"stderr": subprocess.PIPE,
"stdin": subprocess.PIPE,
"shell": False,
}
def __init__(self, *args, **kwargs):
options = dict(self.defaults)
options.update(kwargs)
subprocess.Popen.__init__(self, *args, **options)
def run(cmd, input=None, **options):
if isinstance(cmd, str) and not "shell" in options:
options["shell"] = True
try:
pipe = Popen(cmd, **options)
except OSError as exc:
if exc.errno == 2:
raise NotFoundError(cmd)
else:
raise CmdError(cmd, exc)
stdout, stderr = pipe.communicate(input)
if not stdout is None:
stdout = stdout.decode("utf-8")
if not stderr is None:
stderr = stderr.decode("utf-8")
if pipe.returncode != 0:
raise ExecutionError(cmd, pipe.returncode, stdout, stderr)
return stdout, stderr
if __name__ == '__main__':
stdout, stderr = run('echo hello')
print(stdout)
print(stderr)
try:
stdout, stderr = run('dummy')
print(stdout)
print(stderr)
except Exception as exc:
print(exc)
import os
print(os.environ)
# os.environ에서 Dictionary 형태로 'Key': Value, 'Key': [Value1, Value2...] 로 되어있음
export PATH=/update/dir:$PATH
와 똑같은 짓을 하려면import os
existing_PATH=os.getenv("PATH") # 기존의 $PATH 값을 가져온다.
new_path="/update/dir:"+existing_path # /update/dir:$PATH
os.environ["PATH"] = new_path
print(os.getenv("PATH")) # 확인