通过 Python 来调用 Shell 脚本的三种常用方式

如何 通过 Python 来调用 Shell 脚本

本文介绍三种写法

  1. 使用os.system 来运行
  2. 使用subprocess.run 来运行
  3. 使用 subprocess.Popen 来运行

三种方式的优缺点

os.system subprocess.run subprocess.Popen
是否需要解析参数 no yes yes
同步执行(等待Shell执行结果) yes yes no
能够获得 shell 的输入和输出 no yes yes
Shell 执行结果返回值 return value object object

通过 os.system 运行

import os

return_code = os.system('ls -al .')
print("return code:", return_code)

也会将Shell语句的输出输出到的 Python的命令控制台中,但是 Python 的能够获取的返回值,是数字,0 代表 Shell 语句/脚本的执行成功,否则表示Shell执行的状态值。
适用于不需要详细的返回信息的 shell 脚本
传入 Shell 命令的参数,都是完整的 String。

➜  tmp python test.py
total 8
drwxr-xr-x    3 jinghui.zhang  staff    96 Nov  8 12:29 .
drwxr-xr-x+ 106 jinghui.zhang  staff  3392 Nov  8 12:29 ..
-rwxr-xr-x@   1 jinghui.zhang  staff    82 Nov  8 12:29 test.py
('return code:', 0)

通过 subprocess 运行

通常推荐使用subprocess 模块来执行 shell 命令。
能够相当方便的控制 shell 命令的输入和输出
不同于 os.system 传入的参数是一个数组而不是字符串。

import subprocess

return_code = subprocess.run(["ls", "-al", "."])
print("return code:", return_code)

执行返回的结果是一个CompletedProcess对象,返回结果如下:

➜  tmp python test.py                                                                                                        
total 8
drwxr-xr-x    3 jinghui.zhang  staff    96 Nov  8 12:34 .
drwxr-xr-x+ 106 jinghui.zhang  staff  3392 Nov  8 12:36 ..
-rwxr-xr-x@   1 jinghui.zhang  staff   103 Nov  8 12:33 test.py
return code: CompletedProcess(args=['ls', '-al', '.'], returncode=0)

如果我们不想它在控制台中,输出 shell 脚本执行的结果,则可以指定subprocess 的 stdout,将其忽略。
return_code = subprocess.run(['ls', '-al', '.'], stdout=subprocess.DEVNULL),则输出结果就仅有 Python 程序运行出的结果。

➜  tmp python test.py  
return code: CompletedProcess(args=['ls', '-al', '.'], returncode=0)
12

如果你想指定 shell 命令的输入参数,可以通过 input 参数来传入。

import subprocess

useless_cat_call = subprocess.run(
["cat"], stdout=subprocess.PIPE, text=True, input="Hello from the other side")
print(useless_cat_call.stdout) # Hello from the other side

上面的 subprocess.run命令中:

  • stdout=subprocess.PIPE 告诉 Python,重定向 Shell 命令的输出,并且这部分输出可以被后面的 Python 程序所调用。
  • text=True 告诉 Python,将 shell 命令的执行的 stdoutstderr 当成字符串. 默认是当成 bytes.3.8版本之后才有的
  • input="Hello from the other side" 将字节或字符串传递给子进程的标准输入。

上面程序运行的结果是

Hello from the other side

我们可以开启 shell 命令的执行校验,当 shell 的执行出现任何问题,都会抛出一个异常。

import subprocess

failed_command = subprocess.run(["false"], check=True)
print("The exit code was: %d" % failed_command.returncode)

返回结果

$ python3 false_subprocess.py
Traceback (most recent call last):
  File "false_subprocess.py", line 4, in <module>
    failed_command = subprocess.run(["false"], check=True)
  File "/usr/local/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 512, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['false']' returned non-zero exit status 1.

通过 subprocess.Popen 运行

subprocess.run 可以看成是 subprocess.Popen 一个简化抽象。subprocess.Popen 能够提供更大的灵活性。

默认情况下,subprocess.Popen 不会中暂停 Python 程序本身的运行(异步)。

但是如果你非得同前面两种方式一样,同步运行,你可以加上 .wait() 方法。

import subprocess

list_dir = subprocess.Popen(["ls", "-l"])
list_dir.wait()

当我们仍然处在异步的状况,Python 程序,怎么知道shell 命令是否运行结束与否?可以通过 poll() 来轮询,当放回结果为 None,则表示程序还在运行,否者会返回一个状态码。

list_dir.poll()

如果想,指定输入参数,则需要通过 communicate() 方法。

import subprocess

useless_cat_call = subprocess.Popen(["cat"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, errors = useless_cat_call.communicate(input="Hello from the other side!")
useless_cat_call.wait()
print(output)
print(errors)

.communicate 方法还会返回标准输入和标准输出,更多细节详见官网 https://docs.python.org/3/library/subprocess.html。

参考文档


  1. Executing Shell Commands with Python

实践

1、执行命令,并对输出进行处理

这里以执行systemctl status docker命令为例

import subprocess

result = subprocess.run(["systemctl", 'status', 'docker'], stdout=subprocess.PIPE, text=True)
s=result.stdout.split("\n")
for i in s:
    if i.strip():
        print(i.strip(),'========')

使用run可以执行命令

  • stdout=subprocess.PIPE 告诉 Python,重定向 Shell 命令的输出,并且这部分输出可以被后面的 Python 程序所调用。
  • text=True 告诉 Python,将 shell 命令的执行的 stdoutstderr 当成字符串. 默认是当成 bytes.3.8版本之后才有的
  • ["systemctl", 'status', 'docker']命令部分需要拆成列表或元组

输出的字符串保存在stdout方法中,通过回车换行符来切割,可以定位到行

2、使用管道符

直接在命令列表中使用管道符是无法正常执行的

例如

import subprocess

result = subprocess.run(["systemctl", 'status', 'docker','|','grep','loaded'], stdout=subprocess.PIPE, text=True)

直接添加时无法使用的,需要使用另外的异步的方法执行

import subprocess

result = subprocess.Popen(["systemctl", 'status', 'docker'], stdout=subprocess.PIPE, text=True)
s = subprocess.check_output(['grep', 'loaded'], stdin=result.stdout, text=True)
result.wait()
print(s)

先执行前面的命令,将输出通过输入传给管道符后面的命令,以为是异步的,所以,需要加上wait()等待完成

posted @ 2023-01-17 21:57  厚礼蝎  阅读(2485)  评论(0编辑  收藏  举报