Python执行外部命令的六种方式对比与实战
本文详细对比了Python中执行外部命令的多种方法,包括os.system、os.popen、commands模块以及subprocess系列函数。重点解析了阻塞与非阻塞行为的差异,如何捕获标准输出与错误,以及将输出重定向至文件的技巧。同时指出使用PIPE可能导致死锁的风险,并提供check_call与check_output的异常处理建议,帮助开发者选择合适方案。
Python执行外部命令概述
在Python开发中,经常需要调用外部程序或系统命令。常用的实现方式有多种,下面逐一介绍。
首先介绍os.system()方法。该方法执行时会阻塞当前线程,直到外部命令执行完毕。
执行过程中,命令的标准输出会直接打印到控制台屏幕。
函数返回值为命令的执行结果码(exit code),通常0表示成功,非0表示失败。
# -*- encoding=utf-8 -*-
import os
if __name__ == '__main__':
pass
print('开始')
ret1 = os.system('python')
print('ret1:{}'.format(ret1))
ret2 = os.system('java -version')
print('ret2:{}'.format(ret2))
print('结束')
接下来看具体的运行效果。

有人可能认为阻塞是因为Python解释器本身的机制,为了验证这一点,可以尝试连续执行两个相同的命令,例如都使用java -version进行测试。
# -*- encoding=utf-8 -*-
import os
if __name__ == '__main__':
pass
print('开始')
ret1 = os.system('java -version')
print('ret1:{}'.format(ret1))
ret2 = os.system('java -version')
print('ret2:{}'.format(ret2))
print('结束')
实际运行结果显示,命令依次执行,前一个命令结束后才开始下一个,体现了阻塞特性。

os.popen()方法详解
第二种方式是os.popen()。
关于阻塞行为,os.popen()的表现取决于后续操作:如果不读取输出,它可能不阻塞或表现不同;如果读取输出,则会阻塞。
与os.system不同,os.popen()执行时,命令的标准输出不会直接打印到屏幕。
该函数返回一个文件对象,开发者可以通过read()或readline()方法读取命令的输出内容。
当不阻塞(即不立即读取输出)时:
# -*- encoding=utf-8 -*-
import os
if __name__ == '__main__':
pass
print('开始')
os.popen('python')
ret2 = os.popen('java -version')
print('结束')
运行代码会发现,程序迅速打印“开始”和“结束”,就像只执行了两个print语句一样快,因为os.popen()创建进程后并未立即等待其完成或读取输出。

当阻塞(即调用read()读取输出)时:
# -*- encoding=utf-8 -*-
import os
if __name__ == '__main__':
pass
print('开始')
ret1 = os.popen('python')
print(ret1.read())
ret2 = os.popen('java -version')
print(ret2.read())
print('结束')
运行代码会看到程序等待命令执行完毕,读取输出后再打印“结束”,体现了阻塞特性。

commands.getstatusoutput()方法
第三种方式是commands.getstatusoutput()。
该方法主要在Python 2.x版本中使用,Python 3中已移除。
需要在Linux环境下运行才能看到完整效果。
其阻塞行为与os.popen()类似,取决于是否读取输出。
执行时,命令的标准输出不会直接打印到屏幕。
在非阻塞情况下:
# -*- encoding=utf-8 -*-
import commands
if __name__ == '__main__':
pass
print('开始')
ret1 = commands.getstatusoutput('python')
ret2 = commands.getstatusoutput('java -version')
print('结束')
运行代码,程序快速执行并返回结果元组(状态码和输出),不等待命令完全结束或打印输出。

在阻塞情况下:
# -*- encoding=utf-8 -*-
import commands
if __name__ == '__main__':
pass
print('开始')
ret1 = commands.getstatusoutput('python')
print ret1[0]
print ret1[1]
print '=============='
ret2 = commands.getstatusoutput('java -version')
print ret2[0]
print ret2[1]
print '=============='
print('结束')
运行代码,程序会等待命令执行完毕,返回包含状态码和完整输出的元组。

subprocess.Popen高级用法
第四种方式是subprocess模块中的Popen类。
Popen的阻塞行为同样取决于是否读取IO:如果不读取,可能不阻塞;如果读取或调用wait(),则会阻塞。
默认情况下,命令的标准输出会打印到屏幕。
可以通过stdout和stderr参数指定将标准输出或错误输出重定向到文件。
# -*- encoding=utf-8 -*-
import subprocess
if __name__ == '__main__':
pass
print('开始')
s1 = subprocess.Popen('python')
s1.wait()
s2 = subprocess.Popen('java -version')
s2.wait()
print('结束')
下面展示Popen的基本运行示例。

4.1、将输出重定向到文件。
通过传入文件对象给stdout参数,可以将命令输出写入指定文件。
# -*- encoding=utf-8 -*-
import subprocess
if __name__ == '__main__':
pass
print('开始')
with open('s1.txt', 'w') as f:
s1 = subprocess.Popen('python', stdout=f, ) # 指定输出重定向到文件
s1.wait(10) # 超过10秒还没执行完会抛出异常
with open('s2.txt', 'w') as f:
s2 = subprocess.Popen('java -version', stdout=f, stderr=f) # 指定输出和错误都重定向到文件
s2.wait()
print('结束')
运行代码后,可以看到指定文件已被写入内容,而屏幕没有打印输出。需要注意区分stdout和stderr参数,分别处理标准输出和错误输出。
4.2、捕获输出和错误信息。
使用subprocess.PIPE可以创建一个新的管道来接收命令的运行状态和输出。
# -*- encoding=utf-8 -*-
import subprocess
if __name__ == '__main__':
pass
print('开始')
s1 = subprocess.Popen('java -version', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
s1.wait(10) # 超过10秒还没执行完会抛出异常
print(s1.returncode) # 结果码
print(s1.poll()) # 已经执行结束返回结果码,否则返回None
std1 = s1.stdout.read()
print('std_out:{}'.format(std1))
std2 = s1.stderr.read()
print('std_err:{}'.format(std2))
print('结束')
运行代码,通过读取管道内容获取命令的输出和错误信息。

subprocess.call与check系列函数
第五种方式是subprocess.call()。
该方法旨在替代os.system(),提供更灵活的控制。
subprocess.call()默认是阻塞的,直到命令执行完毕。
# -*- encoding=utf-8 -*-
import subprocess
if __name__ == '__main__':
pass
print('开始')
s1 = subprocess.call('java -version', )
print(s1)
s2 = subprocess.call('python', )
print(s2)
s3 = subprocess.call('python', timeout=3) # 超时抛出异常
print(s3)
print('结束')
第六种是check_call()和check_output()。
这两个函数都会检查命令的结果码,如果结果码不是0,就会抛出异常。
check_call()函数返回命令的状态码。
check_output()函数返回命令的标准输出内容。
# -*- encoding=utf-8 -*-
import subprocess
if __name__ == '__main__':
pass
print('开始')
s1 = subprocess.check_call('java -version', )
print(s1)
s2 = subprocess.check_output('java -version', )
print(s2)
print('结束')
运行代码示例,可以看到成功执行时返回状态码或输出内容。

使用call()、check_call()和check_output()时,尽量避免将参数stderr和stdout设置为subprocess.PIPE。因为这些函数默认会等待子进程完成,如果子进程产生大量输出导致管道堵塞,而父进程仍在等待子进程完成,可能会造成死锁。
以上就是Python中执行外部命令的几种常见方式及其注意事项,希望本文能帮助大家更好地掌握相关技术。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













