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
输出显示 person
是greet
的一个可选参数。尽管对命令来说键入参数是可选的,但该命令的处理方法是始终需要参数的。当不主动键入参数时,方法的参数是一个空字符串。然后由命令处理程序来确定空参数是否有效,或者对命令进行任何进一步的解析和处理。在此示例中,如果提供了一个人的名字,则问候语是个性化的。
(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_header
,misc_header
,undoc_header
和ruler
属性用于格式化输出。
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)
另请参见
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。