bashからzshに移行したときにPS1でターミナルのタイトルを変更していたのが動かなくなったときの対処

# はじめに

BashからZshに移行しようと.bashrcを.zshrcに書き換えている中で、プロンプト表示に設定していた以下の箇所が動かなくなってしまいました。

```bash
# 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
```

# 原因: エスケープ文字の処理の違い

動かなくなってしまった原因は$PS1の中でエスケープ文字`\e`を使用していたことでした。
`\[\e]0;～\a\]`でxtermのアイコン名とタイトルを変更する制御シーケンスになっています。
`ESC ]`がOS制御コマンドの開始(Operating System Commands=OSC)を表していて、`OSC 0 ; <String> BEL`
でタイトルを`<String>`に変更できます。

https://unix.stackexchange.com/questions/306716/meaning-of-e0-in-ps1-in-bashrc

https://invisible-island.net/xterm/ctlseqs/ctlseqs.html

zshではbashとエスケープ文字の処理が異なり、bashでは`\[～\]`がエスケープ処理の開始と終了になっていましたが、zshではエスケープ処理の開始と終了を表すのに`%{～%}`を使用します。
しかし、zshの`%{～%}`は通常のクォート文字列(`'～'`や`"～"`)では使用できず、$を付けた特殊な文字列(`$'～'`)にする必要があるようです。


https://qiita.com/alpaca-honke/items/f6be3e92882cba34cfc6#ansi%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9%E3%81%8C%E9%81%95%E3%81%86

# 対処1: エスケープの処理を抜き出して別の環境変数にする

そのまま`"～"`を`$'～'`に置き換えると、どうやらエスケープの処理だけされて環境変数が置き換えられないらしいです。

```zsh
# bashのプロンプト設定
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"

# zsh用に"～"を$'～'、\u@\hを%n@%m、\wを%~に置き換えるが動かない
PS1=$'\e]0;${debian_chroot:+($debian_chroot)}%n@%m: %~\a$PS1'
-> これは${debian_chroot:+($debian_chroot)}と$PS1が置き換えられず、
   タイトルが ${debian_chroot:+($debian_chroot)}%n@%m: %~ になり
   $PS1 とプロンプトにそのまま表示されてしまう
```

エスケープ処理を行う部分だけ`$'～'`にして別の環境変数に用意すると、なぜかうまく動きます。
(処理の順番の問題？)

```zsh
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    DISABLE_AUTO_TITLE=true # タイトルをプロンプト表示時に書き換えるために自動設定を解除する
    PS1_TITLE=$'\e]0;${debian_chroot:+($debian_chroot)}%n@%m: %~\a' # bashで\[～\]だった部分
    PS1="${PS1_TITLE}${PS1}"
    ;;
*)
    ;;
esac
```

# 対処2: precmdでタイトル変更をプロンプト表示前に設定する

zshにはprecmdというhook関数(bashではPROMPT_COMMANDという変数だったもの)が定義されています。
なので、precmdを自前で定義することでいろいろな処理を実行できます。

```zsh
# bashの場合
PROMPT_COMMAND="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]"

# zshの場合
function precmd() { print -Pn $'\e]0;${debian_chroot:+($debian_chroot)}%n@%m: %~\a' }
```

これで以下のように書き換えることができます。

```zsh
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    DISABLE_AUTO_TITLE=true # タイトルをプロンプト表示時に書き換えるために自動設定を解除する
    function precmd() { print -Pn $'\e]0;${debian_chroot:+($debian_chroot)}%n@%m: %~\a' }
    ;;
*)
    ;;
esac
```

https://zsh.sourceforge.io/Doc/Release/Functions.html#Hook-Functions

# 対処2-1: precmdの処理にタイトルを変更する自作関数を追加する

zshのhook関数には、関数名の後ろに`_functions`をつけるとそのhook関数と同じように実行できる関数の配列を定義できるという仕組みがあるらしく、さらに`add-zsh-hook`というシェル関数を使うと、関数の配列への追加・削除ができるらしいです。

```zsh
# hookに追加する自作関数
function myhook () {
  # hookで実行したい処理
}

# hookへの追加
autoload -Uz add-zsh-hook
add-zsh-hook <hook名> myhook
```

precmdに別の処理をすでに定義している場合は、これを使ってprecmdに自作の関数を追加することができます。

```zsh
# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
    DISABLE_AUTO_TITLE=true # タイトルをプロンプト表示時に書き換えるために自動設定を解除する
    function precmd_mytitle() { print -Pn $'\e]0;${debian_chroot:+($debian_chroot)}%n@%m: %~\a' }
    autoload -Uz add-zsh-hook    
    add-zsh-hook precmd precmd_mytitle # precmdにprecmd_mytitle関数を追加
    ;;
*)
    ;;
esac
```

https://kiririmode.hatenablog.jp/entry/20120327/p1

https://qiita.com/mollifier/items/558712f1a93ee07e22e2

# まとめ

bashとzshで微妙に違っていて移行が地味に大変。