Рубрики
Без рубрики

cmd – командные процессоры с линейной ориентацией

Автор оригинала: 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) greet 
Adam     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)

Смотрите также