Python执行系统命令的几种方法

问题引出

假定面临一个需求,需要在Python程序中调用系统命令/其它可执行程序完成一些事项(下载文件) ,那么有哪些方法可以实现需求?一个示例的 代码结构如下:

class CMDExecutor:
    def __init__(self, executable_path):
        self.executable = executable_path

    def execute(self, params=None):
        """
        执行系统命令
        :param command:
        :return:
        """
        if paramsis None:
            params= self.__params
        # TODO: 执行系统命令
        pass

方法一:通过os.system执行系统命令

根据官方文档对os.system的介绍,

    os.system()通过调用标准 C 函数 system() 来实现在子进程中执行命令(字符串)。
对 sys.stdin 的更改等不会反映在所执行命令的环境中。 
如果 command 生成了任何输出,它将被发送到解释器的标准输出流。 

    C 标准没有指明这个 C 函数返回值的含义,因此这个 Python 函数的返回值取决于具体系统。
在 Unix 上,返回值为进程的退出状态,以针对 wait() 而指定的格式进行编码。
在 Windows 上,返回值是运行 command 后系统 Shell 返回的值。该 Shell 由 Windows
 环境变量 COMSPEC: 给出:通常是 cmd.exe,它会返回命令的退出状态。

具体操作方式示例1:

os.system(" ".join([self.executable, params]))

效果:在命令正确的情况下,os.system会启动一个子进程,并阻塞父进程,直到子进程返回结果。

具体操作方式示例2:如果想要避免主进程被os.system阻塞,可以使用windows的start命令开启一个cmd窗口,代码如下 :

result = os.system(" ".join(["start", self.executable, params]))

但方式2也有问题,如果命令错误(如通过下载器下载文件,指定的url有误),则cmd窗口会挂起(空操作),要实现完整的程序,需要考虑跟踪该cmd子进程并杀死空操作的cmd进程。

方法二:os.popen

新建一个子进程,在子进程中执行命令,并创建一个管道用于父进程和子进程之间的通信。父进程可以从管道读消息或者向管道写消息。

与os.system的明显不同是popen方便父子进程交互,默认不阻塞父进程,除非父进程要从 子进程中读信息

p= os.popen(" ".join([self.executable, command]))
print(p.name)
p.read() # 会阻塞父进程

效果简介:非阻塞方式启用子进程执行命令

方法三:subprocess.Popen()

根据官方文档描述,subprocess 模块允许你生成新的进程,连接它们的输入、输出、错误管道,并且获取它们的返回码。此模块打算代替一些老旧的模块与功能:

os.system
os.spawn*

就功能而言,subprocess.Popen模块允许我们更方便地跟踪子进程的状态

具体使用方式示例 :

p = subprocess.Popen(" ".join([self.executable, command]), shell=False, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL)

p的常用属性和方法包括:

pid: 子进程PID。需要注意如果使用Popen构造函数时将shell参数设置为True,则会启用一个系统终端,默认返回的子进程id是该终端进程的id,而不是在该终端上所执行命令启用的子进程id。

kill(): 杀死子进程。

因此若要杀死通过Popen创建的子进程,只需要调用p.kill()或者调用os模块下的方法,把进程id和信号作为参数传递给kill方法即可。

os.kill(p.pid, signal.SIGTERM)

备注:SIGTERM和SIGKILL都是终止进程的信号,前者较为温和(可能 被进程捕获并 忽略),后者是强制终止信号,会杀死子进程,拒绝抵抗。

参考阅读

  • 调试时遇到一些问题:调用os.killpg(p.pid, signal.SIGUSR1)

报错:AttributeError: module ‘signal’ has no attribute ‘SIGUSR1’

原因:参考issue

"windows doesn't have the same SIGUSR signals. After a quick search, the suggestion
 from other sources was to revert to using SIGTERM when running on windows."

参考文档

CoolCats
CoolCats
理学学士

我的研究兴趣是时空数据分析、知识图谱、自然语言处理与服务端开发