Лучшая обработка KeyboardInterrupt в интерпретаторе командной строки cmd.Cmd

При использовании python cmd.Cmd для создания пользовательского интерфейса командной строки, как мне сообщить обработчику прервать текущую строку и дать мне новое приглашение?

Вот минимальный пример:

# console_min.py
# run: 'python console_min.py'

import cmd, signal

class Console(cmd.Cmd):
    def __init__(self):
        cmd.Cmd.__init__(self)
        self.prompt = "[test] "
        signal.signal(signal.SIGINT, handler)

    def do_exit(self, args):
        return -1

    def do_EOF(self, args):
        return self.do_exit(args)

    def preloop(self):
        cmd.Cmd.preloop(self)
        self._hist    = []
        self._locals  = {}
        self._globals = {}

    def postloop(self):
        cmd.Cmd.postloop(self)
        print "Exiting ..."

    def precmd(self, line):
        self._hist += [ line.strip() ]
        return line

    def postcmd(self, stop, line):
        return stop

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

    def handler(self, signum, frame):
        # self.emptyline() does not work here
        # return cmd.Cmd.emptyline(self) does not work here
        print "caught ctrl+c, press return to continue"

if __name__ == '__main__':
    console = Console()
    console.cmdloop()

Дальнейшая помощь приветствуется.

Оригинальный вопрос и более подробная информация: [В настоящее время приведенные ниже предложения были включены в этот вопрос - все еще ищут ответ. Обновлено для исправления ошибки.]

С тех пор я попытался переместить обработчик на функцию вне цикла, чтобы убедиться, что она более гибкая, но она не выглядит.

Я использую python cmd.Cmd module, чтобы создать собственный интерпретатор командной строки для управления взаимодействием с некоторым программным обеспечением. Я часто нажимаю ctrl + c, ожидая популярного поведения в оболочке, возвращающего новое приглашение, не действуя ни на какую команду. Однако он просто выходит. Я попытался поймать исключения KeyboardInterrupt в разных точках кода (preloop и т.д.) Безрезультатно. Я немного читал в sigints, но не совсем понимаю, как это вписывается здесь.

UPDATE: из приведенных ниже предложений я попытался реализовать сигнал и смог сделать так, чтобы ctrl + c не выходил из CLI, но печатает сообщение. Тем не менее, моя новая проблема заключается в том, что я не могу описать функцию обработчика (см. Ниже), чтобы сделать многое рядом с печатью. Я хотел бы, чтобы ctrl + c в основном прервал текущую строку и дал мне новое приглашение.

Ответ 1

В настоящее время я работаю над созданием оболочки с помощью модуля Cmd. Я столкнулся с той же проблемой и нашел решение.

Вот код:

class Shell(Cmd, object)
...
    def cmdloop(self, intro=None):
        print(self.intro)
        while True:
            try:
                super(Shell, self).cmdloop(intro="")
                break
            except KeyboardInterrupt:
                print("^C")
...

Теперь у вас есть правильный обработчик KeyboardInterrupt (он же CTRL-C) внутри оболочки.

Ответ 2

Вместо использования обработки сигналов вы можете просто поймать KeyboardInterrupt, который поднимает cmd.Cmd.cmdloop(). Конечно, вы можете использовать обработку сигналов, но это не требуется.

Запустите вызов cmdloop() в цикле while, который перезапускает себя при исключении KeyboardInterrupt, но заканчивается должным образом из-за EOF.

import cmd
import sys

class Console(cmd.Cmd):
    def do_EOF(self,line):
        return True
    def do_foo(self,line):
        print "In foo"
    def do_bar(self,line):
        print "In bar"
    def cmdloop_with_keyboard_interrupt(self):
        doQuit = False
        while doQuit != True:
            try:
                self.cmdloop()
                doQuit = True
            except KeyboardInterrupt:
                sys.stdout.write('\n')

console = Console()

console.cmdloop_with_keyboard_interrupt()

print 'Done!'

Выполнение CTRL-c просто печатает новое приглашение в новой строке.

(Cmd) help

Undocumented commands:
======================
EOF  bar  foo  help

(Cmd) <----- ctrl-c pressed
(Cmd) <------ctrl-c pressed 
(Cmd) ddasfjdfaslkdsafjkasdfjklsadfljk <---- ctrl-c pressed
(Cmd) 
(Cmd) bar
In bar
(Cmd) ^DDone!

Ответ 3

В ответ на следующий комментарий в этом ответе:

Это похоже на схождение в решении, но я не знаю, как его интегрировать в мой собственный код (см. выше). Я должен выяснить линию "супер". Мне нужно попытаться заставить это работать в какой-то момент в будущем.

super() будет работать в этом ответе, если у вас есть класс, расширяющий object в дополнение к cmd.Cmd. Вот так:

class Console(cmd.Cmd, object):

Ответ 4

Я хотел сделать то же самое, поэтому я искал его и создал базовый скрипт для понимания целей, мой код можно найти на моем GitHub

Итак, в вашем коде,

вместо этого

if __name__ == '__main__':
    console = Console()
    console.cmdloop()

Используйте это,

if __name__ == '__main__':
    console = Console()
    try: 
       console.cmdloop()
    except KeyboardInterrupt:
       print ("print sth. ")
       raise SystemExit

Надеюсь, это поможет вам. ну, это сработало для меня. 😀

Ответ 5

Вы можете поймать сигнал CTRL-C с помощью обработчика сигнала. Если вы добавите код ниже, интерпретатор откажется покинуть CTRL-C:

import signal

def handler(signum, frame):
    print 'Caught CTRL-C, press enter to continue'

signal.signal(signal.SIGINT, handler)

Если вы не хотите нажимать ENTER после каждого CTRL-C, просто дайте обработчику ничего не делать, что не приведет к улавливанию сигнала:

def handler(signum, frame):
    """ just do nothing """

Ответ 6

Вы можете проверить модуль signal: http://docs.python.org/library/signal.html

import signal

oldSignal = None

def handler(signum, frame):
    global oldSignal
    if signum == 2:
        print "ctrl+c!!!!"
    else:
        oldSignal()

oldSignal = signal.signal(signal.SIGINT, handler)

Ответ 7

Просто добавьте это внутри класса Console(cmd.Cmd):


    def cmdloop(self):
        try:
            cmd.Cmd.cmdloop(self)
        except KeyboardInterrupt as e:
            self.cmdloop()

Забудьте все остальное. Это работает. Он не создает петли внутри петель. Когда он ловит KeyboardInterrupt, он вызывает do_EOF, но только выполняет первую строку; так как ваша первая строка в do_EOF является return do_exit, это нормально.
do_exit вызывает postloop.
Однако, опять же, он выполняет только первую строку после cmd.Cmd.postloop(self). В моей программе это "\n". Как ни странно, если вы SPAM ctrl + C, вы, в конце концов, увидите, что он печатает вторую строчку, обычно печатаемую только на выходе ACTUAL (ctrl + Z, затем введите или введите в выход).

Ответ 8

Я предпочитаю сигнальный метод, но с пропуском. На самом деле не заботятся о том, чтобы вызвать у пользователя что-либо в моей среде оболочки.

import signal

def handler(signum, frame):
    pass

signal.signal(signal.SIGINT, handler)