Python封装shell命令实例分析


Posted in Python onMay 05, 2015

本文实例讲述了Python封装shell命令的方法。分享给大家供大家参考。具体实现方法如下:

# -*- coding: utf-8 -*-
import os
import subprocess
import signal
import pwd
import sys
class MockLogger(object):
  '''模拟日志类。方便单元测试。'''
  def __init__(self):
    self.info = self.error = self.critical = self.debug
  def debug(self, msg):
    print "LOGGER:"+msg
class Shell(object):
  '''完成Shell脚本的包装。
  执行结果存放在Shell.ret_code, Shell.ret_info, Shell.err_info中
  run()为普通调用,会等待shell命令返回。
  run_background()为异步调用,会立刻返回,不等待shell命令完成
  异步调用时,可以使用get_status()查询状态,或使用wait()进入阻塞状态,
  等待shell执行完成。
  异步调用时,使用kill()强行停止脚本后,仍然需要使用wait()等待真正退出。
  TODO 未验证Shell命令含有超大结果输出时的情况。
  '''
  def __init__(self, cmd):
    self.cmd = cmd # cmd包括命令和参数
    self.ret_code = None
    self.ret_info = None
    self.err_info = None
    #使用时可替换为具体的logger
    self.logger = MockLogger()
  def run_background(self):
    '''以非阻塞方式执行shell命令(Popen的默认方式)。
    '''
    self.logger.debug("run %s"%self.cmd)
    # Popen在要执行的命令不存在时会抛出OSError异常,但shell=True后,
    # shell会处理命令不存在的错误,因此没有了OSError异常,故不用处理
    self._process = subprocess.Popen(self.cmd, shell=True,
        stdout=subprocess.PIPE, stderr=subprocess.PIPE) #非阻塞
  def run(self):
    '''以阻塞方式执行shell命令。
    '''
    self.run_background()
    self.wait()
  def run_cmd(self, cmd):
    '''直接执行某条命令。方便一个实例重复使用执行多条命令。
    '''
    self.cmd = cmd
    self.run()
  def wait(self):
    '''等待shell执行完成。
    '''
    self.logger.debug("waiting %s"%self.cmd)
    self.ret_info, self.err_info = self._process.communicate() #阻塞
    # returncode: A negative value -N indicates that the child was
    # terminated by signal N
    self.ret_code = self._process.returncode
    self.logger.debug("waiting %s done. return code is %d"%(self.cmd,
              self.ret_code))
  def get_status(self):
    '''获取脚本运行状态(RUNNING|FINISHED)
    '''
    retcode = self._process.poll()
    if retcode == None:
      status = "RUNNING"
    else:
      status = "FINISHED"
    self.logger.debug("%s status is %s"%(self.cmd, status))
    return status
  # Python2.4的subprocess还没有send_signal,terminate,kill
  # 所以这里要山寨一把,2.7可直接用self._process的kill()
  def send_signal(self, sig):
    self.logger.debug("send signal %s to %s"%(sig, self.cmd))
    os.kill(self._process.pid, sig)
  def terminate(self):
    self.send_signal(signal.SIGTERM)
  def kill(self):
    self.send_signal(signal.SIGKILL)
  def print_result(self):
    print "return code:", self.ret_code
    print "return info:", self.ret_info
    print " error info:", self.err_info
class RemoteShell(Shell):
  '''远程执行命令(ssh方式)。
  XXX 含特殊字符的命令可能导致调用失效,如双引号,美元号$
  NOTE 若cmd含有双引号,可使用RemoteShell2
  '''
  def __init__(self, cmd, ip):
    ssh = ("ssh -o PreferredAuthentications=publickey -o "
        "StrictHostKeyChecking=no -o ConnectTimeout=10")
    # 不必检查IP有效性,也不必检查信任关系,有问题shell会报错
    cmd = '%s %s "%s"'%(ssh, ip, cmd)
    Shell.__init__(self, cmd)
class RemoteShell2(RemoteShell):
  '''与RemoteShell相同,只是变换了引号。
  '''
  def __init__(self, cmd, ip):
    RemoteShell.__init__(self, cmd, ip)
    self.cmd = "%s %s '%s'"%(ssh, ip, cmd)
class SuShell(Shell):
  '''切换用户执行命令(su方式)。
  XXX 只适合使用root切换至其它用户。
    因为其它切换用户后需要输入密码,这样程序会挂住。
  XXX 含特殊字符的命令可能导致调用失效,如双引号,美元号$
  NOTE 若cmd含有双引号,可使用SuShell2
  '''
  def __init__(self, cmd, user):
    if os.getuid() != 0: # 非root用户直接报错
      raise Exception('SuShell must be called by root user!')
    cmd = 'su - %s -c "%s"'%(user, cmd)
    Shell.__init__(self, cmd)
class SuShell2(SuShell):
  '''与SuShell相同,只是变换了引号。
  '''
  def __init__(self, cmd, user):
    SuShell.__init__(self, cmd, user)
    self.cmd = "su - %s -c '%s'"%(user, cmd)
class SuShellDeprecated(Shell):
  '''切换用户执行命令(setuid方式)。
  执行的函数为run2,而不是run
  XXX 以“不干净”的方式运行:仅切换用户和组,环境变量信息不变。
  XXX 无法获取命令的ret_code, ret_info, err_info
  XXX 只适合使用root切换至其它用户。
  '''
  def __init__(self, cmd, user):
    self.user = user
    Shell.__init__(self, cmd)
  def run2(self):
    if os.getuid() != 0: # 非root用户直接报错
      raise Exception('SuShell2 must be called by root user!')
    child_pid = os.fork()
    if child_pid == 0: # 子进程干活
      uid, gid = pwd.getpwnam(self.user)[2:4]
      os.setgid(gid) # 必须先设置组
      os.setuid(uid)
      self.run()
      sys.exit(0) # 子进程退出,防止继续执行其它代码
    else: # 父进程等待子进程退出
      os.waitpid(child_pid, 0)
if __name__ == "__main__":
  '''test code'''
  # 1. test normal
  sa = Shell('who')
  sa.run()
  sa.print_result()
  # 2. test stderr
  sb = Shell('ls /export/dir_should_not_exists')
  sb.run()
  sb.print_result()
  # 3. test background
  sc = Shell('sleep 1')
  sc.run_background()
  print 'hello from parent process'
  print "return code:", sc.ret_code
  print "status:", sc.get_status()
  sc.wait()
  sc.print_result()
  # 4. test kill
  import time
  sd = Shell('sleep 2')
  sd.run_background()
  time.sleep(1)
  sd.kill()
  sd.wait() # NOTE, still need to wait
  sd.print_result()
  # 5. test multiple command and uncompleted command output
  se = Shell('pwd;sleep 1;pwd;pwd')
  se.run_background()
  time.sleep(1)
  se.kill()
  se.wait() # NOTE, still need to wait
  se.print_result()
  # 6. test wrong command
  sf = Shell('aaaaa')
  sf.run()
  sf.print_result()
  # 7. test instance reuse to run other command
  sf.cmd = 'echo aaaaa'
  sf.run()
  sf.print_result()
  sg = RemoteShell('pwd', '127.0.0.1')
  sg.run()
  sg.print_result()
  # unreachable ip
  sg2 = RemoteShell('pwd', '17.0.0.1')
  sg2.run()
  sg2.print_result()
  # invalid ip
  sg3 = RemoteShell('pwd', '1711.0.0.1')
  sg3.run()
  sg3.print_result()
  # ip without trust relation
  sg3 = RemoteShell('pwd', '10.145.132.247')
  sg3.run()
  sg3.print_result()
  sh = SuShell('pwd', 'ossuser')
  sh.run()
  sh.print_result()
  # wrong user
  si = SuShell('pwd', 'ossuser123')
  si.run()
  si.print_result()
  # user need password
  si = SuShell('pwd', 'root')
  si.run()
  si.print_result()

希望本文所述对大家的Python程序设计有所帮助。

Python 相关文章推荐
Python基本数据类型详细介绍
Mar 11 Python
九步学会Python装饰器
May 09 Python
尝试用最短的Python代码来实现服务器和代理服务器
Jun 23 Python
Python回文字符串及回文数字判定功能示例
Mar 20 Python
python如何派生内置不可变类型并修改实例化行为
Mar 21 Python
解决Pycharm后台indexing导致不能run的问题
Jun 27 Python
Python企业编码生成系统之主程序模块设计详解
Jul 26 Python
Kears+Opencv实现简单人脸识别
Aug 28 Python
使用OpenCV实现仿射变换—缩放功能
Aug 29 Python
Python实现疫情通定时自动填写功能(附代码)
May 27 Python
使用Pycharm在运行过程中,查看每个变量的操作(show variables)
Jun 08 Python
Python3中小括号()、中括号[]、花括号{}的区别详解
Nov 15 Python
用Python中的字典来处理索引统计的方法
May 05 #Python
python递归计算N!的方法
May 05 #Python
浅谈Python中数据解析
May 05 #Python
探究Python多进程编程下线程之间变量的共享问题
May 05 #Python
浅谈Python中的数据类型
May 05 #Python
用Python实现一个简单的能够上传下载的HTTP服务器
May 05 #Python
使用Python程序抓取新浪在国内的所有IP的教程
May 04 #Python
You might like
libmysql.dll与php.ini是否真的要拷贝到c:\windows目录下呢
2010/03/15 PHP
php使用ob_start()实现图片存入变量的方法
2014/11/14 PHP
php实现兼容2038年后Unix时间戳转换函数
2015/03/18 PHP
WordPress中编写自定义存储字段的相关PHP函数解析
2015/12/25 PHP
PHP实现的登录,注册及密码修改功能分析
2016/11/25 PHP
php 使用mpdf实现指定字段配置字体样式的方法
2019/07/29 PHP
PHP数组实际占用内存大小原理解析
2020/12/11 PHP
JavaScript Event学习第四章 传统的事件注册模型
2010/02/07 Javascript
js相册效果代码(点击创建即可)
2013/04/16 Javascript
jquery动态调整div大小使其宽度始终为浏览器宽度
2014/06/06 Javascript
JS常用字符串方法(推荐)
2021/01/15 Javascript
jQuery的extend方法【三种】
2016/12/14 Javascript
Node.js websocket使用socket.io库实现实时聊天室
2017/02/20 Javascript
arctext.js实现文字平滑弯曲弧形效果的插件
2019/05/13 Javascript
Node.js从字符串生成文件流的实现方法
2019/08/18 Javascript
微信小程序自定义tabBar在uni-app的适配详解
2019/09/30 Javascript
Vue数据双向绑定底层实现原理
2019/11/22 Javascript
js 计数排序的实现示例(升级版)
2020/01/12 Javascript
python利用urllib和urllib2访问http的GET/POST详解
2017/09/27 Python
分析Python中解析构建数据知识
2018/01/20 Python
PyQt5+requests实现车票查询工具
2019/01/21 Python
使用PYTHON解析Wireshark的PCAP文件方法
2019/07/23 Python
Python正则表达式急速入门(小结)
2019/12/16 Python
Python中实现输入一个整数的案例
2020/05/03 Python
利用Python实现自动扫雷小脚本
2020/12/17 Python
html5配合css3实现带提示文字的输入框(摆脱js)
2013/03/08 HTML / CSS
澳大利亚网上玩具商店:Mr Toys Toyworld
2018/03/25 全球购物
德国传统玻璃制造商:Cristalica
2018/04/23 全球购物
环境科学专业研究生求职信
2013/10/02 职场文书
大学生简单自荐信
2013/11/10 职场文书
国际商务系学生个人的自我评价
2013/11/26 职场文书
派出所班子党的群众路线对照检查材料思想汇报
2014/10/01 职场文书
2015年暑期社会实践方案
2015/07/14 职场文书
交通安全学习心得体会
2016/01/18 职场文书
mysql批量新增和存储的方法实例
2021/04/07 MySQL
MyBatis配置文件解析与MyBatis实例演示
2022/04/07 Java/Android