【Python】使用cmd模块构造一个带有后台线程的交互命令行界面

摘要:
最近,我写了一些测试工具,但我懒得制作GUI。然后我意识到python有一个名为cmd的内置模块,当我使用它时,它是一个救世主。当然,您也可以实现“do_help()”来打印帮助摘要;2.一个简单的例子。让我们减少流言蜚语。首先,来自sysimporttexit345classMyCmd:6def__init__:7super.__的cmdimportCmd2的代码1init__()8自身。prompt=“-˃”910defdo_hello:11print1213defhelp_hello:124print1516defdo_exit:17exit181920defmain():21mycmd=MyCmd()22mycmd。cmdloop23245if__name__==“__main__”:26main()3.最初的想法是使用cmd构建一个连接到服务器的工具。当没有动作时,它将接收并显示发送到那里的数据。在某些情况下,它会根据交互输入指令向服务器发送一些数据。为了方便交互,选择cmd来构造接口。

最近写一些测试工具,实在懒得搞GUI,然后意识到python有一个自带模块叫cmd,用了用发现简直是救星。

1. 基本用法

cmd模块很容易学到,基本的用法比较简单,继承模块下的Cmd类,添加需要的功能入口就好了。

Cmd类有个prompt属性,修改它可以把默认提示符((cmd))替换成自定义的;

为自己的Cmd类添加名为“do_xxx()”的方法,则运行时,在提示符下可以接受xxx指令。但对应的参数解析貌似是要自己搞定的,否则在指令名之后输入的所有东西,只要不回车,它都是一个大参数;

有指令的实现方法,也有对应帮助信息的实现方法。只要添加“help_xxx()”方法,就可以为xxx方法在运行时提供一个帮助信息了。当然你也可以实现一个“do_help()”来打印帮助汇总什么的;

2. 简单实例

闲话少说,先上代码

 1 from cmd import Cmd
 2 from sys import exit
 3 
 4 
 5 class MyCmd(Cmd):
 6     def __init__(self):
 7         super(MyCmd, self).__init__()
 8         self.prompt = "->"
 9 
10     def do_hello(self, args):
11         print("Hello.")
12 
13     def help_hello(self, args):
14         print("hello - print hello and do nothing more.")
15 
16     def do_exit(self, args):
17         exit(0)
18 
19 
20 def main():
21     mycmd = MyCmd()
22     mycmd.cmdloop(intro="My Cmd Demo.")
23 
24 
25 if __name__ == "__main__":
26   main()

3. 问题来了

最初的想法实际上是要用cmd构造一个工具,连接到某个服务端,没动作的时候就接收和显示那边发来的数据,某些时候还要按照交互输入的指令,往服务端那边发送一些数据。为了交互方便,才选择cmd来构造界面。

那么起码来说,工具要有个do_exit()方法用来退出,还要有个do_send()方法用来发数据给服务端。

只有这么简单就好了,实际需要考虑等着收数据的同时还要等着操作者从界面输入指令,指令输入完毕回车以后,就要把数据发出去。显然这是个异步场景。说到异步,想到了tornado和twisted,但试了试twisted发现用来搞这种客户端并不怎么方便;后来想起python自带的asyncore,看了下文档发现很简单就可以做到既一直接收又能随时发送,就改用asyncore了。

但asyncore的dispatcher要跑起来的话,得运行asyncore.loop(),但这个如果在主线程里运行起来,后面就没有Cmd.cmdloop()什么事了。

对此,我想到的是,把asyncore.loop()放到一个后台线程里去,而把cmdloop()放在主线程里。而要让cmd界面能够通过dispatcher发送数据,还是得先让它知道dispatcher实例的存在。

好在python自带了不少各种电池,调用Threading提供的功能,没费多少功夫就搞好了。

大概是这样的:

from cmd import Cmd
from sys import exit, argv
import threading
import socket
import asyncore



class BackgroundRunner(asyncore.dispatcher, object):
    def __init__(self, host, port):
        super(BackgroundRunner, self).__init__()
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, port))
        self.buffer = b''

    def handle_connect(self):
        pass

    def handle_close(self):
        self.close()

    def handle_read(self):
        recvdata = self.recv(4096)
        # TO-DO with the data received

    def writable(self):
        return len(self.buffer) > 0

    def handle_write(self):
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]


class MyCmd(Cmd, object)
    def __init__(self):
        super(MyCmd, self).__init__()
        self.prompt = "->"
        self.bgrunner = BackgroundRunner(host, port)

        # 调用setDaemon()将线程转到后台,否则它执行ssyncore.loop()的时候会占住你的标准输入和输出直到强行退出
        nthd = threading.Thread(target=asyncore.loop)
        nthd.setDaemon(True)
        nthd.start()

  # Cmd.emptyline()方法应该视需要进行重载,否则回车输入空指令的默认处理会是执行上一条指令
def emptyline(self): pass def do_send(self, args): # bla bla bla, 对参数args(其实就是个字符串)做些事,得到你要发送的数据data sendresult = self.bgrunner.send(data) # bla bla bla def do_exit(self, args): self.bgrunner.close() exit(0) def main(): # 其实你完全可以用argparse什么的来处理命令行参数,我这只是给自己额写个示例才直接搞argv的,别太认真 host, port = argv[1:3] c = MyCmd(host, int(port)) c.cmdloop() if __name__ == "__main__": main()

4. 各种小麻烦

默认cmd模块中的Cmd类会使用rawinput来处理提示符显示和输入信息获取的工作,但是特定情况下会有个问题:

当交互线程等待用户输入指令的时候,如果希望后台线程可以打印信息到前台显示的话……

打印随便用print或者sys.stdout.write什么的,当然是打印出来了,但只要开始输入新的指令,这些打印信息就都被清除掉了,只剩下提示符和新的输入。如果想实时看什么东西的话……

反复尝试和阅读cmd模块源码以后发现这么一件事:

Cmd类在实例化的时候,默认会有个use_rawinput属性是为1的,如果重载__init__()的时候把它设置为0,那么会改为通过readline来处理提示符和输入(当然你如果在windows上玩这一手的话,最好先把pyreadline装上,windows上我没弄过gnu readline,不知道有没搞成了的),然后打印信息被擦除的问题就得以解决了。

其实记录信息完全可以让logging模块去搞,但这次的任务只是个即时小工具而已……

免责声明:文章转载自《【Python】使用cmd模块构造一个带有后台线程的交互命令行界面》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇《C#从现象到本质》读书笔记(一)第1章 .NET基础知识ME525+ Defy+ 刷机指南[zz]下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python基础之读取xml

python怎么操作xml文件详细介绍链接:https://www.jb51.net/article/50812.htm 从结构上来说,xml很像常见的HTML超文本标记语言。不过超文本语言被设计用来显示数据,其焦点是数据的外观。xml被设计用来传输和存储数据,其焦点是数据的内容。 特征: 1. 标签对组成:<TEST></TEST>...

远程获取--snmp模块(python)/snmp-cmds,easysnmp

一、简介 snmp-cmds模块通过SNMP与目标设备进行通信,此模块适用于windows,此模块是基于系统已安装了net-snmp环境easysnmp模块通过SNMP与谬表设备进行通信,此模块用于linux,此模块基于系统已安装了net-snmp环境 二、snmp-cmds模块安装 2.1 在Windows平台 #1.系统环境安装net-snmp软件...

python selenium 编码问题

#coding=utf-8 from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.baidu.com") # 返回百度页面底部备案信息 text = driver.find_element_by_id("cp").text print(tex...

Python36和Python27共存的方法

Python27和Python37环境的配置 设置环境变量 我的电脑右键属性-高级系统属性-环境变量 选择系统变量中的Path,双击打开 加入你的Python安装路径 C:Python27;C:Python27;Scripts;C:Python36;C:Python36Scripts; 把C:Python27目录下的Python.exe修改成Pytho...

用python实现与小米网关通讯

用python实现与小米网关通讯 python 与小米网关通讯的三块内容: 以下内容的理解需要配合《绿米网关局域网通讯协议》使用 1、监听网关发出的组播信息:(有网关及连接设备的生命信号,事件信息) 2、读取需要获得的信息 3、控制连接设备(涉及了token加密部分) 1、upd广播监听小米网关的组播信息 1 #!/usr/bin/env pytho...

mysql 无法退出sql命令行编辑

mysql> insert into core_user(login_name,real_name,password,email,create_time,is_disab….. ‘> quit ‘> exit ‘> ; ‘> ‘> c ‘> /c   终止批处理操作吗(Y/N)? y   遇到上面的情况无法终于s...