Python 执行系统命令

本文最后更新于:2022年7月5日 下午

Python 编程过程中经常会用到系统命令,本文记录实现方法。

系统命令

  • 作为胶水语言,Python可以很方便的执行系统命令,Python3中常用的执行操作系统命令有以下方式
  1. os.system()
  2. os.popen()
  3. subprocess 模块

os.system

  • 执行命令但无法获取取命令输出时,可以使用 os.system

  • os.system() 是C语言 system() 函数的封装,返回命令的退出状态码,命令执行结果输出到标准输出(stdout/窗口)。

  • system函数可以将字符串转化成命令在服务器上运行;其原理是每一条system函数执行时,其会创建一个子进程在系统上执行命令行,子进程的执行结果无法影响主进程。

  • 其返回值是指令运行后返回的状态码,0表示指令成功执行,1表示失败,256表示没有返回结果,该方法适用于系统命令不需要输出内容的场景。

  • 用法:

1
os.system("command")
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os

command = "ifconfig"
exit_code = os.system(command)
# 执行 sh 脚本
os.system('sh /root/script/test,sh')

import os
a=os.system("ping 192.168.1.101") #使用a接收返回值
print(a)

# 理论上command是一个字符串,但实际看command还是得变为字节数组
# 当命令中存在中文时可能会报编码错误,此时可以自己给命令编一下码
# exit_code = os.system(command.encode("gbk"))

os.popen

  • 这种调用方式是通过管道的方式来实现,这个函数的返回值是一个文件对象,可以读或者写(由mode决定,mode默认是’r’)。如果mode为’r’,调用该对象的 read()readlines() 方法可以读取输出内容。

  • 用法:

1
os.popen(command[, mode[, bufsize]]) 
  • os.system(cmd)os.popen(cmd),前者返回值是脚本的退出状态码,后者的返回值是脚本执行过程中的输出内容。
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os

command = "ifconfig"
command_output = os.popen(command).readlines()
print(command_output)

a=os.popen("ipconfig")
print(a.read())

>>>

以太网适配器 以太网:

连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe81::b0ed:2b1b:7385:97d1%8
IPv4 地址 . . . . . . . . . . . . : 192.168.1.100
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.1.1

subprocess

  • subprocess模块是python从2.4版本开始引入的模块,主要用来取代 一些旧的模块方法,如os.system、os.spawn、os.popen、commands.*等。官方推荐使用该模块执行系统命令,subprocess模块通过子进程来执行外部指令,并通过input/output/error管道,获取子进程的执行的返回信息。

    好处在于:运用对线程的控制和监控,将返回的结果赋于一变量,便于程序的处理。

subprocess.Popen()

使用Popen可以创建进程,并与进程进行复杂的交互。

  • 用法:
1
child = subprocess.Popen(["cmd","arg1"...])
  • 在一些复杂场景中,我们需要将一个进程的执行输出作为另一个进程的输入。在另一些场景中,我们需要先进入到某个输入环境,然后再执行一系列的指令等。这个时候我们就需要使用到suprocess的Popen()方法。该方法有以下参数
参数 作用
args 一般是一个字符串,是要执行的shell命令内容
bufsize 设置缓冲,负数表示系统默认缓冲,0表示无缓冲,正数表示自定义缓冲行数
stdin 程序的标准输入句柄,NONE表示不进行重定向,继承父进程,PIPE表示创建管道
stdout 程序的标准输出句柄,参数意义同上
stderr 程序的标准错误句柄,参数意义同上,特殊,可以设置成STDOUT,表示与标准输出一致
shell 为True时,表示将通过shell来执行
cwd 用来设置当前子进程的目录
env 用于指定子进程的环境变量。如果env=None,则默认从父进程继承环境变量
universal_newlines 不同系统的的换行符不同,当该参数设定为true时,则表示使用\n作为换行符。
  • Popen方法:
    • 1、Popen.poll():用于检查子进程是否已经结束。设置并返回returncode属性。
    • 2、Popen.wait():等待子进程结束。设置并返回returncode属性。
    • 3、Popen.communicate(input=None):与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。Communicate()返回一个元组:(stdoutdata, stderrdata)。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。
    • 4、Popen.send_signal(signal):向子进程发送信号。
    • 5、Popen.terminate():停止(stop)子进程。在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。
    • 6、Popen.kill():杀死子进程。
    • 7、Popen.stdin:如果在创建Popen对象是,参数stdin被设置为PIPE,Popen.stdin将返回一个文件对象用于策子进程发送指令。否则返回None。
    • 8、Popen.stdout:如果在创建Popen对象是,参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象用于策子进程发送指令。否则返回None。
    • 9、Popen.stderr:如果在创建Popen对象是,参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象用于策子进程发送指令。否则返回None。
    • 10、Popen.pid:获取子进程的进程ID。
    • 11、Popen.returncode:获取进程的返回值。如果进程还没有结束,返回None。
    • 12、subprocess.call(*popenargs, **kwargs):运行命令。该函数将一直等待到子进程运行结束,并返回进程的returncode。文章一开始的例子就演示了call函数。如果子进程不需要进行交互,就可以使用该函数来创建。
    • 13、subprocess.check_call(*popenargs, **kwargs):与subprocess.call(*popenargs, **kwargs)功能一样,只是如果子进程返回的returncode不为0的话,将触发CalledProcessError异常。在异常对象中,包括进程的returncode信息
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from subprocess import PIPE, Popen

# 返回的是 Popen 实例对象
proc = Popen(
'ipconfig', # cmd特定的查询空间的命令
stdin=None, # 标准输入 键盘
stdout=PIPE, # -1 标准输出(演示器、终端) 保存到管道中以便进行操作
stderr=PIPE, # 标准错误,保存到管道
shell=True)

# print(proc.communicate()) # 标准输出的字符串+标准错误的字符串
outinfo, errinfo = proc.communicate()
print(outinfo.decode('gbk')) # 外部程序(windows系统)决定编码格式
print(errinfo.decode('gbk'))

>>>
以太网适配器 以太网:

连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe81::b0ed:2b1b:7385:97d1%8
IPv4 地址 . . . . . . . . . . . . : 192.168.1.100
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.1.1


Process finished with exit code 0
注意
  • 在shell=True这个参数,不写的时候默认是False,shell默认为/bin/sh。如果 args是一个字符串,则该字符串指定要通过shell执行的命令。
  • 当需要设置shell=True时(当False时,arges是列表,第一个参数是shell命令,后面的都是参数’,’ 隔开),须把args设为string,空格隔开,如下
1
2
>>> a = subprocess.Popen(['tail','-f', '/apps/apache-tomcat-8.5.29/logs/catalina.out'])
>>> a = subprocess.Popen('tail -f /apps/apache-tomcat-8.5.29/logs/catalina.out',shell=True)

subprocess.call()

  • 执行指定的命令, 返回命令执行状态, 功能类似os.system(cmd),参数shell默认为False

  • 用法:

1
subprocess.call("command")
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
# linux获取磁盘空间
import subprocess
subprocess.call(['df', '-h']) # 数组作为参数运行命令

输出:
Filesystem Size Used Avail Use% Mounted on
devtmpfs 909M 0 909M 0% /dev
tmpfs 920M 32K 920M 1% /dev/shm
tmpfs 920M 472K 919M 1% /run
tmpfs 920M 0 920M 0% /sys/fs/cgroup
/dev/vda1 50G 6.2G 41G 14% /
tmpfs 184M 0 184M 0% /run/user/0

subprocess.run()

  • python3.5中新增的函数, 执行指定的命令, 等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。run默认不会返回输出,只返回命令和执行状态。

  • 用法:

1
2
3
4
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, universal_newlines=False)
import subprocess
completed = subprocess.run(['ls', '-1'])
print('returncode:', completed.returncode)

subprocess.getstatusoutput()

  • 执行cmd命令,返回一个元组(命令执行状态,命令执行结果输出)。

  • 返回状态码和结果,0表示成功

  • 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import subprocess
ret, val = subprocess.getstatusoutput("ping www.baidu.com")
print(ret)
print(val)
0

正在 Ping www.a.shifen.com [14.215.177.39] 具有 32 字节的数据:
来自 14.215.177.39 的回复: 字节=32 时间=29ms TTL=53
来自 14.215.177.39 的回复: 字节=32 时间=29ms TTL=53
来自 14.215.177.39 的回复: 字节=32 时间=29ms TTL=53
来自 14.215.177.39 的回复: 字节=32 时间=30ms TTL=53

14.215.177.39 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 29ms,最长 = 30ms,平均 = 29ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import subprocess

command = "ifconfig"
# 命令如果带参数则不能直接使用字符串的形式,不然后报FileNotFoundError: [Errno 2] No such file or directory: 'ifconfig -a'等错误
# 此时有两种处理办法,一种是写成以空格为分割的列表形式。可借助shlex.split(command_line)实现自动分割
# command = ["ifconfig", "-a"]
# 别一种是将shell参数置为True
# completed_process_obj = subprocess.run(command, shell=True)
# 当配置了stdout参数,completed_process_obj的stdout属性就会保存命令的输出
completed_process_obj = subprocess.run(command, stdout=subprocess.PIPE)
print(completed_process_obj.stdout.decode())
# subprocess.Popen()基础参数和run()差不多,比run()对命令进程有更强的控制能力。其也能用来获取命令输出
# popen_obj = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
# print(popen_obj.stdout.readlines())
# 其他如subprocess.call()、subprocess.check_call()、subprocess.check_output()都是旧方法了,Python3.5+都建议用run()

subprocess.check_call()

  • 与call方法类似,不同在于如果命令行执行成功,check_call返回返回码0,否则抛出subprocess.CalledProcessError异常。
    subprocess.CalledProcessError异常包括returncode、cmd、output等属性,其中returncode是子进程的退出码,cmd是子进程的执行命令,output为None。

  • 用法

1
subprocess.check_call(args, *, stdin = None, stdout = None, stderr = None, shell = False)
  • 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
a = subprocess.check_call('df -h',shell=True)

>>>
文件系统 容量 已用 可用 已用% 挂载点
/dev/vda3 80G 3.6G 77G 5% /
devtmpfs 7.8G 0 7.8G 0% /dev
tmpfs 7.8G 0 7.8G 0% /dev/shm
tmpfs 7.8G 8.6M 7.8G 1% /run
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/vda6 404G 2.2G 402G 1% /data1
/dev/vda2 497M 128M 370M 26% /boot
tmpfs 1.2G 0 1.2G 0% /run/user/0

print(a)
>>>
0

参考资料


Python 执行系统命令
https://www.zywvvd.com/notes/coding/python/python-sys-command/python-sys-command/
作者
Yiwei Zhang
发布于
2022年7月5日
许可协议