34

ユーザーにコマンドラインを表示するbashスクリプトを作成しています。

CLIコードは次のとおりです。

#!/bin/bash

cmd1() {
    echo $FUNCNAME: "$@"
}

cmd2() {
    echo $FUNCNAME: "$@"
}

cmdN() {
    echo $FUNCNAME: "$@"
}

__complete() {
    echo $allowed_commands
}

shopt -qs extglob

fn_hide_prefix='__'
allowed_commands="$(declare -f | sed -ne '/^'$fn_hide_prefix'.* ()/!s/ ().*//p' | tr '\n' ' ')"

complete -D -W "this should output these words when you hit TAB"

echo "waiting for commands"
while read -ep"-> "; do
    history -s $REPLY
    case "$REPLY" in
        @(${allowed_commands// /|})?(+([[:space:]])*)) $REPLY ;;
        \?) __complete ;;
        *) echo "invalid command: $REPLY" ;;
    esac
done

明確化:Bash4で作成およびテスト済み

つまり、「read -e」はreadline機能を提供し、コマンドを呼び出したり、入力行を編集したりすることができます。私ができないことは、 readlineのタブ補完を機能させることです。

私は2つのことを試しました:

  1. 想定される方法:ここで 機能することが報告されているbash組み込みの「complete」および「compgen」を使用します。更新:スクリプトで機能することは報告されていません

  2. この醜い回避策

スクリプト内で「complete」を使用すると、readlineが正しく動作しないのはなぜですか?インタラクティブモードでbashから試してみると動作します...

4

5 に答える 5

25

動作することがわかっているカスタム補完スクリプトを試して(毎日使用しています)、同じ問題に遭遇した後 (あなたのものと同じように装備したとき)、bash 4.1 のソースを調べてみることにしましたbash-4.1/builtins/read.def:edit_line()

old_attempted_completion_function = rl_attempted_completion_function;
rl_attempted_completion_function = (rl_completion_func_t *)NULL;
if (itext)
  {
    old_startup_hook = rl_startup_hook;
    rl_startup_hook = set_itext;
    deftext = itext;
  }
ret = readline (p);
rl_attempted_completion_function = old_attempted_completion_function;
old_attempted_completion_function = (rl_completion_func_t *)NULL;

beforereadline()が呼び出されると、何らかの理由で完了関数が null にリセットされるようです。これは、bash ハッキングの長いひげだけが知っている可能性があります。したがって、readビルトインでこれを行うと、単にハードコードされて無効になる場合があります。

編集:これについてもう少し:read組み込みの完了を停止するラッピングコードは、bash-2.05a と bash-2.05b の間で発生しました。そのバージョンのbash-2.05b/CWRU/changelogファイルで次のメモを見つけました。

  • edit_line (read -e によって呼び出される) は、rl_attempted_completion_function を NULL に設定することで readline のファイル名補完を行うようになりました。

これは従来の見落としだと思います。プログラム可能な補完は長い道のりを歩んできましたので、あなたがやっていることは役に立ちます。たぶん、あなたがやっていることでそれが実現可能であれば、彼らにそれを追加するか、自分でパッチを当てるように頼むことができます.

残念ながら、これまでに思いついたもの以外に別の解決策はありませんが、少なくともそれが で機能しない理由readはわかっています。

EDIT2:そうです、これは私がテストしたばかりのパッチで、「機能する」ようです。すべての単体テストと登録テストに合格し、パッチを適用した bash を使用してスクリプトを実行すると、期待どおりに次の出力が表示されます。

$ ./tabcompl.sh
waiting for commands
-> **<TAB>**
TAB     hit     output  should  these   this    when    words   you
->

ご覧のとおり、これらの 4 行とタイマー コードをコメント アウトして、rl_attempted_completion_functionいつread -t指定されたかをリセットし、タイムアウトが発生しましたが、これは不要になりました。Chet に何かを送信する場合は、rl_attempted_completion_function最初にジャンク全体を削除することをお勧めしますが、これにより、少なくともスクリプトが適切に動作できるようになります。

パッチ:

--- bash-4.1/builtins/read.def     2009-10-09 00:35:46.000000000 +0900
+++ bash-4.1-patched/builtins/read.def     2011-01-20 07:14:43.000000000 +0900
@@ -394,10 +394,12 @@
        }
       old_alrm = set_signal_handler (SIGALRM, sigalrm);
       add_unwind_protect (reset_alarm, (char *)NULL);
+/*
 #if defined (READLINE)
       if (edit)
        add_unwind_protect (reset_attempted_completion_function, (char *)NULL);
 #endif
+*/
       falarm (tmsec, tmusec);
     }

@@ -914,8 +916,10 @@
   if (bash_readline_initialized == 0)
     initialize_readline ();

+/*
   old_attempted_completion_function = rl_attempted_completion_function;
   rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+*/
   if (itext)
     {
       old_startup_hook = rl_startup_hook;
@@ -923,8 +927,10 @@
       deftext = itext;
     }
   ret = readline (p);
+/*
   rl_attempted_completion_function = old_attempted_completion_function;
   old_attempted_completion_function = (rl_completion_func_t *)NULL;
+*/

   if (ret == 0)
     return ret;

パッチを適用した bash は、スクリプトを使用する場所に配布するか、何らかの方法で利用できるようにする必要があることに注意してください...

于 2011-01-19T17:36:46.657 に答える
17

私はしばらくの間同じ問題に苦しんでいます、そして私はうまくいく解決策を持っていると思います、私の現実の世界の場合、私は可能な補完を生成するためにcompgenを使用しています。ただし、コアロジックを示す例を次に示します。

#!/bin/bash

set -o emacs;
tab() {
  READLINE_LINE="foobar"
  READLINE_POINT="${#READLINE_LINE}"
}
bind -x '"\t":"tab"';
read -ep "$ ";

emacsオプションを設定してキーバインドを有効にし、タブキーを関数にバインドしREADLINE_LINE、プロンプトの後に行を更新するように変更しREADLINE_POINT、行の新しい長い長さを反映するように設定します。

私のユースケースでは、実際にはCOMP_WORDS, COMP_CWORDCOMPREPLY変数を模倣していますが、を使用するときにカスタムタブ補完を追加する方法を理解するにはこれで十分read -epです。

readlineが端末をrawモードにし、入力をキャプチャしているため、プロンプト行を変更するために更新する必要がありREADLINE_LINEます(単一一致の完了)。プロンプトの前にstdin印刷に出力します。

于 2013-03-05T09:46:44.087 に答える
12

さて、私はついに答えに困惑したようですが、悲しいことに、実際には、「read -e」を介して接続する場合、readline は完全にはサポートされていません。

答えは、BASH メンテナの Chet Ramey によって与えられます。このスレッドでは、まったく同じ問題が解決されています。

私はコマンド ライン インタープリターを使用してスクリプトを作成していますが、1 つのことを除いて、ほとんどの操作 (履歴など) を実行できます。一部のコマンドではファイル名の補完がうまく機能しますが、他の補完オプションを使用したいと思います。「実際の」コマンドラインからはうまく動作しますが、「read -e, eval」ループでは正しく動作しません..

あなたはそれをすることができません。`read -e' は readline のデフォルト補完のみを使用します。

チェット

したがって、何か//暴言//が欠けていない限り、完全で適切なCLIユーザーインターフェイスの手段としてbashがプログラマーに「read -e」メカニズムを渡している間、基礎となるメカニズム(readline)であっても機能は損なわれます動作し、bash の残りの部分と完璧に統合します//暴言を終了します//

私は freenode の #bash で親切な人々に質問を公開し、rlferlwrapのような Readline ラッパーを試すように提案されました。

最後に、私は昨日メールで Chet 自身に連絡しました。彼はこれが設計によるものであり、プログラム可能な補完の唯一の使用例として「読み取り」に変更したくない、つまり、コマンドのリストをスクリプト ユーザーは、この作業に時間を費やす説得力のある理由のようには見えません。それにもかかわらず、誰かが実際に仕事をする場合、彼は確かに結果を見るだろうと彼は述べた.

私見ですが、わずか 5 行のコードで完全な CLI を起動できるという努力の価値を考慮していないのは間違いです。

この文脈では、サイモンの答えは素晴らしく、適切な場所にあると思います。私はあなたの手順に従おうとします.おそらく運が良ければ、より多くの情報を得ることができます. ただし、C でハックしないようになってからしばらく経ちましたが、実装するために把握しなければならないコードの量は些細なものではないと思います。でもとにかくやってみます。

于 2011-01-19T22:02:53.370 に答える
2

これがOPの質問に正確に答えるかどうかはわかりませんが、 を押したときに表示されるように、bash既知の実行可能コマンドのデフォルトのタブ補完を取得するために、どのコマンドを使用できるかを探していました( に従って) 。私は最初にこの質問に導かれたので (関連していると思います)、ここにメモを投稿したいと思いました。$PATHTAB

たとえば、私のシステムでは、次のように入力するluaと次のようになりTABます。

$ lua <TAB>
lua lua5.1 luac luac5.1 lualatex luatex luatools

bash組み込み ( #949006 使用可能なすべてのコマンドとエイリアスを一覧表示する Linux コマンド を参照) があり、- と呼ばれ、対話型の場合とcompgen同じ文字列を入力して、同じ結果を得ることができます。lua押されたTAB:

$ compgen -c lua
luac
lua5.1
lua
luac5.1
luatex
lualatex
luatools

...そしてそれはまさに私が探していたものです:)

これが誰かに役立つことを願っています、
乾杯!

于 2012-07-03T18:03:08.550 に答える
1

それだけの労力を費やすのであれば、フォーク 1 つまたは 2 つのコストを追加して、必要なものすべてを提供できる以上のものを使用しないでください。 https://github.com/hanslub42/rlwrap

#!/bin/bash

which yum && yum install rlwrap
which zypper && zypper install rlwrap
which port && port install rlwrap
which apt-get && apt-get install rlwrap

REPLY=$( rlwrap -o cat )

または、マニュアルページに記載されているように:

シェル スクリプトではrlwrap、「ワンショット」モードで、read

order=$(rlwrap -p Yellow -S 'Your pizza? ' -H past_orders -P Margherita -o cat)
于 2012-05-16T02:05:49.737 に答える