シェルプロンプトにコマンドが入力されたかどうかを検出する方法

シェルプロンプトにコマンドが入力されたかどうかを検出する方法

直前のコマンド プロンプトに提供されたものに応答する を書きたいですPROMPT_COMMAND。たとえば、次のように、拡張的で情報豊富なプロンプトとシンプルでコンパクトなプロンプトを切り替えるには、次のようにします。

mikemol@serenity ~ $ echo hi
hi
$ echo ho
ho
$ echo hum
hum
$
mikemol@serenity ~ $

値は空でない場合にのみシェルの履歴に追加されるように見えるため、最新の履歴エントリが空であるかどうかを単純にテストすることはできません。実行してset | grep some_commandsome_command何も返されないため、その情報を含む環境変数は存在しないようです。

私は主に を使用していますbashが、POSIX 互換のソリューションや他のシェルについても興味があります。

答え1

結局、まったく必要ありませんでしたPROMPT_COMMAND。正しい方向を指し示してくれたクリストファーに感謝します。

代わりに、次のファイルを検討してくださいps1.prompt

${__cmdnbary[\#]+$(
    echo '\u@\h: \w' # Your fancy prompt goes here, with all the usual special characters available.
) }${__cmdnbary[\#]=}\$

これを次のように入力しますPS1:

PS1=$(cat ps1.prompt)

(必ずしもこの方法で行う必要はありませんが、イラストや編集には便利だと感じました。)

そして、次のことがわかります。

mikemol@zoe:~$ echo hi
hi
mikemol@zoe:~$ echo ho
ho
mikemol@zoe:~$ echo hum
hum
mikemol@zoe:~$ 
mikemol@zoe:~$ PS1=$(cat ps1.prompt)
$ 
mikemol@zoe: ~ $ echo hi
hi
$ echo ho
ho
$ echo hum
hum
$ 
mikemol@zoe: ~ $ 

配列ハックを使用していますここで実証bashただし、の${parameter:-word}パラメータ置換を使用する代わりに、 を使用して${parameter+word}、以前にコマンドが実行されていない場合にのみトリガーします。

論理的に二重否定を使用しなければならないため、これには多少の説明が必要です。

仕組み${__cmdnbary[\#]-word}${__cmdnbary[\#]=}

元の配列ハックのデモでは、 構造が使用されていました。(わかりやすくするためにに${__cmdnbary[\#]-word}${__cmdnbary[\#]=}置き換えました)。パラメータ展開と配列に詳しくない場合 (私もそうでした)、何が起こっているのかまったくわかりません。$?word

まず理解する\#

パーマニュアル:

\#このコマンドのコマンド番号

...

コマンド番号は、現在のシェル セッション中に実行されたコマンドのシーケンス内の位置です。

この手段は\#変わるだけだもし、もし、コマンドが実行されます。ユーザーがプロンプトで空白行を入力した場合、コマンドは実行されず、\#変更されません。

${__cmdnbary[#]=}に空の文字列を設定する

${__cmdnbary[\#]=}パラメータ拡張を使用します。マニュアル:

${parameter:=word}

デフォルト値を割り当てるパラメータが設定されていないか null の場合、 word の展開がパラメータに割り当てられます。その後、パラメータの値が置き換えられます。

したがって、__cmdnbary[\#]が設定されていないか null の場合、この構造は空の文字列 (wordこの場合は は空の文字列) を割り当て、出力内の構造全体が同じ空の文字列に置き換えられます。

__cmdnbary[\#]意思いつも# は単調であるため、最初に見たときは未設定または null になります。つまり、常に増加するか同じままです。(つまり、ループするまで、おそらく 2^31 または 2^63 あたりですが、他にも問題があります。長さそこに到達する前に。この解決策をちょっとしたハックと表現するのには理由があります。

の条件文${__cmdnbary[\#]-word}

${__cmdnbary[\#]-word}もう一つのパラメータ拡張です。マニュアルより:

${parameter:-word}

デフォルト値を使用するパラメータが設定されていないか null の場合、 word の展開が置換されます。それ以外の場合は、パラメータの値が置換されます。

つまり、配列のエントリ\#未設定またはnullが代わりに使用されます。チェックするまでは(置換を使用して)word割り当てようとしないので、特定のものに対して最初にチェックしたときには、配列内のその位置が設定されていないことがわかります。__cmdnbary[\#]${parameter:=word}\#

bash疎な配列を使用する

Cスタイルの配列に慣れている人のために、1つの点を明確にします。bash実際には疎な配列; 割り当てるまで何か配列内の位置に文字列を代入すると、その位置は未設定になります。空の文字列は、「null または未設定」と同じではありません。

代わりに${__cmdnbary[#]+word}${__cmdnbary[#]=}を使用する理由

${__cmdnbary[\#]+word}${__cmdnbary[\#]=}そして ${__cmdnbary[#]-word}${__cmdnbary[#]=} look very siilar. The *only* thing we change between the two constructs can be found in the first portion; we use${parameter:+word} instead of${parameter:-word}`。

${parameter:-word}では、がnullまたは未設定のword場合にのみ表示されることに注意してください。この場合、parameterしていない配列内の位置はまだ設定されていません。これは、 が増加した場合にのみ実行され\#、コマンドを実行した場合にのみ発生します。

つまり、 の場合${parameter:-word}wordしていないは、まさに私たちがやりたいことと逆のコマンドを実行しました。そこで、${parameter:-word}代わりに を使用します。これもマニュアルから引用します。

${parameter:+word}

代替値を使用するパラメータが null または未設定の場合は何も置換されず、それ以外の場合は word の拡張が置換されます。

これは(残念ながら)理解するのがさらに難しい二重否定の論理ですが、それが現実です。

プロンプト自体

切り替えのメカニズムについては説明しましたが、プロンプト自体はどうでしょうか?

ここでは、$( ... )プロンプトの核心部分を含めるために を使用しています。これは主に私自身の読みやすさのためです。そのようにする必要はありません。$( ... )変数割り当てに通常詰め込むものなら何でも置き換えることができます。

なぜハッキングなのでしょうか?

疎な配列にエントリを追加する方法を覚えていますか?これらのエントリは削除されないので、シェルセッションが終了するまで配列は永遠に大きくなります。シェルはリークしていますPS1。そして、私が知る限り、設定解除プロンプト内の変数または配列の位置。 で試すこともできます$()が、うまくいかないことがわかります。サブシェル内の変数名前空間に加えられた変更は、サブシェルがフォークされたスペースには適用されません。

あなたかもしれないmktmpの早い段階で.bashrc、割り当ての前にを使用しPS1、結果のファイルに情報を詰め込んでみてください。そうすれば、現在のもの\#とそこに保存した内容を比較できますが、プロンプトがディスク I/O に依存するようになり、緊急事態で自分自身を締め出すのに良い方法になります。

関連情報