2016-05-22 12 views
5

cmdモジュールを使用してPython 3.5アプリケーションを作成しました。私が実装したい最後のことは、CTRL-C(sigint)シグナルの適切な処理です。私はそれが多かれ少なかれバッシュはそれをしないように動作したいと思います:Python cmdモジュールでCTRL-Cを処理する

  • 印刷^ C時点でカーソルがバッファ
  • 明らかである入力テキストが削除されるよう
  • は次へスキップライン、プロンプトを出力し、基本的に入力

待つ:ここ

/test $ bla bla bla| 
# user types CTRL-C 
/test $ bla bla bla^C 
/test $ 

は実行可能として簡略化されたコードでありますサンプル:

import cmd 
import signal 


class TestShell(cmd.Cmd): 
    def __init__(self): 
     super().__init__() 

     self.prompt = '$ ' 

     signal.signal(signal.SIGINT, handler=self._ctrl_c_handler) 
     self._interrupted = False 

    def _ctrl_c_handler(self, signal, frame): 
     print('^C') 
     self._interrupted = True 

    def precmd(self, line): 
     if self._interrupted: 
      self._interrupted = False 
      return '' 

     if line == 'EOF': 
      return 'exit' 

     return line 

    def emptyline(self): 
     pass 

    def do_exit(self, line): 
     return True 


TestShell().cmdloop() 

これはほとんど動作します。 CTRL-Cを押すと^ Cがカーソルに表示されますが、まだEnterを押す必要があります。次に、precmdメソッドはハンドラによって設定されたself._interruptedフラグに注目し、空の行を返します。これは私がそれを取ることができる限りですが、どういうわけかその入力を押す必要はありません。

私は何とか強制的に返すようにする必要があると思います。input()は誰でもアイデアを持っていますか?

+0

これは、実行可能なサンプルを小さくするのに役立ちます。あなたのコードをどのように実行していなくても、 '' \ n ''や空白を' 'stdout''に書くだけで十分でしょうか?それはシグナルハンドラが ''^C ''の末尾に改行を張ったときです。 – tijko

+1

@tijkoスニペットを実行可能なサンプルに置き換えてポストを編集しました。私はstdoutに改行を書くことを試みました(実際にはprintメソッドによってデフォルトで行われますが)、それはうまくいかず、どうしてでしょうか? stdinはstdoutから供給される必要がありますが、そうでない場合はパイプではありません。 – wujek

+0

ええ、あなたは正しいです私はコマンドラインstdinプロンプトの観点から考えていませんでした。 @DanGetzはあなたのニーズに合った答えを持っています。そのクリーナーも、信号ハンドラやフラグを設定する必要はありません。あなたのコメントの後、私はstdinのリダイレクトラインに沿って考えるようになりましたが、必要に応じて調整することができます。 – tijko

答えて

3

Ctrl-Cで必要な動作を達成するためのいくつかの方法が見つかりました。

設定use_rawinput=Falsecmd.Cmdのパブリックインターフェイスにstdin

この1本のスティック(...多かれ少なかれ)を交換してください。残念ながら、これはreadlineのサポートを無効にします。

use_rawinputをfalseに設定し、stdinCmd.__init__()に置き換えて別のファイルのようなオブジェクトを渡すことができます。実際には、readline()のみがこのオブジェクトに対して呼び出されます。だから、あなたはあなたの代わりにしたいKeyboardInterruptをキャッチし、行動を実行stdinためのラッパーを作成することができます

class _Wrapper: 

    def __init__(self, fd): 
     self.fd = fd 

    def readline(self, *args): 
     try: 
      return self.fd.readline(*args) 
     except KeyboardInterrupt: 
      print() 
      return '\n' 


class TestShell(cmd.Cmd): 

    def __init__(self): 
     super().__init__(stdin=_Wrapper(sys.stdin)) 
     self.use_rawinput = False 
     self.prompt = '$ ' 

    def precmd(self, line): 
     if line == 'EOF': 
      return 'exit' 
     return line 

    def emptyline(self): 
     pass 

    def do_exit(self, line): 
     return True 


TestShell().cmdloop() 

私はターミナルでこれを実行すると、Ctrlキーを押しながらCは^C示し、新しい行に切り替わります。

モンキーパッチinput()

あなたがinput()の結果をしたい場合は、Ctrl + Cキーで異なる振る舞いをしたい除いて、それを行うための一つの方法ではなくinput()の異なる機能を使用することです:

def my_input(*args): # input() takes either no args or one non-keyword arg 
    try: 
     return input(*args) 
    except KeyboardInterrupt: 
     print('^C') # on my system, input() doesn't show the ^C 
     return '\n' 

input = my_inputを盲目的に設定した場合、my_input()input()(現在はそれ自体)となるため、無限再帰が発生します。しかし、それは修正可能だし、あなたがCmd.cmdloop()中に変更さinput()メソッドを使用するcmdモジュールで__builtins__辞書にパッチを適用することができます

def input_swallowing_interrupt(_input): 
    def _input_swallowing_interrupt(*args): 
     try: 
      return _input(*args) 
     except KeyboardInterrupt: 
      print('^C') 
      return '\n' 
    return _input_swallowing_interrupt 


class TestShell(cmd.Cmd): 

    def cmdloop(self, *args, **kwargs): 
     old_input_fn = cmd.__builtins__['input'] 
     cmd.__builtins__['input'] = input_swallowing_interrupt(old_input_fn) 
     try: 
      super().cmdloop(*args, **kwargs) 
     finally: 
      cmd.__builtins__['input'] = old_input_fn 

    # ... 

これはためinput()すべてCmdオブジェクトだけではなく、TestShellオブジェクトを変更することに注意してください。これはあなたに受け入れられない場合は、可能性...

コピーCmd.cmdloop()ソースとそれに

を変更し、あなたがそれをサブクラス化しているので、あなたはcmdloop()あなたが欲しいものを行うことができます。 「何でもしたいもの」には、Cmd.cmdloop()の部分をコピーして他の部分を書き換えることができます。 input()への呼び出しを別の関数への呼び出しに置き換えるか、KeyboardInterruptをキャッチして、書き直したcmdloop()のところで処理してください。

Pythonの新しいバージョンで変更される基本的な実装が怖い場合は、cmdモジュール全体を新しいモジュールにコピーして、必要なものを変更することができます。

+0

あなたのソリューションをありがとう。残念ながら、私はreadlineを使用しており、かなり重要です。 – wujek

+0

@wujekええ、私は多くを期待した。さて、私はそれを追加するつもりで、私はそれを処理するために少し面白い方法があります。 –

+0

私はオプション3をとり、ちょうどcmdloop()コードをコピーして変更しました。私はそれが最もハックな解決策だと思う...ありがとう! – wujek

関連する問題