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

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

1. 基本用法

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

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

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

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

2. 简单实例

闲话少说,先上代码

1 from cmd importCmd2 from sys importexit3

4

5 classMyCmd(Cmd):6 def __init__(self):7 super(MyCmd, self).__init__()8 self.prompt = "->"

9

10 defdo_hello(self, args):11 print("Hello.")12

13 defhelp_hello(self, args):14 print("hello - print hello and do nothing more.")15

16 defdo_exit(self, args):17 exit(0)18

19

20 defmain():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 importCmdfrom sys importexit, argvimportthreadingimportsocketimportasyncoreclassBackgroundRunner(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''

defhandle_connect(self):pass

defhandle_close(self):

self.close()defhandle_read(self):

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

defwritable(self):return len(self.buffer) >0defhandle_write(self):

sent=self.send(self.buffer)

self.buffer=self.buffer[sent:]classMyCmd(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()方法应该视需要进行重载,否则回车输入空指令的默认处理会是执行上一条指令defemptyline(self):pass

defdo_send(self, args):#bla bla bla, 对参数args(其实就是个字符串)做些事,得到你要发送的数据data

sendresult =self.bgrunner.send(data)#bla bla bla

defdo_exit(self, args):

self.bgrunner.close()

exit(0)defmain():#其实你完全可以用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模块去搞,但这次的任务只是个即时小工具而已……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值