subprocess.run 和 subprocess.Popen 区别
subprocess.run:是阻塞式,非交互性,返回值:不是我们想要的执行结果或相关信息,而是一个 CompletedProcess 类型对象
subprocess.Popen:非阻塞式,交互性,返回值:是一个Popen对象,
<subprocess.Popen object at 0x0000000002B17668>
Popen对象的stdin、stdout和stderr是三个文件句柄,可以像文件那样进行读写操作。
import subprocess
s = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
s.stdin.write(b"import os\n")
s.stdin.write(b"print(os.environ)")
s.stdin.close()
可以对输入,输出交互性进行操作
process = subprocess.Popen(exe_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,shell=True)
# 需等待程序执行完成,才输出结果,属于阻塞式
# stdout, stderr =process.communicate()
# 无需等待程序执行完成,立即输出程序执行状态,非阻塞式
stdout, stderr = process.stdout,process.stderr
============================================================================================================
"""
subprocess.run
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, encoding=None, errors=None)
功能:执行 args 参数所表示的命令,等待命令结束,并返回一个 CompletedProcess 类型对象。
注意,run() 方法返回的不是我们想要的执行结果或相关信息,而是一个 CompletedProcess 类型对象。
上面参数表里展示的只是一些常用的,真实情况还有很多。
args:表示要执行的命令,必须是一个字符串,字符串参数列表。
stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。
subprocess.PIPE表示为子进程创建新的管道,subprocess.DEVNULL表示使用os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出TimeoutExpired异常。
check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹出CalledProcessError异常。
encoding:如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
>>> subprocess.run(["ls", "-l"]) # 没有对输出进行捕获
CompletedProcess(args=['ls', '-l'], returncode=0)
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback (most recent call last):
...
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
>>> subprocess.run(["ls", "-l", "/dev/null"], stdout=subprocess.PIPE)
CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n')
>>> subprocess.run("python --version", stdout=subprocess.PIPE)
CompletedProcess(args='python --version', returncode=0, stdout=b'Python 3.6.1\r\n')
>>>s= subprocess.run("ipconfig", stdout=subprocess.PIPE) # 捕获输出
>>>print(s.stdout.decode("GBK"))
subprocess.CompletedProcess
run() 方法的返回值,表示一个进程结束了。CompletedProcess类有下面这些属性:
args 启动进程的参数,通常是个列表或字符串。
returncode 进程结束状态返回码。0表示成功状态。
stdout 获取子进程的 stdout。通常为 bytes 类型序列,None 表示没有捕获值。如果你在调用 run() 方法时,设置了参数stderr=subprocess.STDOUT,则错误信息会和 stdout 一起输出,此时 stderr 的值是 None。
stderr 获取子进程的错误信息。通常为 bytes 类型序列,None 表示没有捕获值。
check_returncode() 用于检查返回码。如果返回状态码不为零,弹出CalledProcessError异常。
subprocess.DEVNULL
一个特殊值,用于传递给 stdout、stdin 和 stderr 参数。表示使用os.devnull作为参数值。
subprocess.PIPE
管道,可传递给 stdout、stdin 和 stderr 参数。
subprocess.STDOUT
特殊值,可传递给 stderr 参数,表示 stdout 和 stderr 合并输出。
args 与 shell
args 参数可以接收一个类似'du -sh'的字符串,也可以传递一个类似['du', '-sh']的字符串分割列表。shell 参数默认为 False,设置为 True 的时候表示使用操作系统的 shell 执行命令。
获取执行结果
run() 方法返回的是一个 CompletedProcess 类型对象,不能直接获取我们通常想要的结果。要获取命令执行的结果或者信息,在调用 run() 方法的时候,请指定 stdout=subprocess.PIPE。
"""
"""
交互式输入
并不是所有的操作系统命令都像‘dir’或者‘ipconfig’那样单纯地返回执行结果,还有很多像‘python’这种交互式的命令,你要输入点什么,然后它返回执行的结果。使用run()方法怎么向stdin里输入?
这样?
import subprocess
ret = subprocess.run("python", stdin=subprocess.PIPE, stdout=subprocess.PIPE,shell=True)
ret.stdin = "print('haha')" # 错误的用法
print(ret)
这样是不行的,ret作为一个CompletedProcess对象,根本没有stdin属性。那怎么办呢?前面说了,run()方法的stdin参数可以接收一个文件句柄。比如在一个1.txt文件中写入print('i like Python')。然后参考下面的使用方法:
import subprocess
fd = open("d:\\1.txt")
ret = subprocess.run("python", stdin=fd, stdout=subprocess.PIPE,shell=True)
print(ret.stdout)
fd.close()
这样做,虽然可以达到目的,但是很不方便,也不是以代码驱动的方式。这个时候,我们可以使用Popen类。
subprocess.Popen()
用法和参数与run()方法基本类同,但是它的返回值是一个Popen对象,而不是CompletedProcess对象。
>>> ret = subprocess.Popen("dir", shell=True)
>>> type(ret)
<class 'subprocess.Popen'>
>>> ret
<subprocess.Popen object at 0x0000000002B17668>
Popen对象的stdin、stdout和stderr是三个文件句柄,可以像文件那样进行读写操作。
>>>s = subprocess.Popen("ipconfig", stdout=subprocess.PIPE, shell=True)
>>>print(s.stdout.read().decode("GBK"))
要实现前面的‘python’命令功能,可以按下面的例子操作:
import subprocess
s = subprocess.Popen("python", stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=True)
s.stdin.write(b"import os\n")
s.stdin.write(b"print(os.environ)")
s.stdin.close()
out = s.stdout.read().decode("GBK")
s.stdout.close()
print(out)
"""
# https://zhuanlan.zhihu.com/p/91342640
import os
import subprocess
exe_path=os.path.abspath('./wxbot-sidecar.exe')
'''
subprocess.run是一个阻塞方法,执行了这个接口后,需要等待 run入参的命令执行完才能返回
'''
# result = subprocess.run(exe_path,shell=True,stdout=subprocess.PIPE)
# print(type(result))
'''
而有些时候,我们需要单独起一个(进程执行) cmd命令,然后周期性每几秒钟去检查命令执行的状态,
检查完之后我们还可以在主进程干别的事情,
也就是搞一个独立出来的进程。这种情况下, subprocess.run就无法满足,必须直接开 subprocess.Popen
'''
result = subprocess.Popen(exe_path,shell=True,stdout=subprocess.PIPE)
print(type(result))
'''
这段程序模拟了周期性等待子进程执行完成的场景,执行完成后拉取 stdout和 stderr打印,执行超时就强杀进程。基本上关键的地方都有注释,如果有其他类似的场景,可以直接照搬代码。
最后我们也能看到, subprocess本质也是多进程,但和 multiprocessing有所不同, multiprocessing是多个 python进程,着重于管理多个 python进程的运行时环境以及之间的通信;而 subprocess则是侧重于去跟踪 python程序启动的任意类型进程的状态。
两者也有共同点,就是主进程都会持有子进程的 handle,只要没调用类似 subprocess.run这种阻塞获取子进程状态/结果的接口,在起了新进程后,主进程内都能够随时随地去获取子进程的状态信息。
'''
import subprocess
import platform
import os
import signal
def _decode_bytes(_bytes):
encoding = 'gbk'
return _bytes.decode(encoding)
def _decode_stream(stream):
"""windows下解码stdout/stderr的数据"""
if not stream:
return ''
return _decode_bytes(stream.read())
args = ['ping', '127.0.0.1']
working_directory = '.'
wait_timeout = 1
cnt, maxcnt = 0, 4
print(f'platform system: {platform.system()}')
p = subprocess.Popen(args,
cwd=working_directory,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
print(f'process args: {args}')
print(f'process pid: {p.pid}')
while cnt < maxcnt:
try:
p.wait(timeout=wait_timeout)
except Exception as e:
print(f'attempt {cnt} -> wait err: {e}')
cnt += 1
finally:
if p.returncode is not None:
break
if p.returncode is None:
print('[ERROR] retcode is None, maybe timeout, try kill process...')
if platform.system() == 'Windows':
kill_proc_ret = subprocess.run(['taskkill', '/f', '/pid', str(p.pid)], capture_output=True)
print(f'[KILLPROC] {_decode_bytes(kill_proc_ret.stdout)}')
else:
os.kill(p.pid, signal.SIGKILL)
else:
retcode, stdout, stderr = p.returncode, _decode_stream(p.stdout), _decode_stream(p.stderr)
print(f'[OK] retcode: {retcode}\n\tstdout: {stdout}\n\tstderr: {stderr}')