Python os.system() 和 subprocess 怎么选?运行系统命令哪个更好用?

张开发
2026/5/3 20:52:51 15 分钟阅读
Python os.system() 和 subprocess 怎么选?运行系统命令哪个更好用?
引言Python 提供了多种从代码中运行系统命令的方法。无论您需要列出文件、调用外部工具还是自动化 shell 工作流标准库都包含了处理所有这些场景的模块。两种主要方法是较旧的os.system()函数和现代的subprocess模块它提供了subprocess.run()、subprocess.call()、subprocess.Popen()以及相关工具。本教程通过实际示例逐步介绍每种方法解释何时使用每种方法并涵盖重要主题如输出捕获、错误处理、返回码以及与shellTrue相关联的安全风险。到最后您将知道哪种方法适合您的用例以及如何从 Python 安全地运行外部命令。关键要点subprocess.run()是 Python 3.5 及更高版本中执行系统命令的推荐方式取代了os.system()和subprocess.call()。除非您特别需要 shell 功能如管道或通配符否则避免使用shellTrue。传递参数列表而不使用 shell 可以防止命令注入漏洞。使用capture_outputTrue与subprocess.run()一起捕获stdout和stderr作为字符串或字节。这让您完全控制命令输出。设置checkTrue以在命令返回非零退出码时自动引发CalledProcessError使错误处理变得简单。对于高级场景如长时间运行的进程或实时输出流使用subprocess.Popen配合.communicate()、.poll()或.wait()。什么是 Python 系统命令Python 系统命令是从 Python 代码中调用操作系统执行外部程序或 shell 指令的任何调用。这包括运行如ls、git、curl或机器上安装的任何其他命令行工具。Python 通过几个内置函数和模块支持此功能os.system()将命令字符串传递给系统 shell。这是最高效的方法但无法访问命令的输出。subprocess.call()运行命令并返回其退出码。它是subprocess模块的一部分被认为是比os.system()更高级的选择。subprocess.run()是现代的推荐函数Python 3.5 引入。它返回一个CompletedProcess对象包含返回码、捕获的输出和错误流。subprocess.Popen()提供了最多的控制。它允许您实时与运行中的进程交互按行流式传输输出并直接管理 stdin/stdout/stderr 管道。本教程的其余部分将详细介绍这些方法并提供可工作的代码示例。使用os.system()运行 shell 命令os.system()函数通过系统 shell 执行命令字符串并返回命令的退出状态。它是从 Python 运行外部命令的最基本方法。以下是一个简单的示例用于检查安装的 Python 版本import os cmd python3 --version returned_value os.system(cmd) print(returned value:, returned_value)输出Python 3.14.3 returned value: 0返回值为0表示命令成功运行。任何非零值表示错误。使用os.system()时需要注意几点无输出捕获。命令的输出直接输出到控制台。您无法将其存储到变量中。有限的错误处理。您只能获取退出码。无法单独捕获stderr。Shell 执行。命令字符串直接传递给 shellUnix 上为/bin/shWindows 上为cmd.exe如果命令的任何部分来自用户输入这会引入潜在的安全风险。Python os 模块文档本身推荐使用subprocess模块代替。os.system()函数保留用于向后兼容但subprocess.run()是新代码的更好选择。使用subprocess.call()执行命令subprocess.call()函数是subprocess模块的一部分其工作方式类似于os.system()。它运行一个命令并返回其退出码。不同之处在于它提供了更多对命令调用方式的控制。import subprocess cmd python3 --version returned_value subprocess.call(cmd, shellTrue) print(returned value:, returned_value)输出Python 3.14.3 returned value: 0你也可以将命令作为参数列表传递这样完全避免使用 shellimport subprocess returned_value subprocess.call([python3, --version]) print(returned value:, returned_value)输出Python 3.14.3 returned value: 0传递列表更安全因为它绕过了 shell 解释。命令及其参数直接发送给操作系统而无需任何 shell 解析从而消除了命令注入的风险。虽然subprocess.call()改进了os.system()但它仍然无法捕获输出。要实现这一点你需要使用subprocess.run()或subprocess.check_output()。使用subprocess.run()推荐的现代方法subprocess.run()在 Python 3.5 中引入是运行外部命令的推荐方式。它返回一个CompletedProcess对象其中包含返回码、捕获的标准输出和捕获的标准错误。基本用法import subprocess result subprocess.run([echo, Hello from subprocess], capture_outputTrue, textTrue) print(stdout:, result.stdout.strip()) print(returncode:, result.returncode)输出stdout: Hello from subprocess returncode: 0capture_outputTrue参数告诉 Python 收集stdout和stderr。设置textTrue会将输出作为字符串返回而不是原始字节。使用checkTrue自动抛出错误当你传递checkTrue时如果命令以非零返回码退出Python 会抛出subprocess.CalledProcessErrorimport subprocess try: result subprocess.run( [ls, /nonexistent_path], capture_outputTrue, textTrue, checkTrue ) except subprocess.CalledProcessError as e: print(fCommand failed with return code: {e.returncode}) print(fstderr: {e.stderr.strip()})输出Command failed with return code: 1 stderr: ls: /nonexistent_path: No such file or directory这种模式比每次命令后手动检查返回码要简洁得多。设置timeout你可以使用timeout参数单位为秒为命令设置最大执行时间import subprocess try: result subprocess.run([sleep, 10], capture_outputTrue, textTrue, timeout2) except subprocess.TimeoutExpired as e: print(fCommand timed out after {e.timeout} seconds)输出Command timed out after 2 seconds超时机制在调用可能挂起的外部服务或命令时非常有用。理解shellTrue及其安全风险当你将shellTrue传递给任何subprocess函数时命令将通过系统 shell 执行在 Unix 上为/bin/sh在 Windows 上为cmd.exe。这允许你使用 shell 特性如管道、通配符和环境变量展开import subprocess result subprocess.run(echo $HOME, shellTrue, capture_outputTrue, textTrue) print(result.stdout.strip())这会打印你的主目录因为 shell 解释了$HOME。shellTrue的安全问题将shellTrue与不受信任的输入一起使用会产生命令注入漏洞。请考虑这种危险模式import subprocess user_input hello; rm -rf /tmp/testdir subprocess.run(fecho {user_input}, shellTrue)用户输入中的分号会导致 shell 执行第二个意外命令。这是在接受用户提供数据的系统中一个众所周知的攻击向量。shellTrue的安全替代方案最安全的方法是将命令作为参数列表传递而不使用shellTrueimport subprocess user_input hello; rm -rf /tmp/testdir result subprocess.run([echo, user_input], capture_outputTrue, textTrue) print(result.stdout.strip())输出hello; rm -rf /tmp/testdir在这里整个字符串被视为echo的单个参数。shell 永远不会解释分号。如果你需要安全地将命令字符串拆分为参数列表请使用shlex.split()import shlex cmd grep -r search term /var/log args shlex.split(cmd) print(args)输出[grep, -r, search term, /var/log]shlex.split()函数正确处理引号字符串和转义字符生成一个可以直接传递给subprocess.run()的列表。何时可以使用shellTrue只有在明确需要 shell 特性如管道或通配符展开且完全控制命令字符串而没有用户输入时才使用它。使用 subprocess.Popen 进行高级进程控制subprocess.Popen让你对子进程进行直接控制。与subprocess.run()不同后者等待命令完成后再返回Popen会启动进程并让你在它运行时与之交互。使用communicate()的基本Popen用法.communicate()方法向进程发送输入并等待其完成返回一个(stdout, stderr)元组import subprocess process subprocess.Popen( [echo, hello from Popen], stdoutsubprocess.PIPE, stderrsubprocess.PIPE, textTrue ) stdout, stderr process.communicate() print(stdout:, stdout.strip()) print(returncode:, process.returncode)输出stdout: hello from Popen returncode: 0轮询长时间运行的process使用.poll()检查进程是否仍在运行而不会阻塞import subprocess import time process subprocess.Popen([sleep, 3]) while process.poll() is None: print(Process is still running...) time.sleep(1) print(fProcess finished with return code: {process.returncode})输出Process is still running... Process is still running... Process is still running... Process finished with return code: 0进程间管道Popen让你可以将命令串联起来复制 shell 管道import subprocess p1 subprocess.Popen([echo, apple\nbanana\ncherry], stdoutsubprocess.PIPE) p2 subprocess.Popen([grep, banana], stdinp1.stdout, stdoutsubprocess.PIPE, textTrue) p1.stdout.close() output, _ p2.communicate() print(Filtered output:, output.strip())输出Filtered output: banana这种方法比使用shellTrue和管道字符更安全因为每个命令都在自己的进程中运行不经过 shell 解释。Popen与run()的使用时机大多数情况下使用subprocess.run()运行命令并等待结果。当你需要以下功能时选择Popen实时流式传输进程的输出交互式向运行中的进程发送输入并行运行多个进程在进程间构建管道重定向stdout、stderr和stdinsubprocess模块通过stdout、stderr和stdin参数提供对标准流的具体控制。分别捕获outputimport subprocess result subprocess.run( [ls, /tmp, /nonexistent], stdoutsubprocess.PIPE, stderrsubprocess.PIPE, textTrue ) print(stdout:, result.stdout[:80]) print(stderr:, result.stderr.strip())这会将标准输出和标准错误分别捕获到不同的变量中让你可以独立处理成功输出和错误消息。将output重定向到文件import subprocess with open(output.txt, w) as f: subprocess.run([echo, writing to file], stdoutf)命令输出会直接写入output.txt而不是控制台。合并stdout和stderr要将标准错误合并到标准输出中使用stderrsubprocess.STDOUTimport subprocess result subprocess.run( [ls, /tmp, /nonexistent], stdoutsubprocess.PIPE, stderrsubprocess.STDOUT, textTrue ) print(combined output:, result.stdout)当你希望获得命令的所有输出的单一流时这很有用。向命令发送input使用input参数向命令的标准输入传递数据import subprocess result subprocess.run( [grep, world], inputhello\nworld\nfoo\n, capture_outputTrue, textTrue ) print(matched:, result.stdout.strip())输出matched: world有关使用os模块进行更复杂输入处理的说明请参阅 Python os 模块教程。处理return codes和错误每个外部命令都会返回一个退出码。退出码0表示成功任何非零值都表示错误。Python 提供了多种方式来检查和响应这些码。手动检查return codeimport subprocess result subprocess.run([ls, /nonexistent], capture_outputTrue, textTrue) if result.returncode ! 0: print(f命令执行失败退出码 {result.returncode}) print(f错误: {result.stderr.strip()}) else: print(成功:, result.stdout)使用checkTrue自动抛出错误如前所述checkTrue会在非零退出码时抛出subprocess.CalledProcessError。以下是一个包含适当异常处理的完整示例import subprocess try: result subprocess.run( [python3, -c, import sys; sys.exit(42)], capture_outputTrue, textTrue, checkTrue ) except subprocess.CalledProcessError as e: print(f退出码: {e.returncode}) print(f命令: {e.cmd}) except FileNotFoundError: print(系统中未找到该命令) except subprocess.TimeoutExpired: print(命令执行时间过长)输出Return code: 42 Command: [python3, -c, import sys; sys.exit(42)]常见的exception类型及其修复方法异常发生时机修复方法subprocess.CalledProcessError命令返回非零退出码使用checkTrue通过e.stderr/e.output检查命令输出。仔细检查命令参数、文件路径和权限。修复通常涉及修正命令或输入数据。FileNotFoundError系统中不存在该可执行文件确保命令或可执行文件已安装并在系统的PATH中可用。使用which command或按需安装缺失的二进制文件。验证程序名称拼写。subprocess.TimeoutExpired命令超过指定的timeout值如果命令经常需要更长时间运行则增加timeout值或优化命令以更快完成。可选地在预期会超时的情况下处理清理/重试逻辑。PermissionError可执行文件存在但由于权限不足无法运行使用ls -lUnix 系统或文件属性Windows检查文件权限。使用chmod x file使文件可执行或在必要时以提升权限运行脚本例如使用sudo或在 Windows 上以管理员身份运行。预见并处理这些异常有助于使你的自动化脚本更加健壮特别是当脚本可能在不同的环境中运行或遇到缺失依赖时。有关有效的 Python 错误处理参见 Python 错误处理教程。对比表格os.system与subprocess方法特性os.system()subprocess.call()subprocess.run()subprocess.Popen()捕获 stdout否否是 (capture_outputTrue)是 (stdoutPIPE)捕获 stderr否否是 (capture_outputTrue)是 (stderrPIPE)访问退出码是 (返回值)是 (返回值)是 (result.returncode)是 (process.returncode)错误时抛出异常否否是 (checkTrue)否 (手动检查)支持超时否是 (timeout参数)是 (timeout参数)手动 (.wait(timeout))发送输入 (stdin)否否是 (input参数)是 (stdinPIPE)实时流式处理否否否是需要 shell是 (总是)可选可选可选推荐用于新代码否否是高级场景对于大多数任务subprocess.run()提供了简单性和控制性的最佳平衡。只有在需要与运行中的进程进行实时交互时才使用Popen。常见问题解答1. Python 中 os.system() 和 subprocess.run() 有什么区别os.system()将命令字符串传递给系统 shell仅返回退出码。它无法捕获命令的输出或错误消息。subprocess.run()Python 3.5 引入返回一个CompletedProcess对象其中包含返回码、stdout 和 stderr。它还支持超时、通过checkTrue自动抛出错误并且当传递参数列表时可以完全绕过 shell。Python 官方文档推荐使用subprocess.run()替代os.system()。2. Python 3 中 os.system() 是否已被弃用没有os.system()并未正式弃用。它在 Python 3.14 中仍然有效并返回命令的退出状态。然而Python 官方文档指出subprocess模块提供了“更强大的生成新进程的功能”并推荐使用subprocess.run()替代。3. 在 subprocess 中何时应该使用 shellTrue仅当需要 shell 特定功能如管道|、通配符*或环境变量展开$HOME并且完全控制命令字符串时才使用shellTrue。如果命令的任何部分来自用户输入绝不要使用shellTrue因为这会造成命令注入漏洞。更安全的做法是传递参数列表例如subprocess.run([ls, -la])完全绕过 shell。4. 如何在 Python 中捕获 shell 命令的输出使用subprocess.run()并设置capture_outputTrue和textTrueimport subprocess result subprocess.run([date], capture_outputTrue, textTrue) print(result.stdout.strip())这会将命令的标准输出作为字符串存储在result.stdout中。如果不使用textTrue输出将作为 bytes 对象返回。你也可以使用较旧的subprocess.check_output()函数它直接返回输出但在退出码非零时会抛出异常。5. subprocess 中非零返回码意味着什么非零返回码表示命令未成功完成。具体值取决于命令。例如grep在未找到匹配项时返回1ls在严重错误如无效选项时返回2。调用subprocess.run()后可以通过result.returncode检查返回码。如果传递checkTrue当返回码非零时 Python 会自动抛出subprocess.CalledProcessError从而简化错误处理。结论Python 的subprocess模块让你完全掌控运行系统命令从简单的单行命令到具有实时输出流式的复杂管道。对于新项目默认使用subprocess.run()。它在一个函数调用中处理输出捕获、错误检查和超时。将subprocess.Popen()保留用于需要与运行中进程交互的情况并在新代码中避免使用os.system()因为它缺少输出捕获和适当的错误处理。在使用外部命令时始终优先将参数作为列表传递而不是使用shellTrue以防止命令注入。将checkTrue与适当的异常处理结合使用可以编写出可预测失败并提供清晰错误消息的脚本。对于在自己的 Python 脚本中处理命令行参数或调试复杂程序社区还有更多基于这些概念的教程。后续步骤如果你正在构建运行远程服务器上 shell 命令的 Python 自动化脚本Droplets 提供了一种快速启动 Linux 环境用于测试和生产的方法。你可以部署一个预装 Python 的 Droplet并使用本教程中的技术来自动化服务器管理任务。

更多文章