Posted at

Bash で PROMPT_COMMAND を使わずに PS1 を動的に変更

More than 3 years have passed since last update.


はじめに


PROMPT_COMMAND とは

PROMPT_COMMAND に関数やコマンドがセットされていると、Bash でプロンプトを表示する直前に、毎回それが実行されます。

この機能を利用して PS1 を動的に書き換える例を見かけることがあります。


PS1 内でコマンドの出力を表示する

しかし、PS1 内でも関数やコマンドを実行して、結果の文字列を表示することができます。1

ただ、うろ覚えでこの辺りの設定をしていたところ、少々ハマってしまったので、実際に自分が間違えた例と合わせて、正しい設定方法を記しておきます。

よく使われる git-completion を題材に用います。

※以降の例で、git-prompt.sh 相当のものは読み込み済みとします。


間違い例1

# ~/.bashrc

__my_ps1() {
local branch=$(__git_ps1)
[[ $branch ]] && branch="\[\e[0;35m\]$branch\[\e[0m\] "
echo "[${branch}\u@\h:\W] "
}

PS1="$(__my_ps1)"

一見ちゃんと動きそうで、実際動きはするのですが、PS1 の文字列は最初にこのスクリプトを評価したときのままで、ディレクトリを移動しても、__my_ps1 関数が再評価されることはありません。

実行例:

[me@localhost:~] cd path/to/gitrepo

[me@localhost:gitrepo] # branch は表示されない
[me@localhost:gitrepo] . ~/.bashrc # 再読み込み
[ (master) me@localhost:gitrepo] # 表示される
[ (master) me@localhost:gitrepo] cd
[ (master) me@localhost:~] # 非gitディレクトリでも表示されてしまう

(例では、カラーコードは反映されていません。)


間違い例2

# ~/.bashrc

__my_ps1() {
local branch=$(__git_ps1)
[[ $branch ]] && branch="\[\e[0;35m\]$branch\[\e[0m\] "
echo "[${branch}\u@\h:\W] "
}

PS1='$(__my_ps1)'

この場合、__my_ps1 関数は毎回実行・評価されてくれるのですが、エスケープシーケンスが評価されず、文字列としてそのまま表示されてしまいます。

実行例:

[\u@\h:\W] cd path/to/gitrepo

[\[\e[0;35m\] (master)\[\e[0m\] \u@\h:\W] # branch は表示される
[\[\e[0;35m\] (master)\[\e[0m\] \u@\h:\W] cd
[\u@\h:\W] # branch は表示されない

なんとも残念な見た目ですorz


正しい例

正しくは、下のように PS1 内で $(関数 or コマンド) をシングルクォートで記述します。

# ~/.bashrc

PS1='[\[\e[0;35m\]$(__git_ps1)\[\e[0m\] \u@\h:\W] '

ダブルクォートで括りたい場合は、 "\$(__git_ps1)" のようにエスケープすれば OK です。

実行例:

[me@localhost:~] cd path/to/gitrepo

[ (master) me@localhost:gitrepo] # branch は表示されない

以上です。

参考になれば幸いです。


参考





  1. 余談ですが、筆者は PROMPT_COMMAND で PS1 を書き換えて先輩に注意を受けたことがあります。