bashの$PS1とあくまでノーマルにきゃっきゃうふふする

この記事はeeic （東京大学工学部電気電子・電子情報工学科）その２ Advent Calendar 2017の18日目でございます。

みなさんはシェルとして何を使っていますか？今の流行りはfishっぽいですが、移行がめんどいという理由だけでbashを使う人です。

今回は軽いbash $PS1の話をしたいと思います。

# $PS1って何

bashの$PS1は特殊変数で、コマンド入力待ち状態のときに表示されるプロンプトのフォーマットを制御しています。

![プロンプトの例](https://qiita-image-store.s3.amazonaws.com/0/92991/2dbef016-38d2-0a13-269a-adc376fcf8f4.png)
↑Ubuntuでよく見るプロンプト

おもむろに

```shell-session
$ echo $PS1
```

してみましょう。(カスタマイズされていなければ) その環境でデフォルトで設定されている変数がでてくるはずです。

```shell-session
(ubuntu)$ echo $PS1
${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ 
(centos)$ echo $PS1
[\u@\h \W]\$
(gentoo)$ echo $PS1
\[\033]0;\u@\h:\w\007\]\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\]
```

適当にプロンプトを変更してみましょう。

```shell-session
$ PS1='eeicはブラック学科である $'
```

![適当にPS1を変えてみた例](https://qiita-image-store.s3.amazonaws.com/0/92991/48470ece-b028-4040-2118-3821ac87fdba.png)

ブラックというわりには文字がホワイトで表示されていますね！学科がブラックというのはやっぱり嘘でした！("電気系マフィア"[^2] に銃を突きつけられながら)

[^2]: おおよそ、テブナンの定理を鳳-テブナンの定理と呼称する人たち、のこと。

という学科の人にしか通じないローカルネタを挟んだところで、次に移りたいと思います。

# PS1をカスタマイズしてみよう！

さっき```echo $PS1```したときに、よくわからないバックスラッシュ ```\```が含まれていたことに気が付いた人がいるかもしれません (環境によってバックスラッシュがない場合もあります、CentOSとか) 。

このバックスラッシュには当然意味があります。いくつか挙げてみましょう。

- ```\u```: 現在のユーザー名
- ```\h```: ホスト名
- ```\w```: 今いるディレクトリ (current working directory＝作業ディレクトリ) のパス、ホームディレクトリは```~```になる
- ```\W```: 今いるディレクトリの**名前**[^1]、ホームディレクトリは```~```になる
- ```\$```: rootならば```#```、それ以外なら```$```

[^1]: いわゆるbasenameです。

それ以外にも使われていますが、あとで述べます。

これを組み合わせればあなただけのプロンプトが完成です！ (基本的にPS1を設定するときにはシングルクオーテンション```'```でくくってあげましょう)

```
$ PS1='YUKI.N>'
YUKI.N>PS1='わたし\uさん。今\wにいるの。'
わたしmerryさん。今~にいるの。PS1='\$\$\u\u'
$$merrymerry
```

ちなみに、PS1の設定はそのセッションしか有効ではないので注意しましょう (ターミナルを再起動するともとに戻ります)。気に入った設定は、~/.bashrcに追記すると次回起動時にも適用されるようになります[^4]。

[^4]: .bashrcがない場合は.bashrcを作成します。.bashrcだけでは適用されない場合もあります。そのときは、~/.bash_profileに`source ~/.bashrc`とだけ書いて保存するとうまくいったような気がします。

# 色を付けよう！

さて、UbuntuとGentooマンなら、プロンプトに色がついていたことを思い出してください。もちろんついてないこともあります。あと、CentOSはデフォルトでは色がついてない気がします。

なんと、対応しているターミナルを使っていれば、色のついた文字を表示することが出来ます。試してみましょう。

```shell-session
$ printf '\e[31ma\e[32mb\e[33mc\e[34md\e[35me\e[36mf\e[0m\n'
abcdef  ←色付きで表示される
```

printfに渡した引数通りに表示されていないことはわかるかと思います。

実はターミナルは、ESC文字 (ASCII 27=0x1b=033) から始まる、一定の決まりに沿った文字列 (エスケープシーケンス) を認識して、特殊な効果を付けてくれるのです。

詳しい説明は「ANSI エスケープシーケンス」あたりでググってもらうとして、色の付け方はだいたい下のような感じで付けられます (ESC文字は```\e```で表しています。

||文字色|背景色|
|-----:|:------|:-------|
| 黒 | \e[30m  | \e[40m |
| 赤 | \e[31m  | \e[41m |
| 緑 | \e[32m  | \e[42m |
| 黄色| \e[33m  | \e[43m |
| 青 | \e[34m  | \e[44m |
| マゼンタ | \e[35m  | \e[45m |
| シアン | \e[36m  | \e[46m |
| 白 | \e[37m  | \e[47m |

※色は環境の設定によって異なる場合があります。

| | シーケンス |
|:--|:--|
| リセット (色、太字設定をもとに戻す) | \e[0m |
| 太字 (色も一緒に変わることが多い) | \e[1m |

複数同時指定も可能です、たとえば```\e[1;32;41m```ならば**後続の**文字が緑色の太字、背景が赤色になります。

さて、Ubuntuのデフォルト$PS1について解読してみましょう。debian_chrootは邪魔なので消します。あと上での```\e```が```\033```になっていることに注意してください。

```
\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ 
```

- ```\[```: 不可視文字列の始端。色を付けるためのエスケープシーケンスは表示されないので必要。
- ```\033[01;32m```: エスケープシーケンス。後続の文字を太字の緑色系にする。01と1は (たぶん) 一緒の意味です
- ```\]```: 不可視文字列の終端。
- ```\u@\h```: 上で述べたとおりなので省略します。
- ```\[\033[00m\]```: 不可視文字列の始端、色・太字のリセット、不可視文字列の終端
- ```:```: コロン
- ```\[\033[01;34m\]```: 不可視文字列の始端、後続の文字を太字の青色系にする、不可視文字列の終端
- ```\w```: 上の章で述べた、今のディレクトリパス
- ```\[\033[00m\]\$ ```: 省略

和訳してあげると、`ユーザー名@ホスト名`を緑色系で表示、`:`をデフォルトの色で表示、`作業ディレクトリ`を太字の青色系で表示、`$と空白 (rootなら#と空白) `をデフォルトの色で表示。となります。実際に確認してみましょう。

![プロンプトの例 (再掲)](https://qiita-image-store.s3.amazonaws.com/0/92991/2dbef016-38d2-0a13-269a-adc376fcf8f4.png)

和訳した通りみたいですね！

# もっとPS1で遊ぶ

`\w`でディレクトリパスを表示すると、深いディレクトリ階層のときに表示が長くなりすぎるってことありますよね。

![長いファイルパス表示](https://qiita-image-store.s3.amazonaws.com/0/92991/61ca4fe8-5dbf-46fd-d0bd-d96350cdcb63.png)

それが嫌で`\W`を設定する人もいるとおもいますが、最後のパスだけ表示されてもどこにいるかわからないってのも困ったりしませんか？

そういう方には[このgist](https://gist.github.com/ricardochimal/293517)のsmart_pwdが役に立つと思います。

```shell
function smart_pwd {
    local pwdmaxlen=25
    local trunc_symbol=".."
    local dir=${PWD##*/}
    local tmp=""
    pwdmaxlen=$(( ( pwdmaxlen < ${#dir} ) ? ${#dir} : pwdmaxlen ))
    NEW_PWD=${PWD/#$HOME/\~}
    local pwdoffset=$(( ${#NEW_PWD} - pwdmaxlen ))
    if [ ${pwdoffset} -gt "0" ]
    then
        tmp=${NEW_PWD:$pwdoffset:$pwdmaxlen}
        tmp=${trunc_symbol}/${tmp#*/}
        if [ "${#tmp}" -lt "${#NEW_PWD}" ]; then
            NEW_PWD=$tmp
        fi
    fi
}

PROMPT_COMMAND="smart_pwd"
PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]${NEW_PWD}\[\033[00m\]\$ \$'
```

ちょっと長いですが、これを.bashrcに書いてあげると、

![いい感じに短くなる](https://qiita-image-store.s3.amazonaws.com/0/92991/d04fd747-d2e1-1c78-aad4-8237a853ab45.png)

のように、長いパスもいい感じに短くなります。ぼくは先頭一文字 (？) が表示されるタイプ (~/t/o/m/c/s/o/r/g/filter) より、目一杯表示できる分だけパスを表示してくれる方が好きです。

# 256色なプロンプトを使ってみる

実はターミナルによっては、エスケープシーケンスを応用することで256色を表示することが出来る場合があります (Linuxのgnome-terminalは対応しています)。

文字色は`\e[38;5;<色番号>m`、背景色は`\e[48;5;<色番号>m`で設定できます。256色がどのような色かは[xterm-colortest.pl](https://github.com/jpalardy/dotfiles/blob/master/bin/xterm-colortest.pl)などを使えば見ることが出来ます。

256色もあればきっと他の人と被らない、オリジナルプロンプトを作ることも容易いことでしょう。

# 最後に自分の$PS1晒し

母艦はだいたいこんな感じの設定を使っています。[dotfiles](https://github.com/turenar/dotfiles/tree/master/.bashrc.d)

```
\[\e[$[COLUMNS-19]C\]\[\033[48;5;55m\033[38;5;220m\D{%a %b %d %T}\r\033[0m\]\[\033[48;5;17m\033[38;5;117m\]\u\[\033[38;5;114m\]@\[\033[38;5;117m\]\h \[\033[48;5;232m\033[38;5;201m\]${SMART_PWD_TRUNC}\[\033[38;5;86m\]${SMART_PWD}\[\033[0m\]\n\[\033[38;5;41m\]$(__git_ps1 "(%s)")\[\033[38;5;208m\]\$\[\033[0m\] 
```

```shell:.bashrc
function smart_pwd {                                                                                                                                                              
    local pwdmaxlen=$(( $COLUMNS-35 ))                                                                                                                                            
    local trunc_symbol=".."                                                                                                                                                       
    local dir=${PWD##*/}                                                                                                                                                          
    local tmp=""                                                                                                                                                                  
    pwdmaxlen=$(( ( pwdmaxlen < ${#dir} ) ? ${#dir} : pwdmaxlen ))                                                                                                                
    if [[ $PWD == $HOME* ]]; then                                                                                                                                                 
        SMART_PWD="~${PWD#${HOME}}"                                                                                                                                               
    else                                                                                                                                                                          
        SMART_PWD="$PWD"                                                                                                                                                          
    fi                                                                                                                                                                            
    # set title                                                                                                                                                                   
    printf "\033]0;%s\007" "${USER}@${HOSTNAME}: ${SMART_PWD}"                                                                                                                    
                                                                                                                                                                                  
    SMART_PWD_TRUNC=""                                                                                                                                                            
    local pwdoffset=$(( ${#SMART_PWD} - pwdmaxlen ))                                                                                                                              
    if [ ${pwdoffset} -gt "0" ]                                                                                                                                                   
    then                                                                                                                                                                          
        tmp=${SMART_PWD:$pwdoffset:$pwdmaxlen}                                                                                                                                    
        tmp=${trunc_symbol}/${tmp#*/}                                                                                                                                             
        if [ "${#tmp}" -lt "${#SMART_PWD}" ]; then                                                                                                                                
            SMART_PWD_TRUNC="${trunc_symbol}"                                                                                                                                     
            SMART_PWD="${tmp:${#trunc_symbol}}"                                                                                                                                   
        fi                                                                                                                                                                        
    fi
}

# git-prompt.shの中に__git_ps1があるので、インクルードする
# ファイルパスは環境によって違う可能性あり。https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh をダウンロードでもよい
source /usr/share/git/git-prompt.sh

PROMPT_COMMAND="smart_pwd"
PS1='\[\e[$[COLUMNS-19]C\]\[\033[48;5;55m\033[38;5;220m\D{%a %b %d %T}\r\033[0m\]\[\033[48;5;17m\033[38;5;117m\]\u\[\033[38;5;114m\]@\[\033[38;5;117m\]\h \[\033[48;5;232m\033[38;5;201m\]${SMART_PWD_TRUNC}\[\033[38;5;86m\]${SMART_PWD}\[\033[0m\]\n\[\033[38;5;41m\]$(__git_ps1 "(%s)")\[\033[38;5;208m\]\$\[\033[0m\]'
```

これを設定するとだいたいこんな感じになります

![設定例](https://qiita-image-store.s3.amazonaws.com/0/92991/774bb0c8-74bf-435f-4417-8076eb2114c4.png)

まあこんなかんじで、256色、日時、いい感じのパス表示、gitの情報表示、などが組み合わさった、個人的に気に入っている設定になります。

みなさんもいい感じのPS1をつくってみてね！