2012-01-11 73 views
8

使用python的cmd.Cmd創建自定義CLI時,如何告訴處理程序中止當前行並給我一個新提示?更好地處理cmd.Cmd命令行解釋器中的KeyboardInterrupt

這裏是一個小例子:

# 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,期望流行的類似shell的行爲返回一個新的提示,而不用處理任何鍵入的命令。但是,它只是退出。我試圖在代碼中的各個點(preloop等)捕獲KeyboardInterrupt異常無濟於事。我讀過sigints,但不太清楚這是否適合這裏。

UPDATE:從下面的建議,我試圖執行信號,並能做到這樣CTRL + C,現在不退出CLI但並打印信息。然而,我的新問題是,我似乎無法告訴處理函數(見下文)在印刷旁邊做很多事情。我想ctrl + c基本上中止當前行,並給我一個新的提示。

回答

8

我目前正在制定一項創建一個Shell的使用日e Cmd模塊。我遇到了同樣的問題,我找到了解決方案。

下面是代碼:

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

現在你有殼內的適當一個KeyboardInterrupt(又名CTRL-C)處理。

+0

這*看起來似乎在融合在一個解決方案上,但我不知道如何將它集成到我自己的代碼中(上圖)。我必須弄清楚'超級'線。我需要嘗試在將來的某個時刻使這項工作順利進行。 – 2012-12-05 16:31:57

+0

在Python 2.x中,'cmd.Cmd'是一個*舊式class *,所以'super'不起作用。看[這個問題](http://stackoverflow.com/questions/770134)。 – 2015-12-11 18:19:39

+1

爲什麼你必須自己調用'postloop()'? – 2015-12-11 18:37:26

-1

可以趕上CTRL-C signalsignal handler。如果您添加下面的代碼,解釋拒絕退出的CTRL-C:

import signal 

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

signal.signal(signal.SIGINT, handler) 

如果你不想按每個CTRL-C後回車,只是讓處理器做什麼,這將陷阱信號無任何影響:

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

*什麼都不做*可以用Python中的'pass'來表示。 – 2016-07-15 12:06:33

-1

您可以檢查出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) 
3

而不是使用信號處理,你可以只趕上KeyboardInterruptcmd.Cmd.cmdloop()引起的。您當然可以使用信號處理,但不是必需的。

在while循環中運行呼叫cmdloop(),該循環在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! 
+1

這是正確的解決方案。如果KeyboardInterrupt被捕獲,它可以將'self.intro'設置爲'None'。 – 2016-11-26 09:44:05

+0

'doQuit'循環可以放在'__init __()'的末尾,而不是創建一個'cmdloop_with_keyboard_interrupt()'方法,然後控制檯就會在調用Console()時運行。 – 2017-02-16 02:40:12

+0

而不是'doQuit',在'cmdloop()'成功完成後使用'while:'然後'break'會更簡單一些。 – jamesdlin 2017-06-23 00:36:08

-2

就在班級裏面Console(cmd.Cmd)添加此:


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

忘記所有其他的東西。這工作。它不會在循環內部形成循環。當它捕獲KeyboardInterrupt時,它調用do_EOF,但只執行第一行;因爲你的第一行do_EOF是返回do_exit這很好。
do_exit來電postloop
但是,它只是執行cmd.Cmd.postloop(self)後的第一行。在我的程序中,這是打印「\ n」。奇怪的是,如果你SPAM ctrl + C你最終會看到它打印第二行通常只打印在實際出口(按Ctrl + Z然後輸入,或輸入退出)。

+0

這是我最終採取的方法。唯一可能的問題是,如果'intro'是我上面示例中設置的一個屬性,它將不斷打印介紹(從下面的@ seb的評論中得到啓發)。所以我希望可以編輯你的評論來反映這一點。 – 2014-06-10 19:08:13

+5

遞歸限制任何人?如果兩個'KeyboardInterrupt'太近,也會出現問題。 – Veedrac 2014-06-10 19:15:21

-2

我更喜歡信號方法,但只是通過。不關心在我的shell環境中用什麼提示用戶。

import signal 

def handler(signum, frame): 
    pass 

signal.signal(signal.SIGINT, handler) 
+0

這完全不符合OP的要求 - 即忽略當前寫入提示的輸入。 – 2016-11-26 09:47:17

0

針對以下注釋中this answer

這似乎是一個解決方案趨同,但我不知道如何將其整合到自己的代碼(上圖)。我必須弄清楚'超級'線。我需要嘗試在將來的某個時刻使這項工作順利進行。

super()會在這個答案的工作,如果你有你的類除了延長objectcmd.Cmd。像這樣:

class Console(cmd.Cmd, object):