Ubuntuのデフォルト設定から学ぶbashプロンプト

ターミナルに表示される `user@host:~$ ` みたいなものは変数 `PS1` で設定できると習ったのだが、Ubuntuのデフォルトの設定を見てみるとやけに複雑な形をしていて、何を表しているのかさっぱり分からなかった。

```shell-session
user@host:~$ echo "'$PS1'"
'\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
```

なので、これを理解するために調べたことをまとめておく。

使用した環境は以下の通り。

- Ubuntu 14.04
- bash 4.3.11
- GNOME端末 3.6.2 (`TERM=xterm`)
- （色がつくようにだけ設定変更あり）

## .bashrcの内容
パラメーターによる条件分岐や参考になるコメントが書いてあるので、プロンプト設定に関わる部分を抜粋する。なお `force_color_prompt=yes` のコメントアウトは外してある。

```bash:~/.bashrc
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot=$(cat /etc/debian_chroot)
fi

# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
    xterm-color) color_prompt=yes;;
esac

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
	# We have color support; assume it's compliant with Ecma-48
	# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
	# a case would tend to support setf rather than setaf.)
	color_prompt=yes
    else
	color_prompt=
    fi
fi

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
    ;;
*)
    ;;
esac
```

## 記号の意味

### プロンプト用の特殊な文字列
バックスラッシュで始まる特定の文字列は、プロンプトに用いると別の文字列に変換される。

- `\u` : ユーザー名
- `\h` : ホスト名
- `\w` : 現在のディレクトリ（フルパス）
- `\$` : ふつうは `$`、実効uidが0なら `#`
- `\[...\]` : 表示しない文字列（ターミナル制御用のもの）はこれで囲っておくこと[^hide]
- `\033`, `\e` : エスケープ文字
- `\a` : ベル文字

他にもたくさんある。一覧はネットでも `man bash` でも調べられるので省略。

[^hide]: これを使わずに着色設定をしたことがあるが、コマンドを履歴から出したときなどに表示がおかしくなった

### 変数展開
`$変数名` または `${変数名}` と書いてあると、変数の表す文字列に展開される。シェルコマンドやスクリプトの中で使うことが多いが、プロンプトを表示する際にも展開が有効。そのため、変数を定義したら `PS1` を再設定しなくても次行のプロンプトにすぐ反映される。

変数をただ展開するだけでなく、変数の状態に応じて処理を変えることもできる。`${変数名:+文字列}` は、「変数の値が存在していたら指定の文字列に展開」となる。今回の `${debian_chroot:+($debian_chroot)}` の場合は、例えば `debian_chroot=foobar` と定義されていれば文字列 `(foobar)` に展開する（つまり丸括弧で囲んで出力）。未定義または空文字列なら何も表示されない。

```shell-session
user@host:~$ debian_chroot="foobar"
(foobar)user@host:~$ debian_chroot=""
user@host:~$ unset debian_chroot
user@host:~$ 
```

プロンプトで展開するためには、`PS1` を定義する際には展開しないようにする必要がある。そのためには、`PS1='$var'` と文字列をシングルクォートで括るか、`PS1="\$var"` とエスケープすればいい。（`PS1='\$var'` だとバックスラッシュが残って前節の `\$` の意味になるのでダメ）

#### debian_chroot
肝心のこの変数の利用法については、[参考に挙げたページ](http://askubuntu.com/questions/372849/what-does-debian-chrootdebian-chroot-do-in-my-terminal-prompt)の説明が分かりやすかった。

`.bashrc` にもある通り、 `debian_chroot` には `/etc/debian_chroot` の内容を（可能なら）セットしている。この仕組みが役立つのは、`chroot` コマンドでルートディレクトリを変えるとき。あるサブディレクトリに別のLinux環境が一式入っている場合、ルートディレクトリをそこへ切り替えれば、全てのパスの指し示す先がサブディレクトリ以下となるため、そのまま別のLinuxを使用する状態にできる。各環境内にあらかじめ `/etc/debian_chroot` を用意しておけば、どの環境を使用中かプロンプトから区別することができる。

### ASCII制御文字
`\e` (`\033`), `\a` (`\007`) はプロンプトの中で制御文字 `ESC`, `BEL` に変換される。これら自体はまともな文字として表示されないが、決められた一連の文字列を作るとターミナルが特別な動作をする。

#### 文字の装飾
`\033[...m` のようになっているところでは色や太字などの制御をしている。使われている番号の意味は以下の通り。

- 0 : 全部リセット
- 1 : 太字
- 32 : 文字色を緑
- 34 : 文字色を青

0番台では太字以外に下線・文字色と背景色の反転などもできる。30番台は文字色、40番台は背景色。

#### ターミナルのタイトル
`\e]0;...\a` はターミナルのタイトルに文字列を表示させる。デフォルト設定の場合は `\u@\h: \w` の変換結果が表示される。

`.bashrc` では `PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"` となっているが、全体をダブルクォートで囲っているため変数がその場で展開され、`debian_chroot` を後からタイトルに反映させることはできなくなっている。

#### 試してみる
これらの機能はプロンプトに限らず、ターミナルに出力するだけで効果がある。簡単な試し方として、`echo` コマンドなら `-e` オプションでバックスラッシュ文字列を変換してくれる。

```shell-session
user@host:~$ PS1='\u@\h:\w\$ '         # プロンプトから設定を消しておく
user@host:~$ echo -e '\e[32mfoobar'    # 緑字に変更
foobar
user@host:~$ echo -e '\e]0;foobar\a'   # タイトル変更

user@host:~$ . ~/.bashrc               # 設定を再読み込みすれば大抵は元通り
user@host:~$ echo -ne '\e[0m'          # 色が消えなければ手動で消す
user@host:~$ exit                      # どうしても直らなければ一旦ターミナルを閉じる
```

## 目的別まとめ

- プロンプトらしい文字列表示だけなら `PS1='\u@\h:\w\$ '` みたいに短く済む
- 変数の内容を表示させたければ `${変数名}` などを埋め込む。`echo $PS1` としたときに未展開のまま表示され、`\$` ともなっていなければOK
	- `${変数名:+代替文字列}` や `${変数名:-デフォルト文字列}` などを使えばより凝った設定ができる
	- ちなみに変数展開だけでなく、コマンド置換 `$(コマンド)` や算術展開 `$((式))` も使える
- 文字を装飾したければ `\[\e[...m\]` を間に入れる。適宜 `\[\e[0m\]` などして設定をリセットすること
- ターミナルのタイトルを変更したければ `\[\e]0;...\a\]` を加える

## 参考

- プロンプト全般
	- [Bash Reference Manual - 6.9 Controlling the Prompt](http://www.gnu.org/software/bash/manual/bashref.html#Controlling-the-Prompt)
- 変数展開
	- [Bash Reference Manual - 3.5.3 Shell Parameter Expansion](http://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion)
- debian_chroot
	- [What does “${debian_chroot:+($debian_chroot)}” do in my terminal prompt?](http://askubuntu.com/questions/372849/what-does-debian-chrootdebian-chroot-do-in-my-terminal-prompt)
- 文字の装飾・ターミナルのタイトル
	- [ANSI escape code (Wikipedia英語版)](https://en.wikipedia.org/wiki/ANSI_escape_code)
	- [Tera Term ヘルプ - 対応制御シーケンス](https://ttssh2.osdn.jp/manual/ja/about/ctrlseq.html)