Python实用工具:sh库入门到实战,轻松调用系统命令

一、sh库概述:用途、原理与特性

sh库是Python中一款轻量级的系统命令调用工具,能让开发者像调用Python函数一样执行Linux、macOS等系统的Shell命令,无需手动处理 subprocess 模块的复杂参数。其工作原理是通过动态生成函数映射系统命令,自动处理输入输出流、管道和返回码。优点是语法简洁、上手快,大幅简化命令调用代码;缺点是对Windows系统支持有限,部分复杂命令需额外适配。该库采用MIT许可证,允许自由使用、修改和分发。

二、sh库安装与基础使用

2.1 安装sh库

sh库支持Python 3.6及以上版本,安装方式简单,通过pip命令即可完成:

# 安装最新版本的sh库
pip install sh

如果需要安装特定版本,可指定版本号,例如安装1.14.3版本:

pip install sh==1.14.3

2.2 基础命令调用

sh库最核心的优势是“命令即函数”,无需额外封装,直接调用系统中已有的命令。

2.2.1 简单命令执行

ls(列出目录内容)和pwd(显示当前工作目录)命令为例:

import sh

# 执行ls命令,列出当前目录下的文件和文件夹
# 直接调用sh.ls(),返回结果为字符串
ls_result = sh.ls()
print("ls命令执行结果:")
print(ls_result)

# 执行pwd命令,获取当前工作目录
pwd_result = sh.pwd()
print("\n当前工作目录:")
print(pwd_result)

运行结果如下(因环境不同会有差异):

ls命令执行结果:
demo.py
test_folder
requirements.txt

当前工作目录:
/home/user/python_projects

2.2.2 带参数的命令执行

当命令需要参数时,直接在函数中传入参数即可,参数顺序与在Shell中一致。例如ls -l(详细列出目录内容)、mkdir new_folder(创建新文件夹):

import sh

# 执行ls -l命令,详细列出目录内容
ls_detail = sh.ls("-l")
print("ls -l命令执行结果:")
print(ls_detail)

# 执行mkdir命令,创建名为"sh_demo"的文件夹
# 若文件夹已存在,会抛出sh.ErrorReturnCode_1错误
try:
    sh.mkdir("sh_demo")
    print("\n文件夹sh_demo创建成功")
except sh.ErrorReturnCode as e:
    print(f"\n创建文件夹失败:{e}")

# 执行rmdir命令,删除名为"sh_demo"的文件夹
try:
    sh.rmdir("sh_demo")
    print("文件夹sh_demo删除成功")
except sh.ErrorReturnCode as e:
    print(f"删除文件夹失败:{e}")

运行结果中,ls -l会显示文件的权限、所有者、大小等详细信息,而文件夹的创建和删除操作会根据执行结果输出成功提示或错误信息。

三、sh库进阶用法:管道、重定向与交互

3.1 管道操作

在Shell中,管道(|)用于将前一个命令的输出作为后一个命令的输入。sh库通过函数链式调用实现管道功能,语法比subprocess更直观。例如ps aux | grep python(查看Python相关进程):

import sh

# 实现ps aux | grep python的管道操作
# 先执行sh.ps("aux"),再将结果传给sh.grep("python")
processes = sh.grep(sh.ps("aux"), "python")
print("Python相关进程:")
print(processes)

运行后会输出当前系统中所有包含“python”关键词的进程信息,格式与在Shell中执行该命令一致。

3.2 输入输出重定向

重定向用于将命令的输入/输出指向文件,sh库通过_in(标准输入)、_out(标准输出)、_err(标准错误)参数实现。

3.2.1 输出重定向到文件

ls -l的结果写入file_list.txt文件:

import sh

# 将ls -l的输出重定向到file_list.txt
# _out参数指定输出文件路径,若文件已存在会覆盖内容
sh.ls("-l", _out="file_list.txt")
print("ls -l结果已写入file_list.txt")

# 验证文件内容,读取file_list.txt并打印
with open("file_list.txt", "r") as f:
    content = f.read()
print("\nfile_list.txt内容:")
print(content)

3.2.2 从文件读取输入

grep命令为例,从file_list.txt中搜索包含“py”的行:

import sh

# 从file_list.txt中读取输入,搜索"py"关键词
# _in参数指定输入文件路径
grep_result = sh.grep("py", _in="file_list.txt")
print("file_list.txt中包含'py'的行:")
print(grep_result)

3.2.3 标准错误重定向

将命令的错误信息重定向到文件,例如执行不存在的命令invalid_cmd,将错误输出到error.log

import sh

# 执行不存在的命令,将错误输出重定向到error.log
try:
    sh.invalid_cmd(_err="error.log")
except sh.ErrorReturnCode as e:
    print("命令执行失败,错误信息已写入error.log")

# 读取错误日志
with open("error.log", "r") as f:
    error_content = f.read()
print("\nerror.log内容:")
print(error_content)

3.3 命令交互

对于需要动态输入的命令(如sudopasswd),sh库可通过_in参数传入多行输入,或使用stdin进行实时交互。以下以sudo ls /root为例,自动输入密码:

import sh

# 注意:实际使用中不建议硬编码密码,存在安全风险
password = "your_sudo_password\n"  # 换行符表示输入完成

# 执行sudo ls /root,通过_in传入密码
# -S参数表示sudo从标准输入读取密码
try:
    sudo_result = sh.sudo("-S", "ls", "/root", _in=password)
    print("/root目录内容:")
    print(sudo_result)
except sh.ErrorReturnCode as e:
    print(f"sudo执行失败:{e}")

四、实战案例:自动化文件备份脚本

结合sh库的核心功能,我们编写一个自动化文件备份脚本,实现以下功能:1. 遍历指定目录;2. 压缩目录内容为tar.gz格式;3. 将备份文件移动到指定备份目录;4. 记录备份日志;5. 清理7天前的旧备份。

4.1 脚本代码实现

import sh
import os
from datetime import datetime, timedelta

def file_backup(source_dir, backup_dir, log_file):
    """
    自动化文件备份函数
    :param source_dir: 待备份的源目录
    :param backup_dir: 备份文件存放目录
    :param log_file: 备份日志文件路径
    """
    # 1. 验证目录是否存在
    if not os.path.exists(source_dir):
        log_msg = f"[{datetime.now()}] 错误:源目录{source_dir}不存在\n"
        print(log_msg.strip())
        with open(log_file, "a") as f:
            f.write(log_msg)
        return

    if not os.path.exists(backup_dir):
        # 创建备份目录
        sh.mkdir("-p", backup_dir)  # -p确保父目录不存在时也能创建
        log_msg = f"[{datetime.now()}] 备份目录{backup_dir}不存在,已自动创建\n"
        print(log_msg.strip())
        with open(log_file, "a") as f:
            f.write(log_msg)

    # 2. 生成备份文件名(包含时间戳,避免重复)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_filename = f"backup_{timestamp}.tar.gz"
    backup_path = os.path.join(backup_dir, backup_filename)

    # 3. 压缩源目录内容
    log_msg = f"[{datetime.now()}] 开始备份{source_dir}到{backup_path}\n"
    print(log_msg.strip())
    with open(log_file, "a") as f:
        f.write(log_msg)

    try:
        # 执行tar命令压缩:tar -czf 备份文件 源目录
        sh.tar("-czf", backup_path, source_dir)
        log_msg = f"[{datetime.now()}] 备份成功,备份文件:{backup_path}\n"
        print(log_msg.strip())
    except sh.ErrorReturnCode as e:
        log_msg = f"[{datetime.now()}] 备份失败:{e}\n"
        print(log_msg.strip())
        with open(log_file, "a") as f:
            f.write(log_msg)
        return

    # 4. 记录备份文件大小
    # 执行du -h命令获取文件大小
    file_size = sh.du("-h", backup_path).split()[0]
    log_msg = f"[{datetime.now()}] 备份文件大小:{file_size}\n"
    print(log_msg.strip())

    # 5. 清理7天前的旧备份
    seven_days_ago = datetime.now() - timedelta(days=7)
    for file in os.listdir(backup_dir):
        file_path = os.path.join(backup_dir, file)
        if file.startswith("backup_") and file.endswith(".tar.gz"):
            # 提取文件名中的时间戳
            try:
                file_timestamp = datetime.strptime(file.split("_")[1].split(".")[0], "%Y%m%d_%H%M%S")
                if file_timestamp < seven_days_ago:
                    # 删除旧备份
                    sh.rm(file_path)
                    log_msg = f"[{datetime.now()}] 已清理7天前的旧备份:{file_path}\n"
                    print(log_msg.strip())
            except ValueError:
                # 文件名格式不符合时跳过
                continue

    # 6. 写入完整日志
    with open(log_file, "a") as f:
        f.write(log_msg)

# 脚本执行入口
if __name__ == "__main__":
    # 配置参数(根据实际需求修改)
    SOURCE_DIR = "/home/user/documents"  # 待备份的目录
    BACKUP_DIR = "/home/user/backups"    # 备份存放目录
    LOG_FILE = "/home/user/backup_log.txt"  # 日志文件路径

    # 执行备份
    file_backup(SOURCE_DIR, BACKUP_DIR, LOG_FILE)

4.2 脚本说明与运行

  1. 参数配置:脚本开头的SOURCE_DIRBACKUP_DIRLOG_FILE需根据实际环境修改,分别指定待备份目录、备份存放目录和日志文件路径。
  2. 核心功能实现
  • 目录验证与创建:通过os.path.exists判断目录是否存在,使用sh.mkdir("-p")创建多级目录。
  • 备份压缩:调用sh.tar("-czf", ...)实现目录压缩,生成带时间戳的备份文件,避免文件名重复。
  • 日志记录:实时将备份过程写入日志文件,便于后续排查问题。
  • 旧备份清理:通过datetime计算7天前的时间,遍历备份目录删除过期文件。
  1. 运行方式:在终端中执行以下命令:
python backup_script.py
  1. 运行效果:执行后会输出备份过程日志,同时在BACKUP_DIR中生成backup_20240520_153000.tar.gz格式的备份文件,LOG_FILE中会记录完整的操作历史。

五、相关资源

  • Pypi地址:https://pypi.org/project/sh/
  • Github地址:https://github.com/amoffat/sh
  • 官方文档地址:https://amoffat.github.io/sh/

关注我,每天分享一个实用的Python自动化工具。