Автор оригинала: Doug Hellmann.
Цель:
Создавайте линейно-ориентированные командные процессоры.
Модуль cmd
содержит один открытый класс, Cmd
, предназначенный для использования в качестве базового класса для интерактивных оболочек и других интерпретаторов команд. По умолчанию он использует строку чтения для интерактивной обработки запросов, редактирования командной строки и завершения команд.
Команды обработки
Интерпретатор команд, созданный с помощью cmd
, использует цикл для чтения всех строк из входных данных, их анализа и отправки команды соответствующему обработчику команд . Строки ввода разбираются на две части: команду и любой другой текст в строке. Если пользователь вводит foo bar
, а класс интерпретатора включает метод с именем do_foo ()
, он вызывается с "bar"
в качестве единственного аргумент.
Маркер конца файла отправляется в do_EOF ()
. Если обработчик команд возвращает истинное значение, программа завершится без ошибок. Итак, чтобы обеспечить чистый способ выхода из интерпретатора, убедитесь, что реализован do_EOF ()
и он возвращает True.
Этот простой пример программы поддерживает команду “приветствовать”:
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): help Undocumented commands: EOF greet
Команда help
встроена в Cmd
. Без аргументов help
показывает список доступных команд. Если входные данные включают имя команды, выходные данные будут более подробными и ограничены деталями этой команды, если они доступны.
Если команда – greet
, для ее обработки вызывается do_greet ()
:
(Cmd) greet hello
Если в классе нет определенного обработчика для команды, вызывается метод default ()
со всей строкой ввода в качестве аргумента. Встроенная реализация 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): greet help Undocumented commands: EOF (Cmd) help greet greet [person] Greet the named person
В выходных данных показан один необязательный аргумент для greet
, person
. Хотя аргумент не является обязательным для команды, существует различие между командой и методом обратного вызова. Метод всегда принимает аргумент, но иногда значение представляет собой пустую строку. Обработчик команды может определить, допустим ли пустой аргумент, или выполнить дальнейший синтаксический анализ и обработку команды. В этом примере, если указано имя человека, приветствие персонализировано.
(Cmd) greet Alice hi, Alice (Cmd) greet hi
Независимо от того, задан ли аргумент пользователем или нет, значение, передаваемое обработчику команд, не включает саму команду. Это упрощает синтаксический анализ в обработчике команд, особенно если требуется несколько аргументов.
Живая помощь
В предыдущем примере форматирование текста справки оставляет желать лучшего. Поскольку он исходит из строки документации, он сохраняет отступ от исходного файла. Источник можно изменить, чтобы удалить лишнее пробел, но это оставит код приложения плохо отформатированным. Лучшее решение – реализовать обработчик справки для команды greet
с именем help_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('\n'.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
включает поддержку завершения команд на основе имен команд с методами обработчика. Пользователь запускает завершение, нажимая клавишу табуляции в строке ввода. Если возможно несколько завершений, при двойном нажатии табуляции будет напечатан список опций.
Примечание
Библиотеки GNU, необходимые для readline
, по умолчанию доступны не на всех платформах. В этих случаях завершение табуляции может не работать. См. В строке чтения советы по установке необходимых библиотек, если в вашей установке Python их нет.
$ python3 cmd_do_help.py (Cmd)EOF greet help (Cmd) h (Cmd) help
Как только команда известна, завершение аргумента обрабатывается методами с префиксом 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) greetAdam Alice Barbara Bob (Cmd) greet A Adam Alice (Cmd) greet Ad (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, introNone): 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 Through
В дополнение к методам, описанным ранее, существует несколько атрибутов для управления интерпретаторами команд. prompt
можно задать как строку, которая будет печататься каждый раз, когда пользователя просят ввести новую команду. intro
– это «приветственное» сообщение, выводимое в начале программы. 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:
Запуск команд оболочки
В дополнение к стандартной обработке команд 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, shellTrue, stdoutsubprocess.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. """ # Obviously not robust print(line.replace('$out', self.last_output)) def do_EOF(self, line): return True if __name__ '__main__': ShellEnabled().cmdloop()
Эта реализация команды echo
заменяет строку $ out
в своем аргументе выводом предыдущей команды оболочки.
$ python3 cmd_do_shell.py (Cmd) ? Documented commands (type help): 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, также можно передать серию команд в стандартный ввод, используя стандартное перенаправление оболочки Unix.
$ echo help | python3 cmd_do_help.py (Cmd) Documented commands (type help): greet help Undocumented commands: EOF (Cmd)
Чтобы программа читала файл сценария напрямую, может потребоваться еще несколько изменений. Поскольку readline взаимодействует с устройством терминал/tty, а не со стандартным потоком ввода, его следует отключить, когда сценарий будет читать из файла. Кроме того, чтобы избежать вывода лишних подсказок, подсказку можно задать пустой строкой. В этом примере показано, как открыть файл и передать его в качестве входных данных в измененную версию примера HelloWorld
.
cmd_file.py
import cmd class HelloWorld(cmd.Cmd): # Disable rawinput module use use_rawinput False # Do not show a prompt after each command read 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(stdininput).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
на