Python运行Shell命令的正确方法

Python运行Shell命令的正确方法

Python是自动化任何事情的流行选择,包括自动化系统管理任务或需要运行其他程序或与操作系统交互的任务。然而,在Python中有很多方法可以实现这一点,其中大部分可以说是不好的。

原生工具

一般的经验法则应该是使用内置函数,而不是直接调用其他程序或操作系统命令。所以,首先让我们看看原生Python选项:

  • pathlib
    如果你需要创建或删除文件/目录;检查文件是否存在;更改权限;等等。绝对没有理由运行系统命令,只需使用pathlib,它就有你需要的一切。当你开始使用pathlib时,你也会意识到你可以忘记其他Python模块,例如grobos. path

  • tempfile
    同样,如果您需要一个临时文件,只需使用temfile模块,不要手动使用/tmp

  • shutil
    pathlib应该可以满足您在Python中与文件相关的大部分需求,但如果您需要例如复制、移动、‘chown’、'that’或创建存档,那么您应该转向shutil

  • signal
    以防您需要使用信号处理程序。

  • syslog
    用于Unix syslog的接口。

如果上述内置选项都不能满足您的需求,只有这样开始直接与操作系统或其他程序交互才有意义……

OS模块

从最糟糕的选项开始—os模块—它提供了与操作系统交互的低级功能—其中许多已被其他模块中的功能所取代。

如果你只是想调用其他程序,你可以使用os. system函数,但你不应该。我甚至不想给你举个例子,因为你根本不应该使用它。

虽然os不应该是您的首选,但您可能会发现有几个功能很有用:

import os  
  
print(os.getenv('PATH'))  
# /home/martin/.local/bin:/usr/local/sbin:/usr/local/bin:...  
print(os.uname())  
# posix.uname_result(sysname='Linux', nodename='...', release='...', version='...', machine='x86_64')  
print(os.times())  
# posix.times_result(user=0.01, system=0.0, children_user=0.0, children_system=0.0, elapsed=1740.63)  
print(os.cpu_count())  
# 16  
print(os.getloadavg())  
# (2.021484375, 2.35595703125, 2.04052734375)  
old_umask = os.umask(0o022)  
# Do stuff with files...  
os.umask(old_umask)  # restore old umask  
  
# Only if you need better random numbers than pseudo-random numbers from 'random' module:  
# 仅当您需要比来自“随机”模块的伪随机数更好的随机数时:  
from base64 import b64encode  
  
random_bytes = os.urandom(64)  
print(b64encode(random_bytes).decode('utf-8'))  
# C2F3kHjdzxcP7461ETRj/YZredUf+NH...hxz9MXXHJNfo5nXVH7e5olqLwhahqFCe/mzLQ==  

除了上面显示的函数,还有用于创建fd(文件描述符)、管道、打开PTYchrootchmodmkdirkillstat的函数,但我不鼓励你使用它们,因为还有更好的选择。文档中甚至有[部分](https://docs.python.org/3/library/subprocess.html#subprocess-replacements)展示了如何用“子进程”模块替换os,所以不要考虑使用os. popenos.spawnos.system

os模块中的大多数剩余函数都是与操作系统(或C语言)API的直接接口,例如os.dupos.spliceos.mkafoos.exvos.fork等。如果你需要使用所有这些,那么我不确定Python是否是适合这项任务的语言……

subprocess模块

我们在Python中的第二个稍微好一点的选项是subprocess模块:

import subprocess  
  
p = subprocess.run('ls -l', shell=True, check=True, capture_output=True, encoding='utf-8')  
  
# 'p' is instance of 'CompletedProcess(args='ls -la', returncode=0)'  
print(f'Command {p.args} exited with {p.returncode} code, output: \n{p.stdout}')  
# Command ls -la exited with 0 code  
  
# total 36  
# drwxrwxr-x  2 martin martin  4096 apr 22 12:53 .  
# drwxrwxr-x 42 martin martin 20480 apr 22 11:01 ..  
# ...  

如文档所述:调用子进程的推荐方法是对它可以处理的所有用例使用run()函数。

在大多数情况下,您使用subprocess.run,传入kwargs来改变它的行为就足够了,例如shell=True允许您将命令作为单个字符串传递,check=True会在退出代码不是0时引发异常,而capture_output=True会填充stdout属性。

虽然subprocess.run()是调用进程的推荐方法,但此模块中还有其他(不必要的、已弃用的)选项:callcheck_callcheck_outputgetstatusoutgetout。通常,您应该只使用runPopen

with subprocess.Popen(['ls', '-la'], stdout=subprocess.PIPE, encoding='utf-8') as process:  
    # process.wait(timeout=5)  # Returns only code: 0  
    outs, errs = process.communicate(timeout=5)  
    print(f'Command {process.args} exited with {process.returncode} code, output: \n{outs}')  
  
# Pipes  
import shlex  
ls = shlex.split('ls -la')  
awk = shlex.split("awk '{print $9}'")  
ls_process = subprocess.Popen(ls, stdout=subprocess.PIPE)  
awk_process = subprocess.Popen(awk, stdin=ls_process.stdout, stdout=subprocess.PIPE, encoding='utf-8')  
  
for line in awk_process.stdout:  
    print(line.strip())  
    # .  
    # ..  
    # examples.py  
    # ...  

上面的第一个例子显示了Popen等同于前面显示的subprocess.run。但是,您应该只在需要比run提供的更大灵活性时使用Popen,例如在第二个例子中,您可以看到如何将一个命令的输出通过管道传输到另一个命令中,有效地运行ls-la|awk '{print 9美元}'。您还可以看到我们使用了shlex. split,这是一个方便的函数,它将字符串拆分为可以传递给Popenrun的标记数组,而无需使用shell=True

当使用Popen时,您还可以使用 terminate(), kill()send_signal()与进程进行更多交互。

在前面的例子中,我们没有真正做任何错误处理,但是在运行其他进程时可能会出错。对于简单的脚本,check=True可能就足够了,因为它会导致CalledProcessError在子进程运行到非零返回代码时立即引发,所以你的程序会快速而响亮地失败,这很好。如果你还设置了timeout参数,那么你也可以得到TimeoutExpired异常,但是一般来说,subProccess模块中的所有异常都继承自Subprocess Error,所以如果你想捕捉异常,那么你可以简单地观察Subprocess Error

linux系统支持的sh库

使用pip命令安装sh:

linux系统pip install sh

windows系统(win32api)pip install pywin32

# https://pypi.org/project/sh/  
# pip install sh  
import sh  
  
# Run any command in $PATH...  
print(sh.ls('-la'))  
  
ls_cmd = sh.Command('ls')  
print(ls_cmd('-la'))  # Explicit  
# total 36  
# drwxrwxr-x  2 martin martin  4096 apr  8 14:18 .  
# drwxrwxr-x 41 martin martin 20480 apr  7 15:23 ..  
# -rw-rw-r--  1 martin martin    30 apr  8 14:18 examples.py  
  
# If command is not in PATH:  
custom_cmd = sh.Command('/path/to/my/cmd')  
custom_cmd('some', 'args')  
  
with sh.contrib.sudo:  
    # Do stuff using 'sudo'...  
    ...  

当我们调用sh.some_command时,sh库会尝试查找内置的shell命令或具有该名称的$PATH中的二进制文件。如果它找到这样的命令,它会简单地为您执行它。如果命令不在$PATH中,那么您可以创建Command的实例并以这种方式调用它。如果您需要使用sudo,您可以使用contrib模块中的sudo上下文管理器。如此简单明了,对吧?

要将命令的输出写入文件,您只需向函数提供“_out”参数:

sh.ip.address(_out='/tmp/ipaddr')  
# Same as 'ip address > /tmp/ipaddr'  

上面还展示了如何调用子命令——只需使用点。

最后,您还可以使用_in参数 (|) 管道:

print(sh.awk('{print $9}', _in=sh.ls('-la')))  
# Same as "ls -la | awk '{print $9}'"  
  
print(sh.wc('-l', _in=sh.ls('.', '-1')))  
# Same as "ls -1 | wc -l"  

至于错误处理,您可以简单地观察“错误返回代码”或TimeoutException异常:

try:  
    sh.cat('/tmp/doesnt/exist')  
except sh.ErrorReturnCode as e:  
    print(f'Command {e.full_cmd} exited with {e.exit_code}')  
    # Command /usr/bin/cat /tmp/doesnt/exist exited with 1  
  
curl = sh.curl('https://httpbin.org/delay/5', _bg=True)  
try:  
    curl.wait(timeout=3)  
except sh.TimeoutException:  
    print("Command timed out...")  
    curl.kill()  

可选地,如果您的进程从您将收到的信号SignalException终止,您可以使用例如SignalException_SIGKILL(或_SIGTERM_SIGSTOP)来检查特定信号。

该库还具有内置的日志记录支持,您所要做的就是打开它:

import logging  
  
# Turn on default logging:  
logging.basicConfig(level=logging.INFO)  
sh.ls('-la')  
# INFO:sh.command:<Command '/usr/bin/ls -la', pid 1631463>: process started  
  
# Change log level:  
logging.getLogger('sh').setLevel(logging.DEBUG)  
sh.ls('-la')  
# INFO:sh.command:<Command '/usr/bin/ls -la', pid 1631661>: process started  
# DEBUG:sh.command:<Command '/usr/bin/ls -la'>: starting process  
# DEBUG:sh.command.process:<Command '/usr/bin/ls -la'>.<Process 1631666 ['/usr/bin/ls', '-la']>: started process  
# ...  

【我之前自学Python的时候整理了很多Python学习资料,现在我也用不上了,我上传到CSDN官方了,有需要的朋友可以扫描下方二维码进行获取】

一、学习大纲

在这里插入图片描述

二、开发工具

在这里插入图片描述

三、Python基础材料

在这里插入图片描述

四、实战资料

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值