利用python cmd模块开发小型命令行应用程序

利用python的cmd模块可以轻松开发出一个基于命令行接口(CLI)的交互式应用程序。最常见的使用方式是从cmd.Cmd派生一个类,重载类中成员函数,从而实现用户自定义的功能。首先看一个例子:

import cmd
import subprocess
import sys

class CLI(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)

        # 设置命令提示符
        self.prompt = "> "
        # 设置程序Banner
        self.intr = ""

    def help_run(self, args):
        print "run a sub shell"

    def do_help(self, args):
        if args == "exit":
            print "exit this program"
        elif args == "quit":
            print "quit this program"
        elif args == "shell":
            print "run a shell commad"
        elif args == "run":
            print "run a sub shell"
        else:
            print "unknown command"

    def do_run(self, args):
        this.shell("cmd")

    def do_exit(self, args):
        "exit this program"

        sys.exit()

    def do_quit(self, args):
        "quit this program"

        return True

    def do_shell(self, args):
        "run a shell commad"

        subshell = subprocess.Popen(args, shell=True, stdin=None, stdout=None)
        subshell.communicate()
        subshell.terminate()

        print("")

    # define shortcuts for quit and run and help
    do_q = do_quit
    do_r = do_run
    do_h = do_help

以上代码实现了一个cmd.Cmd的子类CLI,定义了一些用户命令及其帮助信息。
在cmd.Cmd中,以do_开头的函数为命令,以help_开头的函数为帮助信息。?和!是两个特殊的命令,?可以获取命令的帮助信息,!可以在事件循环中调用系统shell命令(例如,ls、dir、cd等等),但是需要实现do_shell方法,否则!无效。而?则与之相反,使用?其实是调用了do_help方法。但是cmd.Cmd内部已经实现了一个do_help方法,因而,如果子类中也定义了do_help方法,则会覆盖原始的do_help方法 。

关于cmd模块的实现可以参考其源码cmd.py。其中命令的分发采用Python反射机制实现:

func = getattr(self, "do_"+cmd)
func(args)

从上面的代码中也可以理解为什么所有用户命令都必须要以do_开头,因为cmd.py中就是通过”do_”+cmd方式来搜索函数。

之后,实例化一个CLI类,并调用其cmdloop方法开启主事件循环即可以得到一个简单的基于命令行界面的交互性应用程序。程序主要功能体现在用户实现的一系列do_开头的功能函数中。例如,在prompt后面输入quit,则程序会调用do_quit函数实现程序退出;输入?则程序会执行do_help,打印帮助信息。输入!ls则会调用do_shell,执行系统命令ls。

if __name__ == "__main__":
    cli = CLI()
    try:
        cli.cmdloop("This is a CLI program")
    except KeyboardInterrupt as e:
        print "\nProgram aborted by user\n"

    print "Bye!"

上面的代码首先实例化一个CLI类,然后调用对象的cmdloop进入主事件循环,通过exit和quit命令退出应用程序。通过try...except...语句块将cmdloop调用包起来以捕获Ctrl+C信号,使程序输出更加优雅。否则,当用户按下Ctrl+C强制退出程序时,程序会抛出一个KeyboardInterrupt类型的异常,对用户不够友好。

为了让cmd支持!调用系统shell命令,开发者必须要实现do_shell方法。在事件循环中执行shell命令一般是通过新建一个子进程,然后在子进程中执行shell命令,这可以利用subprocess模块实现。代码如下:

subshell = subprocess.Popen(args, shell=True, stdin=None, stdout=None)
subshell.communicate()
subshell.terminate()

以上这三条语句一个都不能少:

  • 第一条语句中利用subprocess模块建立子进程,设置stdin和stdout为None可以使得子进程从父进程继承输入和输出。
  • 第二条语句中的communicate可以阻塞父进程和子进程之间的通信。当子进程结束时才会继续执行下面的语句。
  • 第三条语句则是释放资源。如果没有这一条语句,则之后再次调用subprocess时可能会出现问题。

以下简要谈谈在一般的软件开发中exit和quit的区别。

  • exit是中断整个程序的执行流程,强制退出,这会干扰其他代码的执行,一般用于发生了致命错误的场合。
  • quit相对温和,它只是从当前模块中退出,返回到调用模块中,整个程序执行流程不被强制中断。

拿上面的代码来做说明。exit调用sys.exit()实现,这会导致执行该语句后直接中止程序运行,该语句之后的代码不会被执行。上面的代码中cli.cmdloop()之后还调用了print "Bye!"。如果采用exit方式,则这一条语句不会被执行。相反,quit的实现是采用return语句,这回导致cli.cmdloop()返回,这样就可以继续执行后面的print "Bye!",所以程序会输出Bye!

当然,这里说的只是一种惯例。实际上,利用cmd.Cmd开发命令行交互式程序时,命令的语义是由程序员定义的。程序员完全可以定义exit只退出当前模块,也可以定义quit为中止程序执行。只是这一定义与惯例不符合,显得有些怪异罢了。

建议利用cmd.Cmd开发程序时,采用尽量温和的方式处理退出,即只实现do_quit,不实现do_exit,且quit的语义为退出当前主事件循环,而不是退出整个程序。

而且,利用quit的处理方式还可以实现cmd.Cmd的嵌套,例如:

class SubCLI(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)

        self.prompt = ">> "

    def do_quit(self, args):
        return True
    def do_exit(self, args):
        sys.exit()

class CLI(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)

        self.prompt = "> "
        self.cli = SubCLI()

    def do_cli(self, args):
        self.cli.cmdloop()
        print "Return from sub cli"

    def do_quit(self, args):
        return True

在SubCLI中如果采用quit方式退出,则能够返回到主CLI中,而如果采用exit方式退出,则不能返回主CLI。所以利用quit方式退出有很多优势,建议采用。

顺便提一下,如果坚持使用exit方式退出,但是又想实现cmd.Cmd的嵌套,那么可以使用子进程的方式来实现。即,新建一个子进程,在子进程中再开一个cmd.Cmd的事件循环。这样,sys.exit只是退出子进程的事件循环,而不会影响主cli的事件循环。实现代码如下,只需对CLI的do_cli做少量修改:

def do_cli(self, args):
        subshell = subprocess.Popen('python -c "import SubCLI; SubCLI.cmdloop();"', shell=True, stdin=None, stdout=None)
        subshell.communicate()
        subshell.terminate()

        print "Return from sub cli"
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值