15.6. cmd — 命令行处理器

未匹配的标注

目的:创建面向行的命令处理器/Create line-oriented command processors。

cmd模块包含一个公共类Cmd,旨在用作交互式 Shell 和其他命令解释器的基类。默认情况下,它使用 readline 进行交互式提示处理,命令行编辑,并完成命令。

处理命令

cmd创建的命令解释器使用循环从其输入中读取所有行,解析它们,然后将命令分派给适当的命令处理程序。输入行被分为两部分:命令和该行上的任何其他文本。如果用户输入foo barbar,并且解释器类包含名为do_foo()的方法,则以“ barbar”作为唯一参数来调用它。

文件结束标记被分派到do_EOF()。如果命令处理程序返回一个真值,则程序将干净退出。因此,为了提供一种退出解释器的干净方法,请确保实现do_EOF() 并使它返回 True。

这个简单的示例程序支持“ greet ”命令:

cmd_simple.py

import cmd

class HelloWorld(cmd.Cmd):

    def do_greet(self, line):
        print("hello")

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

交互运行将演示如何调度命令,并显示Cmd中包含的某些功能。

$ python3 cmd_simple.py

(Cmd)

首先要注意的是命令提示符(Cmd) 。可以通过属性prompt配置提示。提示值是动态的,并且如果命令处理程序更改了提示属性,则新值将用于查询下一个命令。

Documented commands (type help <topic>):
========================================
help

Undocumented commands:
======================
EOF  greet

help命令内置于Cmd中。不带任何参数的help将显示可用命令列表。如果输入中包含命令名称,则输出更详细,并且仅在可用时限制于该命令的详细信息。

如果命令是greet,则调用do_greet() 来处理它:

(Cmd) greet
hello

如果输入的命令没有对应的处理函数,则方法default()函数被自动调用且整个输入行作为它的参数。该方法默认报告一个错误。

(Cmd) foo
*** Unknown syntax: foo

由于do_EOF()返回 True,因此输入 Ctrl-D 将导致解释器退出。

(Cmd) ^D$

命令参数

此示例包括一些增强功能,以消除一些烦恼并为greet命令添加帮助。

cmd_arguments.py

import cmd

class HelloWorld(cmd.Cmd):

    def do_greet(self, person):
        """greet [person]
        Greet the named person"""
        if person:
            print("hi,", person)
        else:
            print('hi')

    def do_EOF(self, line):
        return True

    def postloop(self):
        print()

if __name__ == '__main__':
    HelloWorld().cmdloop()

添加到do_greet() 的文档字符串成为该命令的帮助文本:

$ python3 cmd_arguments.py

(Cmd) help

Documented commands (type help <topic>):
========================================
greet  help

Undocumented commands:
======================
EOF

(Cmd) help greet
greet [person]
        Greet the named person

输出显示 persongreet 的一个可选参数。尽管对命令来说键入参数是可选的,但该命令的处理方法是始终需要参数的。当不主动键入参数时,方法的参数是一个空字符串。然后由命令处理程序来确定空参数是否有效,或者对命令进行任何进一步的解析和处理。在此示例中,如果提供了一个人的名字,则问候语是个性化的。

(Cmd) greet Alice
hi, Alice
(Cmd) greet
hi

无论用户是否提供参数,传递给命令处理程序的值都不包括命令本身。这简化了命令处理程序中的解析,尤其是在需要多个参数的情况下。

实时帮助

在前面的示例中,帮助文本的格式尚待改进。由于它来自文档字符串,因此保留了源文件中的缩进。可以直接更改源代码以删除多余的空格,但是这会使该程序代码的格式不正确。更好的解决方案是在名为help_greet()的方法中为 greet 命令实现帮助处理程序。调用帮助处理程序以生成命名命令的帮助文本。

cmd_do_help.py

# Set up gnureadline as readline if installed.
try:
    import gnureadline
    import sys
    sys.modules['readline'] = gnureadline
except ImportError:
    pass

import cmd

class HelloWorld(cmd.Cmd):

    def do_greet(self, person):
        if person:
            print("hi,", person)
        else:
            print('hi')

    def help_greet(self):
        print('.'.join([
            'greet [person]',
            'Greet the named person',
        ]))

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

在此示例中,文本是静态的,但格式更美观。也可以使用先前的命令状态将帮助文本的内容调整为当前上下文。

$ python3 cmd_do_help.py

(Cmd) help greet
greet [person]
Greet the named person

帮助处理程序要实际输出帮助消息,而不是简单地返回帮助文本以在其他地方处理。

自动完成

Cmd支持对带有处理方法的命令的名称自动补全。用户通过在输入提示下按 Tab 键来触发完成。如果存在多个可能补全的命令,请按两次 Tab 键以打印选项列表。

注意

默认情况下,readline所需的GNU库并非在所有平台上都可用。在这种情况下,制表符补全可能不起作用。请参阅[readline](https://pymotw.com/3/readline/index.html#module-readline“ readline:GNU readline库”),以获取有关在安装Python时安装必要库的提示没有它们。

$ python3 cmd_do_help.py

(Cmd) <tab><tab>
EOF    greet  help
(Cmd) h<tab>
(Cmd) help

知道命令后,该命令处理方法的参数由前缀为complete_的方法处理完成。这允许完成处理程序(complete_)使用任意条件来组合可能完成的列表 (例如查询数据库或查看文件系统上的文件或目录) 。在下面的例程中,该程序具有一组硬编码的 “朋友”,与命名或匿名陌生人相比,他们收到的正式问候较少。真正的程序可能会将列表保存在某处,并读取一次,然后根据需要缓存
要扫描的内容。

cmd_arg_completion.py

# Set up gnureadline as readline if installed.
try:
    import gnureadline
    import sys
    sys.modules['readline'] = gnureadline
except ImportError:
    pass

import cmd

class HelloWorld(cmd.Cmd):

    FRIENDS = ['Alice', 'Adam', 'Barbara', 'Bob']

    def do_greet(self, person):
        "Greet the person"
        if person and person in self.FRIENDS:
            greeting = 'hi, {}!'.format(person)
        elif person:
            greeting = 'hello, {}'.format(person)
        else:
            greeting = 'hello'
        print(greeting)

    def complete_greet(self, text, line, begidx, endidx):
        if not text:
            completions = self.FRIENDS[:]
        else:
            completions = [
                f
                for f in self.FRIENDS
                if f.startswith(text)
            ]
        return completions

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

输入文本时,complete_greet()返回匹配的朋友列表。否则,将返回完整的朋友列表。

$ python3 cmd_arg_completion.py

(Cmd) greet <tab><tab>
Adam     Alice    Barbara  Bob
(Cmd) greet A<tab><tab>
Adam   Alice
(Cmd) greet Ad<tab>
(Cmd) greet Adam
hi, Adam!

如果给定的名字不在朋友列表中,则给出正式问候。

(Cmd) greet Joe
hello, Joe

覆盖基类方法

Cmd包括几种方法,这些方法可以作为执行操作或更改基类行为的钩子而重写。该示例不够全面的,但包含许多有用的方法。

cmd_illustrate_methods.py

# Set up gnureadline as readline if installed.
try:
    import gnureadline
    import sys
    sys.modules['readline'] = gnureadline
except ImportError:
    pass

import cmd

class Illustrate(cmd.Cmd):
    "Illustrate the base class method use."

    def cmdloop(self, intro=None):
        print('cmdloop({})'.format(intro))
        return cmd.Cmd.cmdloop(self, intro)

    def preloop(self):
        print('preloop()')

    def postloop(self):
        print('postloop()')

    def parseline(self, line):
        print('parseline({!r}) =>'.format(line), end='')
        ret = cmd.Cmd.parseline(self, line)
        print(ret)
        return ret

    def onecmd(self, s):
        print('onecmd({})'.format(s))
        return cmd.Cmd.onecmd(self, s)

    def emptyline(self):
        print('emptyline()')
        return cmd.Cmd.emptyline(self)

    def default(self, line):
        print('default({})'.format(line))
        return cmd.Cmd.default(self, line)

    def precmd(self, line):
        print('precmd({})'.format(line))
        return cmd.Cmd.precmd(self, line)

    def postcmd(self, stop, line):
        print('postcmd({}, {})'.format(stop, line))
        return cmd.Cmd.postcmd(self, stop, line)

    def do_greet(self, line):
        print('hello,', line)

    def do_EOF(self, line):
        "Exit"
        return True

if __name__ == '__main__':
    Illustrate().cmdloop('Illustrating the methods of cmd.Cmd')

cmdloop() 是解释器的主要处理循环。通常不需要重写它,因为 preloop()postloop()钩子都可用改变它的行为。

通过 cmdloop() 进行的每次迭代都会调用 onecmd() 将命令分派到对应的处理程序。用 parseline() 解析实际的输入行以创建一个包含命令和该行其余部分的元组。

如果该行为空,则调用emptyline()。默认实现再次运行前面的命令。如果该行包含命令,则首先调用precmd(),然后查找并调用处理程序。如果未找到,则调用default()。最后调用postcmd()

这是添加了print语句的示例会话:

$ python3 cmd_illustrate_methods.py

cmdloop(Illustrating the methods of cmd.Cmd)
preloop()
Illustrating the methods of cmd.Cmd
(Cmd) greet Bob
precmd(greet Bob)
onecmd(greet Bob)
parseline(greet Bob) => ('greet', 'Bob', 'greet Bob')
hello, Bob
postcmd(None, greet Bob)
(Cmd) ^Dprecmd(EOF)
onecmd(EOF)
parseline(EOF) => ('EOF', '', 'EOF')
postcmd(True, EOF)
postloop()

通过属性配置Cmd

除了前面介绍的方法外,还有一些用于控制命令解释器的属性。可以将提示设置为每次要求用户输入新命令时要打印的字符串。 简介是程序开始时显示的“欢迎”消息。 cmdloop() 接受该值的参数,或者可以直接在类上设置它。打印帮助时,doc_headermisc_headerundoc_headerruler属性用于格式化输出。
cmd_attributes.py

import cmd

class HelloWorld(cmd.Cmd):

    prompt = 'prompt: '
    intro = "Simple command processor example."

    doc_header = 'doc_header'
    misc_header = 'misc_header'
    undoc_header = 'undoc_header'

    ruler = '-'

    def do_prompt(self, line):
        "Change the interactive prompt"
        self.prompt = line + ': '

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    HelloWorld().cmdloop()

该示例类显示了一个命令处理程序,该命令处理程序使用户可以控制交互式会话的提示。

$ python3 cmd_attributes.py

Simple command processor example.
prompt: prompt hello
hello: help

doc_header
----------
help  prompt

undoc_header
------------
EOF

hello:

##运行Shell命令

为了补充标准命令处理,Cmd包括两个特殊的命令前缀。问号()等同于内置的help命令,并且可以以相同的方式使用。感叹号()映射到do_shell(),用于“脱壳”以运行其他命令,如本例所示。

cmd_do_shell.py

import cmd
import subprocess

class ShellEnabled(cmd.Cmd):

    last_output = ''

    def do_shell(self, line):
        "Run a shell command"
        print("running shell command:", line)
        sub_cmd = subprocess.Popen(line,
                                   shell=True,
                                   stdout=subprocess.PIPE)
        output = sub_cmd.communicate()[0].decode('utf-8')
        print(output)
        self.last_output = output

    def do_echo(self, line):
        """Print the input, replacing '$out' with
        the output of the last shell command.
        """
        # 显然不可靠
        print(line.replace('$out', self.last_output))

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    ShellEnabled().cmdloop()

echo命令实现将其参数中的字符串$ out替换为上一个shell命令的输出。

$ python3 cmd_do_shell.py

(Cmd) ?

Documented commands (type help <topic>):
========================================
echo  help  shell

Undocumented commands:
======================
EOF

(Cmd) ? shell
Run a shell command
(Cmd) ? echo
Print the input, replacing '$out' with
        the output of the last shell command
(Cmd) shell pwd
running shell command: pwd
.../pymotw-3/source/cmd

(Cmd) ! pwd
running shell command: pwd
.../pymotw-3/source/cmd

(Cmd) echo $out
.../pymotw-3/source/cmd

##替代输入

虽然Cmd()的默认模式是通过[readline](pymotw.com/3/readline/index.html#m... readline“ readline:GNU readline库”)库,也可以使用标准Unix shell重定向将一系列命令传递到标准输入中。

$ echo help | python3 cmd_do_help.py

(Cmd)
Documented commands (type help <topic>):
========================================
greet  help

Undocumented commands:
======================
EOF

(Cmd)

若要使程序直接读取脚本文件,可能需要进行其他一些更改。由于[readline](https://pymotw.com/3/readline/index.html#module-readline“ readline:GNU readline库”)与终端/ tty设备进行交互,而不是与标准输入流,要从文件中读取脚本时应将其禁用。另外,为避免打印多余的提示,可以将提示设置为空字符串。此示例显示如何打开文件并将其作为输入传递给HelloWorld示例的修改版本。

cmd_file.py

import cmd

class HelloWorld(cmd.Cmd):

    #禁用rawinput模块使用
    use_rawinput = False

    #读取每个命令后不显示提示
    prompt = ''

    def do_greet(self, line):
        print("hello,", line)

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    import sys
    with open(sys.argv[1], 'rt') as input:
        HelloWorld(stdin=input).cmdloop()

use_rawinput设置为False并且将prompt设置为空字符串,可以在输入文件上每行使用一个命令来调用脚本。

cmd_file.txt

greet
greet Alice and Bob

使用示例输入运行示例脚本将产生以下输出。

$ python3 cmd_file.py cmd_file.txt

hello,
hello, Alice and Bob

##来自sys.argv的命令

程序的命令行参数也可以作为解释器类的命令处理,而不是从控制台或文件中读取命令。要使用命令行参数,如本例所示,直接调用onecmd()

cmd_argv.py

import cmd

class InteractiveOrCommandLine(cmd.Cmd):
    """Accepts commands via the normal interactive
    prompt or on the command line.
    """

    def do_greet(self, line):
        print('hello,', line)

    def do_EOF(self, line):
        return True

if __name__ == '__main__':
    import sys
    if len(sys.argv) > 1:
        InteractiveOrCommandLine().onecmd(' '.join(sys.argv[1:]))
    else:
        InteractiveOrCommandLine().cmdloop()

由于onecmd()采用单个字符串作为输入,因此在传入之前必须将程序的参数连接在一起。

$ python3 cmd_argv.py greet Command-Line User

hello, Command-Line User

$ python3 cmd_argv.py

(Cmd) greet Interactive User
hello, Interactive User
(Cmd)

另请参见

-cmd的标准库文档
-cmd2-具有其他功能的cmd的直接替代。
-[GNU readline]

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/cmd-line...

译文地址:https://learnku.com/docs/pymotw/cmd-line...

上一篇 下一篇
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~