许多知名库都在使用此模块创建进程,以及做为跨语言粘合工具。典型如ansible, celery,selenium 等。
根据上述主要区别,不难推断出, subprocess创建子进程的用途,主要用于执行非python的外部程序,如windows/linux 命令,C程序,Java程序等,而且可以实现进程通信,多进程管道,以及异步执行等。
(1)run()方法创建子进程
(2)stdin, stdout,stderr 的配置,以及管道使用
(3)Popen API使用。
(4)进程之间通信
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, text=None, env=None)
返回值类型:
subprocess.CompletedProcess
主要参数:
主要属性:
示例
>>> subprocess.run(["ls", "-l"]) # doesn't capture output
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
OS 执行一个shell命令,会自动打开三个标准文件:
进程的I/O操作:
标准输入、输出可以重定向, 从ubuntu linux为例
有时host进程可能修改了输入/输出设备,subprocess将继承,可以手工指定I/O设备
windows用run()时,args指令中前面要加cmd.exe做为执行器
cmdTuple =(“cmd.exe”, “/C”, r"del d:\output*.png")
subprocess.run(cmdTuple)
如果运行dos命令,前两个参数为 “cmd.exe”, “/C”, 否则报错。
subprocess.run([‘cmd’, ‘/C’, ‘dir D:\app’])
也可使用powshell 做执行器, 其格式如下:
subprocess.run([“powershell”, “-Command”, “dir D:\app”])
运行 .py文件,无须加 cmd.exe
subprocess.run([‘python’, ‘demo.py’, ‘5’]) 其中5为参数
指令也可以用字符串的形式,用shlex来解析为list
import shlex
print( shlex.split(“python subp_timer.py 5”))
subprocess.run(shlex.split(“python subp_timer.py 5”))
output:
[‘python’, ‘subp_timer.py’, ‘5’]
Starting timer of 5 seconds
…Done!
linux 使用默认shell做为执行器,也可以指定如用 ‘bash’
subprocess.run([“bash”, “-c”, “ls /usr/bin | grep pycode”])
Pipe 即管道,可以将两个进程连接起来:上1个进程的stdout 可以做为下1个进程的输入
cp1 = subprocess.run(
['cmd.exe','/C','dir /A:D /B','D:\workplace'],
stdout=subprocess.PIPE
)
print(cp1.stdout.decode('utf-8'))
cp2 = subprocess.run(
['cmd.exe','/C','find','/I','\"python\"'],
input=cp1.stdout,
stdout=subprocess.PIPE
)
print(cp2)
Popen 是 subprocess的核心,底层的子进程的创建和管理都靠它处理,它支持主程序与子进程之间通信。 run()方法只能用于一些简单场合,Popen()更加方便。
class subprocess.Popen(args, bufsize=-1, stdin=None, stdout=None, stderr=None,
shell=False, cwd=None, env=None, *, encoding=None)
常用参数:
创建一个子进程,然后执行一个简单的命令:
实例
>>> import subprocess
>>> p = subprocess.Popen('ls -l', shell=True)
>>> total 164
-rw-r--r-- 1 root root 133 Jul 4 16:25 admin-openrc.sh
-rw-r--r-- 1 root root 268 Jul 10 15:55 admin-openrc-v3.sh ...
>>> p.returncode
>>> p.wait() 0
>>> p.returncode
Popen(["/usr/bin/git", "commit", "-m", "Fixes a bug."])
with Popen(["ifconfig"], stdout=PIPE) as proc:
log.write(proc.stdout.read())
proc = subprocess.Popen(...)
try:
outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
proc.kill()
outs, errs = proc.communicate()
属性:
args, stdin, stdout, stderr, pid, returncode
stdout值为bytes 类型,查看时通常需要转为str, 但windows 命令返回的stdout编码类型可能不是utf-8. 需要使用chardet.detect( bytes_obj) 来检测
import chardet
import subprocess
cmd = ['cmd.exe','/C', 'ipconfig']
pp = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out: bytes = pp.stdout.read()
encode = chardet.detect(out)['encoding']
print(encode)
print(out.decode(encode))
output:
PS D:\workplace\python\test1\multi_thread> py subp_2.py
GB2312
Windows IP 配置
...
方式1: 通过communicate(input=bytes_obj) 输入参数
process = subprocess.Popen(['cmd', '/C', 'findstr','example'], stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 使用input参数传递输入
input_data = b"Some input\n subprocess \n example line"
out, err = process.communicate(input=input_data)
print(out)
方式2: 通过Pipe向子进程输入数据: process.stdin.write()
process = subprocess.Popen(['cmd', '/C', 'findstr','example'], stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# Write to the subprocess's standard input
process.stdin.write(b'first line \n 2:some example input\n third line\n')
# Close the input stream
process.stdin.close()
out, err = process.communicate()
print(out, err)
3)获取子进程的输出内容
方式1: 使用 process.communicate() 方法获取 output 与 error
out, err = process.communicate(), out, err 均为bytes 类型
方式2: 直接读 process.stdout 属性, 方式与读文件相同,
line = process.stdout.readline()
content = process.stdout.read()
示例
# 读取子进程的输出
cmd = ["ping", "baidu.com"]
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
counter = 0
while True:
# Read a line from the subprocess's stdout
line = process.stdout.readline()
# Check if the line is empty, indicating that the subprocess has finished
if not line :
break
if counter > 3:
print(f"terminate process {process.pid}")
process.terminate() # 强行终止进程
break
counter += 1
print(process.poll()) # 检查进程是否结束
# Process and print the line
print(line, end='')
# Wait for the subprocess to finish and get its return code
return_code = process.wait(2)
print(f"Subprocess returned with exit code: {return_code}")
print(process.poll())
asyncio异步模块也提供了 subprocess 类, 好处是避开了GIL锁的限制, 运行速度显著提高
import asyncio
async def run(cmd):
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
print(f'[{cmd!r} exited with {proc.returncode}]')
if stdout:
print(f'[stdout]\n{stdout.decode()}')
if stderr:
print(f'[stderr]\n{stderr.decode()}')
async def main():
await asyncio.gather(
run('python subp_timer.py 2'),
)
asyncio.run(main())
子进程可能会遇到各种问题,建议使用如下处理异常的代码结构:
import subprocess
try:
cmd = ["your_command_here"]
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
stdout, stderr = process.communicate()
print(stdout,stderr)
except subprocess.CalledProcessError as e:
print(f'Subprocess failed with return code {e.returncode}')
except FileNotFoundError:
print('Command not found')
(1)命令不能运行,通常是args 列表有问题。 可先在terminal 测试
(2)命令行处理的文件与当前目录不同,
(3)进程block问题