如何 通过 Python 来调用 Shell 脚本
本文介绍三种写法
- 使用
os.system
来运行 - 使用
subprocess.run
来运行 - 使用
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 命令的执行的stdout
和stderr
当成字符串. 默认是当成 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、执行命令,并对输出进行处理
这里以执行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 命令的执行的stdout
和stderr
当成字符串. 默认是当成 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()
等待完成