16

readline を使用して、次のようなコードを取得しました。

#include <errno.h>
#include <error.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <readline/readline.h>
#include <readline/history.h>

void handle_signals(int signo) {
  if (signo == SIGINT) {
    printf("You pressed Ctrl+C\n");
  }
}

int main (int argc, char **argv)
{
   //printf("path is: %s\n", path_string);
  char * input;
  char * shell_prompt = "i-shell> ";
  if (signal(SIGINT, handle_signals) == SIG_ERR) {
    printf("failed to register interrupts with kernel\n");
  }

  //set up custom completer and associated data strucutres
  setup_readline();

  while (1) 
  {
    input = readline(shell_prompt);
    if (!input)
      break;
    add_history(input);

    //do something with the code
    execute_command(input);

  }  
  return 0;
}

インターセプトSIGINT(ユーザーが を押すCtrl+C) するように設定したので、シグナル ハンドラーhandle_signals()が動作していることがわかります。ただし、制御が に戻るとreadline()、入力前と同じ行のテキストが使用されます。私がやりたいのは、readline が現在のテキスト行を「キャンセル」して、BASH シェルのように新しい行を表示することです。そのようなもの:

i-shell> bad_command^C
i-shell> _

これを機能させる可能性はありますか?私が読んだメーリング リストのどこかで を使用することが言及されていましlongjmp(2)たが、それは実際には良い考えとは思えません。

4

4 に答える 4

9

siglongjmpジャンプを行う前に、信号マスクで受信した信号のブロックを解除することが目的であることが判明するまで、最初はjanchetaの答えに戸惑いました。シグナルは、ハンドラーがそれ自体を中断しないように、シグナル ハンドラーのエントリでブロックされます。通常の実行を再開するときにシグナルをブロックしたままにしたくないため、siglongjmp代わりにを使用しlongjmpます。AIUI、これは省略形です。 のsigprocmask後にを呼び出すこともできます。longjmpこれは glibc が で行っているようsiglongjmpです。

readline()と. _ malloc_ freeasync-signal-unsafe 関数がグローバル状態を変更しているときにシグナルを受信した場合mallocfreeシグナル ハンドラーから飛び出すと、何らかの破損が発生する可能性があります。しかし、Readline はこれに注意する独自のシグナルハンドラをインストールします。フラグを設定して終了するだけです。Readline ライブラリが再び制御を取得すると (通常は 'read()' 呼び出しが中断された後)、それが呼び出さRL_CHECK_SIGNALS()れ、保留中のシグナルが を使用してクライアント アプリケーションに転送されますkill()。したがってsiglongjmp()、呼び出しを中断したシグナルのシグナルハンドラーを終了するために使用しreadline()ても安全です。シグナルは、async-signal-unsafe 関数中に受信されていないことが保証されています。

実際には、 の直前に呼び出されるmalloc()free()の中rl_set_prompt()でへの呼び出しがいくつかあるため、これは完全に正しいわけではありません。この呼び出し順序を変更する必要があるのだろうか。いずれにせよ、競合状態の確率は非常にわずかです。readline()rl_set_signals()

Bash のソース コードを見たところ、SIGINT ハンドラから飛び出しているようです。

使用できる別の Readline インターフェイスは、コールバック インターフェイスです。これは、一度に複数のファイル記述子をリッスンする必要がある Python や R などのアプリケーションで使用されます。たとえば、コマンド ライン インターフェイスがアクティブなときにプロット ウィンドウのサイズが変更されているかどうかを確認するために使用されます。彼らはこれをselect()ループで行います。

これは、コールバック インターフェイスで SIGINT を受信したときに Bash のような動作を取得するために何をすべきかについてのアイデアを提供する Chet Ramey からのメッセージです。

https://lists.gnu.org/archive/html/bug-readline/2016-04/msg00071.html

メッセージは、次のようなことを行うことを提案しています。

    rl_free_line_state ();
    rl_cleanup_after_signal ();
    RL_UNSETSTATE(RL_STATE_ISEARCH|RL_STATE_NSEARCH|RL_STATE_VIMOTION|RL_STATE_NUMERICARG|RL_STATE_MULTIKEY);
    rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
    printf("\n");

SIGINT を受信したら、フラグを設定し、後でselect()ループ内でフラグを確認できselect()ますerrno==EINTR。フラグが設定されていることがわかった場合は、上記のコードを実行します。

私の意見では、Readline は、独自の SIGINT 処理コードで上記のフラグメントのようなものを実行する必要があります。現在のところ、多かれ少なかれ最初の 2 行だけが実行されます。そのため、インクリメンタル検索やキーボード マクロなどは ^C によってキャンセルされますが、行はクリアされません。

別の投稿者は、"Call rl_clear_signals()" と言っていましたが、これはまだ私を混乱させます。readline()試したことはありませんが、(1) Readline のシグナル ハンドラーがとにかくシグナルを転送し、(2)エントリ時にシグナル ハンドラーをインストールする (終了時にそれらをクリアする) ことを考えると、それがどのように達成されるかわかりません。であるため、通常、Readline コードの外ではアクティブになりません。

于 2016-04-30T21:50:35.633 に答える
7

ジャンプの作成はハックでエラーが発生しやすいように思えます。このサポートを追加しようとしていたシェルの実装では、この変更が許可されていませんでした。

幸いなことにreadline、より明確な代替ソリューションがあります。私のSIGINTハンドラーは次のようになります。

static void
int_handler(int status) {
    printf("\n"); // Move to a new line
    rl_on_new_line(); // Regenerate the prompt on a newline
    rl_replace_line("", 0); // Clear the previous text
    rl_redisplay();
}

これを機能させるために他のコードを追加する必要はありませんでした — グローバル変数も、設定のジャンプもありません。

于 2017-01-29T05:17:38.673 に答える
4

コールしrl_clear_signals()ます。

これにより、インストールされているシグナル ハンドラーが無効になりますlibreadline。処理するものSIGINTは、プロンプトを復元するという観察された動作に責任があります。

readline()シグナル処理の管理方法の詳細については、こちらを参照してください。

于 2013-05-30T16:05:29.260 に答える