- 游戏开发
- 后端
【免费下载链接】kbengine
A MMOG engine of server.
本指南以 KBEngine 仓库内置 Python 3.7.3(见 patchlevel.h)随附的标准库文档 subprocess.rst 为骨架,系统讲解subprocess模块的进程派生、管道通信、超时控制与异常体系。读完本文,你将掌握run()与Popen两代 API 的全部常用参数与调用模式、call/check_call/check_output等新旧函数的等价关系、shell=True的安全边界,以及如何把该模块用于 KBEngine 服务器安装脚本这类真实运维场景。
一、subprocess 是什么:新一代子进程管理接口
subprocess模块允许你在 Python 中派生新进程、连接其标准输入/输出/错误管道,并获取其返回码。它的设计意图是取代一批旧模块与旧函数,主要包括:
os.system os.spawn*也就是说,凡是过去靠os.system执行命令、靠os.spawn*家族派生进程的写法,都应优先迁移到subprocess。文档中提供了完整的“替换对照表”(见后文第五节),并指出所有被替换的旧函数在“执行程序找不到”时会(多多少少)静默失败,而subprocess的替代写法会抛出OSError,行为更明确、更安全。
在 KBEngine 仓库中,该模块被内置 Python 标准库收录,源码实现位于 Lib/subprocess.py(run实现于第 431 行、Popen类实现于第 608 行、CompletedProcess实现于第 398 行),并有完整的单元测试 test/test_subprocess.py 佐证其行为。仓库工具脚本中也大量使用它,例如 tools/server/install/installer.py 中便通过subprocess.Popen执行系统命令并采集输出。
二、推荐入口:run() 函数与 CompletedProcess
2.1 run() 函数签名
run()是 Python 3.5 起推荐的统一入口,适用于它能覆盖的所有场景;更高级的场景再退回底层Popen接口。其签名(简化版)为:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)- 执行由
args描述的命令,等待命令结束后返回一个CompletedProcess实例。 - 上面列出的是最常见参数(因此采用仅限关键字的形式);完整签名与
Popen构造函数基本一致,绝大多数参数会直接透传给Popen。其中timeout、input、check、capture_output四个参数不会透传。 - 若
capture_output为真,则 stdout 与 stderr 都会被捕获:内部会自动以stdout=PIPE、stderr=PIPE创建Popen对象,此时不能再同时指定stdout或stderr参数。 timeout参数透传给Popen.communicate;若超时,子进程会被杀死并等待其结束,随后重新抛出TimeoutExpired异常。input参数透传给Popen.communicate作为子进程的 stdin 数据:必须是字节序列,或在指定了encoding/errors、或text=True时可以是字符串。使用它时内部会自动以stdin=PIPE创建Popen,不能再同时指定stdin参数。check=True时,若进程以非零退出码结束,将抛出CalledProcessError;该异常的属性中保存着参数、退出码,以及被捕获的 stdout/stderr。- 指定
encoding/errors,或text=True时,stdin/stdout/stderr 文件对象会以文本模式打开,使用指定的编码与错误处理方式(或io.TextIOWrapper的默认值);universal_newlines与text等价,仅为向后兼容保留。默认情况下文件对象以二进制模式打开。 env非None时,必须是定义新进程环境变量的映射,用于替代默认的“继承当前进程环境”行为,直接透传给Popen。
2.2 运行示例
文档给出的三个经典示例:
>>> 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"], capture_output=True) 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', stderr=b'')注意第三个示例中stdout是bytes序列,因为未开启文本模式;若传入encoding/errors或text=True,则变为字符串。
2.3 CompletedProcess 类
CompletedProcess是run()的返回值,表示一个已结束的进程,包含以下属性:
| 属性 | 含义 |
|---|---|
args | 启动进程所用的参数,可能是列表或字符串 |
returncode | 子进程退出状态;通常 0 表示成功;负值-N表示被信号N终止(仅 POSIX) |
stdout | 捕获到的子进程 stdout。字节序列,或在调用时指定了encoding/errors/text=True时为字符串;未捕获则为None。若以stderr=subprocess.STDOUT运行,stdout 与 stderr 会合并到该属性,stderr属性为None |
stderr | 捕获到的子进程 stderr,规则同stdout |
此外还有方法check_returncode():若returncode非零则抛出CalledProcessError,这是手动复查退出状态的便捷手段。
2.4 三个特殊常量
DEVNULL:用作stdin/stdout/stderr参数,表示使用特殊文件os.devnull(丢弃/不读取数据)。PIPE:表示打开通向标准流的管道,最常配合Popen.communicate使用。STDOUT:仅用于stderr参数,表示标准错误与标准输出共用同一句柄(合并输出)。
三、常用参数深度解析(Frequently Used Arguments)
Popen构造函数(以及各便捷函数)支持大量可选参数以覆盖多种使用场景,但大多数场景用默认值即可。最常被修改的参数如下。
3.1 args:参数序列优先于字符串
args是所有调用都必需的参数,可以是字符串或程序参数序列。官方推荐优先传序列,因为模块会替你处理转义与引号(例如文件名含空格时)。若传单个字符串,则要么shell=True(见下文),要么该字符串只能是无参数的纯程序名。
3.2 stdin / stdout / stderr:标准流重定向
三个参数分别指定被执行程序的标准输入、标准输出、标准错误句柄。合法取值:
PIPE:为子进程新建一条管道;DEVNULL:使用os.devnull;- 一个已存在的文件描述符(正整数);
- 一个已存在的文件对象;
None(默认):不做任何重定向,子进程句柄直接继承自父进程。
此外,stderr还可以取STDOUT,表示把子进程的 stderr 数据捕获进与 stdout 相同的句柄。
3.3 文本模式:text / encoding / errors / universal_newlines
若指定了encoding或errors,或text(即universal_newlines)为真,则 stdin/stdout/stderr 文件对象会以文本模式打开,使用调用中指定的encoding/errors,或io.TextIOWrapper的默认值。文本模式下:
- 对
stdin,输入中的换行符'\n'会被转换为默认行分隔符os.linesep; - 对
stdout/stderr,输出中所有行尾都会被转换为'\n'(详见io.TextIOWrapper在newline=None时的文档)。
如果不使用文本模式,三个流均以二进制流打开,不做任何编码或换行转换。自 3.3 起,universal_newlines=True时使用locale.getpreferredencoding(False)而非locale.getpreferredencoding()。
注意:
Popen.communicate方法不会更新Popen.stdin、Popen.stdout、Popen.stderr文件对象的newlines属性。
3.4 shell=True:何时需要 shell
若shell为True,指定命令将通过 shell 执行。当你主要用 Python 的增强控制流、但仍想利用 shell 的管道、文件名通配符、环境变量展开以及~展开到用户主目录等特性时,这会很方便。不过 Python 自身其实已提供大量类似功能:glob、fnmatch、os.walk、os.path.expandvars、os.path.expanduser、shutil。
安全提示:使用
shell=True之前务必先阅读“安全考虑”一节。
3.5 cwd 与 env
cwd:若非None,执行子进程前会切换到该工作目录;cwd可以是str或 path-like 对象(3.6 起支持),若可执行文件路径是相对路径,则会相对cwd查找executable(或args首项)。env:若非None,则使用该映射作为新进程的环境变量,替代默认的继承行为。注意:若指定了env,必须提供程序运行所需的全部变量——在 Windows 上,为了运行 side-by-side assembly,env必须包含有效的SystemRoot。
四、异常体系与安全考虑
4.1 异常类型
SubprocessError:本模块所有其他异常的基类(3.3 起)。TimeoutExpired:SubprocessError的子类,等待子进程超时时抛出。属性包括:cmd(派生子进程所用的命令)、timeout(秒数)、output(若被run/check_output捕获则为子进程输出,否则None)、stdout(output的别名,与stderr对称)、stderr(若被run捕获则为 stderr 输出,否则None)。CalledProcessError:SubprocessError的子类,check_call或check_output遇到非零退出状态时抛出。属性包括:returncode(若因信号退出则为负数信号号)、cmd、output、stdout(别名)、stderr。
除了模块自定义异常,还有两类常见情况:
- 在子进程真正开始执行新程序前抛出的异常会在父进程中重新抛出,最常见的是
OSError(例如要执行的文件不存在),应用应做好捕获准备。 - 传入非法参数给
Popen会抛出ValueError。 - 所有接受
timeout参数的函数与方法(如call、Popen.communicate)在超时前未等到进程退出时,都会抛出TimeoutExpired。
4.2 安全考虑:shell 注入防护
与某些popen函数不同,本实现绝不会隐式调用系统 shell。这意味着所有字符(包括 shell 元字符)都可以安全地传给子进程。
但若显式通过shell=True调用 shell,则应用自身有责任对所有空白与元字符做恰当的引号转义,以避免 shell 注入漏洞。构造 shell 命令字符串时,应使用shlex.quote函数对字符串做正确转义。
五、底层接口:Popen 构造函数与对象
Popen类负责模块底层的一切进程创建与管理,提供极大的灵活性,用于处理便捷函数覆盖不到的少见场景。在 POSIX 上它采用os.execvp类似行为执行子程序,在 Windows 上采用CreateProcess()。
5.1 构造函数完整签名
subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, encoding=None, errors=None, text=None)各参数要点:
- args:程序参数序列或单个字符串。默认执行
args首项(序列形式)。若为字符串,解释方式依平台而定:POSIX 上将字符串解释为要执行的程序名或路径(仅限不带参数时);Windows 上序列会被按“参数序列转字符串”规则(见第七节)转换。 - shell:默认
False。True时推荐以字符串形式传args。POSIX 上默认 shell 为/bin/sh;args为字符串时它就是要通过 shell 执行的命令(须与在 shell 提示符下输入时完全一致,包括空格文件名要加引号或反斜杠转义);args为序列时等价于Popen(['/bin/sh', '-c', args[0], args[1], ...])。Windows 上默认 shell 由环境变量COMSPEC指定;仅当命令是 shell 内建命令(如dir、copy)或运行批处理文件/控制台程序时才需要shell=True。 - bufsize:创建 stdin/stdout/stderr 管道文件对象时传给
open的对应参数:0为无缓冲;1为行缓冲(仅文本模式下可用);其他正值表示约该大小的缓冲区;负数(默认)使用系统默认io.DEFAULT_BUFFER_SIZE。 - executable:很少用到。
shell=False时它替换args指定的程序,但原始args仍传给程序;POSIX 上args名会成为ps等工具中的显示名。shell=True时(POSIX)它指定替换默认/bin/sh的 shell。 - preexec_fn:若设为可调用对象,会在子进程执行前调用(仅 POSIX)。警告:在线程环境中使用
preexec_fn不安全,子进程可能在 exec 前死锁;如必须用,务必保持简单并尽量减少调用的库。需要修改子进程环境时请用env参数,而不是在preexec_fn里做;start_new_session可替代过去用preexec_fn调用os.setsid()的常见用法。 - close_fds:为真时,除
0、1、2外的所有文件描述符都会在子进程执行前关闭;为假时文件描述符遵循其 inheritable 标志。Windows 上为真时不继承任何句柄(除非在STARTUPINFO.lpAttributeList的handle_list中显式传入,或通过标准句柄重定向)。3.7 起 Windows 上重定向标准句柄时默认值也变为True。 - pass_fds:可选的、需要在父子进程间保持打开的文件描述符序列;提供任何
pass_fds都会强制close_fds=True(仅 POSIX)。 - restore_signals:为真(默认)时,Python 已设为 SIG_IGN 的所有信号(当前包括 SIGPIPE、SIGXFZ、SIGXFSZ)在 exec 前于子进程中恢复为 SIG_DFL(仅 POSIX)。
- start_new_session:为真时,执行子进程前会调用
setsid()系统调用(仅 POSIX)。 - startupinfo / creationflags:Windows 专用,见第七节。
5.2 Popen 支持上下文管理器
Popen对象可作为上下文管理器使用(3.2 起):退出with块时关闭标准文件描述符并等待进程结束:
with Popen(["ifconfig"], stdout=PIPE) as proc: log.write(proc.stdout.read())另外,3.6 起若子进程仍在运行,Popen析构函数会发出ResourceWarning警告,提醒及时回收。
5.3 Popen 对象方法
- poll():检查子进程是否已终止;设置并返回
returncode属性,未终止则返回None。 - wait(timeout=None):等待子进程终止,设置并返回
returncode。超过timeout秒未终止则抛TimeoutExpired,捕获后可以安全地重试等待。注意:使用stdout=PIPE或stderr=PIPE且子进程输出足以撑满 OS 管道缓冲区时,wait()会死锁(子进程阻塞等待管道被读取)——使用管道时请用communicate()。该函数内部用忙循环(非阻塞调用加短眠)实现,需要异步等待时可用asyncio模块的asyncio.create_subprocess_exec。 - communicate(input=None, timeout=None):与进程交互——向 stdin 发送数据、读取 stdout/stderr 直到 EOF、等待进程终止,返回
(stdout_data, stderr_data)元组。文本模式下input须为字符串,否则为 bytes。要发送 stdin 数据须以stdin=PIPE创建;要拿到非None的输出须同时给stdout=PIPE和/或stderr=PIPE。若超时抛TimeoutExpired,捕获后重试通信不会丢失任何输出。超时并不会自动杀死子进程,规范做法是主动 kill 后补一次通信:
proc = subprocess.Popen(...) try: outs, errs = proc.communicate(timeout=15) except TimeoutExpired: proc.kill() outs, errs = proc.communicate()注意:
communicate读取的数据在内存中缓冲,数据量大或无限时不要使用该方法。
- send_signal(signal):向子进程发送信号。Windows 上 SIGTERM 是
terminate的别名;CTRL_C_EVENT 与 CTRL_BREAK_EVENT 可发送给以包含CREATE_NEW_PROCESS_GROUP的creationflags启动的进程。 - terminate():停止子进程。POSIX 发 SIGTERM;Windows 调用 Win32 API
TerminateProcess。 - kill():杀死子进程。POSIX 发 SIGKILL;Windows 上
kill是terminate的别名。
5.4 Popen 对象属性
| 属性 | 含义 |
|---|---|
args | 传给Popen的args(程序参数序列或单个字符串) |
stdin | 若stdin=PIPE,为可写流对象(文本或字节流取决于encoding/errors/universal_newlines);否则为None |
stdout | 若stdout=PIPE,为可读流对象,读取即得到子进程输出;否则为None |
stderr | 若stderr=PIPE,为可读流对象;否则为None |
pid | 子进程进程 ID。注意若shell=True,这是被派生的 shell 的 PID |
returncode | 由poll/wait/communicate设置;None表示进程尚未终止;负值-N表示被信号N终止(仅 POSIX) |
死锁警告:应使用
communicate()而非Popen.stdin.write、Popen.stdout.read、Popen.stderr.read,以避免任一 OS 管道缓冲区被填满而阻塞子进程导致的死锁。
六、旧版高级 API:call / check_call / check_output
Python 3.5 之前,这三个函数构成高级 API;run()出现后新代码应优先用run(),但存量代码大量仍在调用它们。
- call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None):运行命令并等待结束,返回
Popen.returncode。等价于run(...).returncode(但不支持input与check参数)。不要与stdout=PIPE或stderr=PIPE连用——管道没人读取,子进程输出撑满 OS 管道缓冲区时会阻塞。 - check_call(...):同
call,但返回码非零时抛CalledProcessError(其returncode属性保存返回码)。等价于run(..., check=True)(不支持input)。同样不建议与PIPE连用。 - check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=None, timeout=None, text=None):运行命令并返回其输出。返回码非零抛
CalledProcessError,输出保存在其output属性。等价于run(..., check=True, stdout=PIPE).stdout。默认返回编码后的 bytes(实际编码取决于被调用的命令,解码常需在应用层处理);设universal_newlines=True可覆盖。不支持显式传input=None来继承父进程标准输入。合并捕获 stderr 用stderr=subprocess.STDOUT:
>>> subprocess.check_output( ... "ls non_existent_file; exit 0", ... stderr=subprocess.STDOUT, ... shell=True) 'ls: non_existent_file: No such file or directory\n'七、替换旧函数对照表
“a 变成 b”表示 b 可作为 a 的替代。所有被替换的 “a” 函数在程序找不到时都会(多或少)静默失败,而替代写法抛OSError;使用check_output的替代写法在非零返回码时会抛CalledProcessError(输出仍可从其output属性获取)。
替换 shell 反引号:
output=`mycmd myarg`output = check_output(["mycmd", "myarg"])替换 shell 管道:
output=`dmesg | grep hda`p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) p1.stdout.close() # 允许 p1 在 p2 退出时收到 SIGPIPE output = p2.communicate()[0]p1.stdout.close()这一步很重要:若 p2 先于 p1 退出,p1 需要收到 SIGPIPE。对可信输入,也可直接用 shell 管道:
output = check_output("dmesg | grep hda", shell=True)替换 os.system:
sts = os.system("mycmd" + " myarg") # 变成 sts = call("mycmd" + " myarg", shell=True)更贴近现实的完整写法(含信号与 OSError 处理):
try: retcode = call("mycmd" + " myarg", shell=True) if retcode < 0: print("Child was terminated by signal", -retcode, file=sys.stderr) else: print("Child returned", retcode, file=sys.stderr) except OSError as e: print("Execution failed:", e, file=sys.stderr)替换 os.spawn 家族:
pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") # ==> pid = Popen(["/bin/mycmd", "myarg"]).pid 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 / os.popen2 / os.popen3 / os.popen4:
(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)返回码处理对照:
pipe = os.popen(cmd, 'w') ... rc = pipe.close() if rc is not None and rc >> 8: print("There were some errors") # ==> process = Popen(cmd, stdin=PIPE) ... process.stdin.close() if process.wait() != 0: print("There were some errors")替换 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/Popen4基本等同subprocess.Popen,区别在于:Popen执行失败会抛异常;capturestderr参数被stderr参数取代;必须显式指定stdin=PIPE与stdout=PIPE;popen2 默认关闭所有文件描述符,而用Popen需显式close_fds=True才能在所有平台/旧版 Python 上保证此行为。
八、遗留 shell 调用函数:getstatusoutput / getoutput
这两个函数来自 2.x 的commands模块,会隐式调用系统 shell,前文所述的安全性与异常处理一致性保证对它们不适用,仅作兼容保留。
- getstatusoutput(cmd):在 shell 中执行字符串
cmd,返回(exitcode, output)二元组(通过Popen.check_output实现,使用 locale 编码)。输出末尾的换行会被去除。示例:
>>> subprocess.getstatusoutput('ls /bin/ls') (0, '/bin/ls') >>> subprocess.getstatusoutput('cat /bin/junk') (1, 'cat: /bin/junk: No such file or directory') >>> subprocess.getstatusoutput('/bin/junk') (127, 'sh: /bin/junk: not found') >>> subprocess.getstatusoutput('/bin/kill $$') (-15, '')- getoutput(cmd):与
getstatusoutput类似,但忽略退出码,仅返回包含命令输出(stdout 与 stderr)的字符串:
>>> subprocess.getoutput('ls /bin/ls') '/bin/ls'两者 POSIX 与 Windows 均可用。
九、Windows 专属能力
9.1 STARTUPINFO 与进程创建
STARTUPINFO类与下列常量仅 Windows 可用,用于精细控制进程创建(部分支持 WindowsSTARTUPINFO结构):
STARTUPINFO(*, dwFlags=0, hStdInput=None, hStdOutput=None, hStdError=None, wShowWindow=0, lpAttributeList=None)dwFlags:决定某些STARTUPINFO属性在进程创建窗口时是否生效的位字段;常用组合如STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW。hStdInput/hStdOutput/hStdError:当dwFlags指定STARTF_USESTDHANDLES时分别指定进程的标准输入/输出/错误句柄;否则各自使用默认(键盘缓冲区/控制台窗口缓冲区)。wShowWindow:当dwFlags指定STARTF_USESHOWWINDOW时,可为ShowWindow函数nCmdShow参数允许的任何值(SW_SHOWDEFAULT除外);SW_HIDE用于此属性,Popen以shell=True调用时即使用它。lpAttributeList:STARTUPINFOEX的附加属性字典,支持handle_list(将被继承的句柄序列;非空时close_fds必须为真)。传入Popen构造函数前,这些句柄须先用os.set_handle_inheritable临时设为可继承,否则会抛 Windows 错误ERROR_INVALID_PARAMETER(87)。
9.2 creationflags 常量
Popen的creationflags参数可选以下常量(可组合):
| 常量 | 含义 |
|---|---|
CREATE_NEW_CONSOLE | 新进程使用新控制台,而非继承父控制台 |
CREATE_NEW_PROCESS_GROUP | 创建新进程组;用os.kill杀子进程所必需;与CREATE_NEW_CONSOLE同时指定时被忽略 |
ABOVE_NORMAL_PRIORITY_CLASS/BELOW_NORMAL_PRIORITY_CLASS | 高于/低于平均优先级 |
HIGH_PRIORITY_CLASS/IDLE_PRIORITY_CLASS/NORMAL_PRIORITY_CLASS | 高/空闲(最低)/正常(默认)优先级 |
REALTIME_PRIORITY_CLASS | 实时优先级;几乎不该用,会打断管理鼠标、键盘输入与后台磁盘刷新的系统线程 |
CREATE_NO_WINDOW | 新进程不创建窗口 |
DETACHED_PROCESS | 新进程不继承父控制台;不能与CREATE_NEW_CONSOLE同用 |
CREATE_DEFAULT_ERROR_MODE | 新进程不继承调用进程的错误模式,而使用默认错误模式(对以禁用硬错误运行的多线程 shell 应用尤其有用) |
CREATE_BREAKAWAY_FROM_JOB | 新进程不与作业关联 |
另有STD_INPUT_HANDLE/STD_OUTPUT_HANDLE/STD_ERROR_HANDLE(标准输入/输出/错误设备,初始为控制台缓冲区CONIN$/CONOUT$)与SW_HIDE(隐藏窗口)等常量。
9.3 Windows 上参数序列转字符串规则
Windows 上args序列会被转换为可被 MS C 运行时解析的字符串,规则如下:
- 参数以空白(空格或制表符)分隔;
- 被双引号包围的字符串视为单个参数,不论其内部空白;带引号字符串可嵌入参数中;
- 反斜杠前加双引号,该双引号视为字面双引号;
- 反斜杠按字面解释,除非它紧跟在双引号前;
- 若反斜杠紧跟在双引号前,每对反斜杠解释为一个字面反斜杠;若反斜杠数量为奇数,最后一个反斜杠按规则 3 转义其后的双引号。
相关解析与转义工具可参考shlex模块。
十、仓库实战:KBEngine 中的 subprocess 使用模式
KBEngine 仓库内置的正是本指南所对应的 Python 3.7.3(见 patchlevel.h 第 26 行),因此本文档所述的run()/capture_output/text等 3.5~3.7 特性在该环境中均可直接使用;实现与测试可在 Lib/subprocess.py 与 test/test_subprocess.py 中查看。
在服务器运维侧,tools/server/install/installer.py 提供了贴近实际的调用样本:
import subprocess from subprocess import check_call ... cret = subprocess.Popen(cmdstr, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.readlines() ... ret = subprocess.Popen("whereis mysql", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.readlines()[0].split()可以看出该脚本采用了shell=True+stdout=PIPE+stderr=STDOUT的组合:让命令经 shell 执行(便于拼接重定向与多个命令)、把标准错误合并进标准输出、随后读取全部行。这正是“捕获命令输出、判断命令是否可用”(如探测 mysql 安装位置)这类运维场景的常见写法。参考本文第四节的建议,当这类命令串中包含外部可控输入时应改用参数序列或shlex.quote规避注入风险;需要同步等待并校验退出码时,则可升级为run(..., check=True, capture_output=True, text=True)的现代写法。
小结
subprocess是 Python 中创建与管理子进程的标准答案:日常场景用run()一把梭,需要流式交互或精细控制时下沉到Popen;call/check_call/check_output用于兼容存量代码;getstatusoutput/getoutput仅作遗留兼容。核心要点可归纳为四句话:能用参数序列就别用 shell 字符串、用管道必配communicate防死锁、涉及外部输入必须防注入、需要校验结果就用check=True并捕获CalledProcessError。在 KBEngine 内置的 Python 3.7.3 环境中,上述全部 API 均可直接使用,仓库的安装脚本 installer.py 即是现成的实战参照。
- 游戏开发
- 后端
【免费下载链接】kbengine
A MMOG engine of server.
相关推荐
KBEngine 内置 Python 3.7 多进程编程实战:multiprocessing 模块完全指南与源码级解析
KBEngine 内置 Python 3.7 多进程编程实战:multiprocessing 模块完全指南与源码级解析 KBEngine( gh_mirrors
游戏开发后端KBEngine 内置 Python 3.7.3 数据类详解:dataclasses 模块从原理到实战
KBEngine 内置 Python 3.7.3 数据类详解:dataclasses 模块从原理到实战 KBEngine 服务端脚本引擎基于 Python 实现
游戏开发后端KBEngine 内置 Python 的 html 模块完全指南:escape、unescape 与 HTMLParser 实战解析
KBEngine 内置 Python 的 html 模块完全指南:escape、unescape 与 HTMLParser 实战解析 本文以 KBEngine
游戏开发后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考