如何从 Python 执行程序或调用系统命令
简单,使用subprocess.run,它返回一个CompletedProcess对象:
>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)
(run 想要一个词法分析的 shell 参数列表 - 这是你在 shell 中键入的内容,用空格分隔,但不是空格被引用的地方,所以使用专门的函数 split 来拆分你在 shell 中输入的内容)
为什么?
从 Python 3.5 开始,文档推荐subprocess.run:
调用子流程的推荐方法是对它可以处理的所有用例使用 run() 函数。对于更高级的用例,可以直接使用底层的 Popen 接口。
这是一个最简单的用法示例 - 它完全按照要求进行:
>>> from subprocess import run
>>> from shlex import split
>>> completed_process = run(split('python --version'))
Python 3.8.8
>>> completed_process
CompletedProcess(args=['python', '--version'], returncode=0)
run 等待命令成功完成,然后返回一个CompletedProcess 对象。它可能会引发TimeoutExpired(如果你给它一个timeout=参数)或CalledProcessError(如果它失败并且你通过check=True)。
您可能从上面的示例中推断出,默认情况下,stdout 和 stderr 都会通过管道传输到您自己的 stdout 和 stderr。
我们可以检查返回的对象并查看给出的命令和返回码:
>>> completed_process.args
['python', '--version']
>>> completed_process.returncode
0
捕获输出
如果要捕获输出,可以将subprocess.PIPE 传递给相应的stderr 或stdout:
>>> from subprocess import PIPE
>>> completed_process = run(shlex.split('python --version'), stdout=PIPE, stderr=PIPE)
>>> completed_process.stdout
b'Python 3.8.8\n'
>>> completed_process.stderr
b''
并且那些各自的属性返回字节。
传递一个命令列表
人们可能很容易从手动提供命令字符串(如问题所暗示的那样)转向提供以编程方式构建的字符串。 不要以编程方式构建字符串。这是一个潜在的安全问题。最好假设您不信任输入。
>>> import textwrap
>>> args = ['python', textwrap.__file__]
>>> cp = run(args, stdout=subprocess.PIPE)
>>> cp.stdout
b'Hello there.\n This is indented.\n'
注意,只有args 应该按位置传递。
完整签名
这是源代码中的实际签名,如help(run)所示:
def run(*popenargs, input=None, timeout=None, check=False, **kwargs):
popenargs 和 kwargs 被赋予 Popen 构造函数。 input 可以是字节字符串(或 unicode,如果指定编码或 universal_newlines=True),将通过管道传输到子进程的标准输入。
文档对timeout= 和check=True 的描述比我想象的要好:
超时参数被传递给 Popen.communicate()。如果超时
过期,子进程将被杀死并等待。这
TimeoutExpired 异常将在子进程完成后重新引发
终止。
如果检查为真,并且进程以非零退出代码退出,则
将引发 CalledProcessError 异常。那的属性
异常保存参数、退出代码以及 stdout 和 stderr if
他们被俘虏了。
check=True 的这个例子比我想出的要好:
>>> 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(args, *, stdin=None, input=None, stdout=None, stderr=None,
shell=False, cwd=None, timeout=None, check=False, encoding=None,
errors=None)
请注意,这表明只有 args 列表应按位置传递。所以将剩余的参数作为关键字参数传递。
打开
何时改用Popen?我很难仅根据论点找到用例。但是,直接使用 Popen 可以让您访问其方法,包括 poll、'send_signal'、'terminate' 和 'wait'。
这是the source 中给出的Popen 签名。我认为这是对信息最精确的封装(相对于help(Popen)):
def __init__(self, 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=(), *, user=None, group=None, extra_groups=None,
encoding=None, errors=None, text=None, umask=-1, pipesize=-1):
但信息量更大的是the Popen documentation:
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=(), *, group=None,
extra_groups=None, user=None, umask=-1, encoding=None, errors=None,
text=None)
在新进程中执行子程序。在 POSIX 上,该类使用
os.execvp()-like 行为来执行子程序。在 Windows 上,
该类使用 Windows CreateProcess() 函数。论据
Popen如下。
了解Popen 上的其余文档将留给读者作为练习。