杰瑞科技汇

Python如何调用shell脚本?

Python调用Shell脚本全攻略:5种实战方法与最佳实践(新手必看)

Meta描述:

本文详细讲解Python中调用Shell脚本的5种主流方法(os.system, subprocess, commands等),从基础到进阶,附带完整代码示例与错误处理,无论你是Python新手还是资深开发者,都能在这里找到最适合你的Shell调用方案,并掌握最佳实践,提升开发效率。

Python如何调用shell脚本?-图1
(图片来源网络,侵删)

引言:为什么Python需要调用Shell脚本?

在自动化运维、数据处理和系统管理等领域,Python以其简洁的语法和强大的库生态备受青睐,许多成熟的系统工具、性能优化的命令行程序或复杂的批处理任务,早已以Shell脚本(Bash/Zsh等)的形式存在,如何在Python中优雅地调用这些Shell脚本,实现两种语言的优势互补,就成了开发者必备的核心技能。

本文将为你系统梳理Python调用Shell脚本的多种方法,分析各自的优劣与适用场景,并给出经过实战检验的最佳实践,读完本文,你将能够根据具体需求,选择最合适的方案,并编写出健壮、高效的混合程序。


核心方法一:os.system() - 最简单直接的方式

os.system()是Python调用外部命令最古老、最简单的方法,它会在子shell中执行命令,并返回命令的退出状态码。

代码示例:

Python如何调用shell脚本?-图2
(图片来源网络,侵删)
import os
# 执行一个简单的Shell命令
return_code = os.system("ls -l")
print(f"命令退出状态码: {return_code}")
# 执行一个Shell脚本
os.system("./my_script.sh")

优点:

  • 简单易用:语法直观,一行代码即可完成调用。
  • 兼容性好:在所有Python版本中都可用。

缺点:

  • 功能有限:无法获取命令的输出结果(除非重定向到文件再读取)。
  • 安全性问题:如果命令参数来自用户输入,容易引发命令注入攻击(Command Injection)。
  • 阻塞式:会等待命令执行完毕才返回,无法进行交互式操作。

适用场景:

  • 只需要执行命令,不关心其输出。
  • 快速原型验证或简单的自动化任务。

核心方法二:subprocess模块 - 现代化、功能强大的首选

从Python 2.4开始引入的subprocess模块,是官方推荐用于创建和管理子进程的模块,它功能全面,灵活性强,是现代Python开发中的首选方案。

1 subprocess.run() - Python 3.5+ 推荐用法

subprocess.run()subprocess模块的高级API,封装了大部分常用功能。

代码示例:

import subprocess
# 执行命令并获取输出
try:
    # 捕获标准输出和标准错误
    result = subprocess.run(["ls", "-l"], check=True, capture_output=True, text=True, encoding='utf-8')
    print("命令执行成功!")
    print("标准输出:")
    print(result.stdout)
except subprocess.CalledProcessError as e:
    print(f"命令执行失败,返回码: {e.returncode}")
    print("标准错误:")
    print(e.stderr)

参数解析:

  • ["ls", "-l"]:推荐将命令和参数作为列表传递,可以避免命令注入风险。
  • check=True:如果命令返回非零退出码(表示失败),则抛出CalledProcessError异常。
  • capture_output=True:捕获标准输出和标准错误。
  • text=Trueuniversal_newlines=True:将输出解码为文本(字符串),否则为字节流。
  • encoding='utf-8':明确指定编码,防止乱码。

2 subprocess.Popen() - 底层灵活控制

Popensubprocess的核心类,提供了更底层的控制,适合需要与子进程进行复杂交互的场景。

代码示例:

import subprocess
# 创建一个子进程
process = subprocess.Popen(["ping", "-c", "4", "www.baidu.com"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 获取输出
stdout, stderr = process.communicate()
print("标准输出:")
print(stdout)
if process.returncode != 0:
    print("标准错误:")
    print(stderr)

适用场景:

  • 需要长时间运行的命令。
  • 需要实时读取命令的输出流。
  • 需要与子进程进行双向通信(如stdin交互)。

核心方法三:使用 && 或 连接命令

在某些情况下,你可能需要像在Shell中一样,用逻辑运算符连接多个命令。subprocess.run()同样可以轻松实现。

代码示例:

import subprocess
# 使用 && 连接命令,前一个成功才执行后一个
subprocess.run("ls -l && echo 'List successful'", shell=True, check=True)
# 使用 ; 连接命令,无论前一个是否成功都执行后一个
subprocess.run("ls -nonexistent-file; echo 'This will always be printed'", shell=True)

注意: 当使用shell=True时,命令以字符串形式传入,这会带来命令注入的风险,请确保命令来源可信,或对输入进行严格的过滤和转义。


核心方法四:通过文件传递复杂数据

如果Shell脚本的输出非常复杂(如JSON、多行文本),或者需要传递大量数据,通过临时文件进行交互是一个稳定可靠的方法。

Python代码 (caller.py):

import subprocess
import json
import os
# 准备输入数据
input_data = {"name": "Python", "version": "3.9", "features": ["Simple", "Powerful"]}
input_file = "input.json"
# 将数据写入临时文件
with open(input_file, 'w') as f:
    json.dump(input_data, f)
# 调用Shell脚本,并传入输入文件路径
subprocess.run(["./process_data.sh", input_file], check=True)
# 读取Shell脚本的输出文件
output_file = "output.txt"
if os.path.exists(output_file):
    with open(output_file, 'r') as f:
        print("Shell脚本处理结果:")
        print(f.read())
# 清理临时文件
os.remove(input_file)

Shell脚本 (process_data.sh):

#!/bin/bash
# 从参数获取输入文件路径
INPUT_FILE=$1
OUTPUT_FILE="output.txt"
# 检查文件是否存在
if [ ! -f "$INPUT_FILE" ]; then
    echo "Error: Input file not found!" >&2
    exit 1
fi
# 模拟处理:读取JSON,提取name并写入输出文件
NAME=$(jq -r '.name' "$INPUT_FILE")
echo "Processing data for: $NAME" > "$OUTPUT_FILE"
echo "Data processed successfully at $(date)" >> "$OUTPUT_FILE"
exit 0

优点:

  • 解耦性好:Python和Shell脚本通过文件交互,逻辑清晰,易于维护。
  • 数据量大:适合处理大型数据集,避免内存溢出。
  • 稳定可靠:不依赖于复杂的命令行参数解析。

核心方法五:环境变量交互

Python和Shell脚本可以通过环境变量共享简单的配置信息。

Python代码:

import os
import subprocess
# 设置环境变量
os.environ["MY_APP_CONFIG"] = "production_mode"
# 调用Shell脚本
subprocess.run("./read_env.sh", shell=True)

Shell脚本 (read_env.sh):

#!/bin/bash
echo "Hello from Shell script!"
echo "The value of MY_APP_CONFIG is: $MY_APP_CONFIG"

适用场景:

  • 传递简单的配置项,如数据库地址、API密钥等。
  • 通知Shell脚本当前运行的环境(开发/测试/生产)。

最佳实践与注意事项

  1. 首选 subprocess.run():对于大多数新项目,优先使用Python 3.5+的subprocess.run(),它简洁、安全且功能强大。
  2. 警惕命令注入:永远不要直接拼接用户输入来构造命令,如果必须使用shell=True,务必对输入进行白名单验证或使用shlex.quote()进行转义。
  3. 妥善处理编码:明确指定encoding参数(如'utf-8'),以避免在不同系统上出现乱码问题。
  4. 善用异常处理:使用try...except块捕获subprocess.CalledProcessError等异常,使你的程序更加健壮。
  5. 关注资源释放:使用Popen时,如果不需要与子进程持续交互,记得调用process.communicate()process.wait()来回收资源,避免僵尸进程。
  6. 日志记录:记录你执行的命令及其输出,这对于调试和审计至关重要。

方法 优点 缺点 推荐度
os.system() 极其简单 功能弱,不安全,已不推荐
subprocess.run() 功能强大,安全,API现代 相对os.system稍复杂 ⭐⭐⭐⭐⭐ (首选)
subprocess.Popen() 灵活,底层控制 使用复杂,需手动管理资源 ⭐⭐⭐⭐ (特定场景)
文件交互 稳定,适合大数据 需要额外I/O操作 ⭐⭐⭐⭐ (复杂场景)
环境变量 简单,适合配置 仅限简单字符串 ⭐⭐⭐ (特定用途)

Python与Shell脚本的结合是提升自动化能力的利器,掌握subprocess模块,特别是subprocess.run(),是每个Python开发者的必修课,希望本文能为你提供清晰的指引和实用的代码,助你在实际项目中游刃有余地实现Python与Shell的无缝协作。


(可选)文末互动/CTA

如果你有其他Python调用Shell的酷炫技巧或遇到了棘手的问题,欢迎在评论区留言分享,我们一起交流探讨!别忘了点赞收藏本文,以便随时查阅,关注我们,获取更多前沿的编程干货!

分享:
扫描分享到社交APP
上一篇
下一篇