以前、以下記事でdirenvのインストール手順 hook direnv into your shell. について調べてみました。
その際、direnvのhookコマンドで出力されるBash用のフックを設定するためのスクリプトが気になったため、少し調べてみました。
direnv hook bash でどんなスクリプトが出力されるの?
Macのターミナルでdirenv hook bash
を実行し、スクリプトを出力してみました。
% direnv hook bash
_direnv_hook() {
local previous_exit_status=$?;
trap -- '' SIGINT;
eval "$("/opt/homebrew/bin/direnv" export bash)";
trap - SIGINT;
return $previous_exit_status;
};
if [[ ";${PROMPT_COMMAND[*]:-};" != *";_direnv_hook;"* ]]; then
if [[ "$(declare -p PROMPT_COMMAND 2>&1)" == "declare -a"* ]]; then
PROMPT_COMMAND=(_direnv_hook "${PROMPT_COMMAND[@]}")
else
PROMPT_COMMAND="_direnv_hook${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
fi
fi
いくつかわからない処理があったので、以下の要素に分解して調べてみましょう。
- local
- trap
- PROMPT_COMMAND
localとは
出力されたスクリプトの_direnv_hook()
内で使われており、Bashのマニュアル4.2 Bash Builtin Commandsに記載があります。
_direnv_hook() {
local previous_exit_status=$?;
〜省略〜
return $previous_exit_status;
};
local [option] name[=value] …
For each argument, a local variable named name is created, and assigned value. The option can be any of the options accepted by declare. local can only be used within a function; it makes the variable name have a visible scope restricted to that function and its children.
「localは関数内でのみ使用可能で、変数名の可視スコープをその関数とその子関数に限定する。」
とあることから、関数内でのみ有効な変数を宣言することができ、宣言した変数が別の関数やスクリプト全体に影響を与えることを防ぐことができます。
trapとは
出力されたスクリプトの_direnv_hook()
内で使われており、Bashのマニュアル4.1 Bourne Shell Builtinsに記載があります。
_direnv_hook() {
〜省略〜
trap -- '' SIGINT;
〜省略〜
trap - SIGINT;
〜省略〜
};
trap [-lp] [arg] [sigspec …]
The commands in arg are to be read and executed when the shell receives signal sigspec. If arg is absent (and there is a single sigspec) or equal to ‘-’, each specified signal’s disposition is reset to the value it had when the shell was started. If arg is the null string, then the signal specified by each sigspec is ignored by the shell and commands it invokes.
「arg のコマンドは、シェルがシグナル sigspec を受け取ったときに読み込まれ、実行される。 arg が存在しない (そして sigspec が 1 つである) か、'-' に等しい場合、指定された各シグナルの処分は、シェルが起動されたときの値にリセットされる。 arg が NULL 文字列である場合、各 sigspec で指定されたシグナルは、シェルとそれが呼び出すコマンドによって無視される。」
とあることから、trap -- '' SIGINT
でSIGINTを無視し、trap - SIGINT
でその無視をリセットしています。
--について
引数'--'を与えることがでオプション処理を終了させ、次の単語がオプション以外の引数であることを示し、それ以外は無視されます。これは、コマンドの引数が'-'で始まる場合に便利です。
SIGINTについて
シグナル(プロセスに送信される信号)の一つで、「キーボードからの割り込み、control + c」をプロセスに要求します。
PROMPT_COMMANDとは
出力されたスクリプトのif文で使われており、Bashのマニュアル5.2 Bash Variablesに記載があります。
if [[ ";${PROMPT_COMMAND[*]:-};" != *";_direnv_hook;"* ]]; then
if [[ "$(declare -p PROMPT_COMMAND 2>&1)" == "declare -a"* ]]; then
PROMPT_COMMAND=(_direnv_hook "${PROMPT_COMMAND[@]}")
else
PROMPT_COMMAND="_direnv_hook${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
fi
fi
PROMPT_COMMAND
If this variable is set, and is an array, the value of each set element is interpreted as a command to execute before printing the primary prompt ($PS1). If this is set but not an array variable, its value is used as a command to execute instead.
「この変数が設定されていて配列である場合、設定されている各要素の値は、 主プロンプト($PS1)を表示する前に実行するコマンドとして解釈される。 この変数が設定されているが配列変数でない場合は、 その値が代わりに実行するコマンドとして使われる。」
とあることから、変数PROMPT_COMMANDに関数を設定することで、主プロンプト($PS1)を表示する前に関数を実行することができます。
また、if [[ ";${PROMPT_COMMAND[*]:-};" != *";_direnv_hook;"* ]]; then
でPROMPT_COMMANDに_direnv_hookが存在するか確認し、存在しない場合はPROMPT_COMMANDに_direnv_hookを設定しています。
IFS変数が空の場合、;区切りで展開されない
macOSの場合、配列ではなくテキストなので問題ない
Linux環境での挙動を試してみる
direnvでバグを見つけた場合の修正依頼はどうすればいい?
direnv hook bash で出力されるスクリプトがしていること
- BashのPROMPT_COMMANDに_direnv_hookが存在するか確認し、存在しない場合に設定
- _direnv_hookでは
trap -- '' SIGINTでSIGINT
を無視しdirenv export bash
を実行、trap - SIGINT
でその無視をリセット
まとめ
いかがでしたでしょうか?
direnv hook bash
でどんなスクリプトが出力されているかだいぶイメージできるようになりました。
_direnv_hookで実行しているdirenv export bash
も少し気になりますが、exportとあることから環境変数を有効にしているのだろうと理解しました。
direnvについて詳しく調べることで、ますますdirenvを使いたくなりました。
今後もどんどん使っていきたいと思います。