blog(unstable)

tmuxの起動タイミングを変更した

December 1, 2024

category : 技術

tags :


この記事はひとりアドベントカレンダー(便秘解消) DAY 1の記事です。


tl;dr

ログイン時にtmuxを自動で起動したい場合、起動スクリプトは .zprofile or .zlogin に書くのがよさそう


解決したかったこと

ロケールを日本語に設定したLinuxのログインシェルでtmuxを起動したとき、日本語が表示されなかった。
具体的には、ASCIIの範囲の文字は表示されるが、それ以外はすべて _ として表示される。
また、一度tmuxを終了させてzshに抜けたとき、およびあらためてtmux起動したときは正しく日本語が表示されるようになる。

環境情報

以下のansibleを実行して、日本語ロケールを生成済。

- name: '日本語ロケールの生成'
  become: true
  community.general.locale_gen:
    name: ja_JP.UTF-8

- name: '日本語ロケールを使う'
  become: true
  ansible.builtin.copy:
    content: 'LANG=ja_JP.UTF-8'
    dest: /etc/locale.conf

https://github.com/kei-s16/homelab/blob/a4e6228e2c4715ee28cd67f9e4874de7964b1900/ansible/roles/add-worker/tasks/main.yaml#L2-L11

tmuxの起動は、~/.zshenv に書かれた以下のスクリプトで実行している。

# TMUX
if which tmux >/dev/null 2>&1; then
    # if no session is started, start a new session
    test -z ${TMUX} && tmux

    # when quitting tmux, try to attach
    while test -z ${TMUX}; do
        tmux attach || break
    done
fi

予測

一度tmuxを終了させてzshに抜けたとき、およびあらためてtmux起動したときは正しく日本語が表示されるようになる。

この挙動から、初回のtmux起動までの時間で、設定したロケールが読まれていないのではないかと仮説を立てた。

調査したこと

envの差分を取る

ロケール情報は環境変数に入る。そのため、

  1. ログイン後自動でtmuxを起動したとき
  2. zshに抜けたとき
  3. あらためてtmuxを起動したとき

の環境変数を比較した。

$ mkdir tmp
$ env > tmp/${現在試している状態}-env.txt

その後、環境変数を比較した。ソートをしないとまともなdiffが取れないので、環境変数名をキーにソートしている。

# 以下は初回起動とtmux再起動後の環境変数を比較している
$ zsh -c "diff -ui <(sort tmp/env-first-tmux.txt) <(sort tmp/env-re-tmux.txt)"

残念ながら、目星を付けていた LANG には差分がなく、どの状態でも LANG=ja_JP.UTF-8 が入っていた。

zshrcに処理を移植する

tmuxのドキュメントには、

tmux will attempt to detect if the terminal it is running in supports UTF-8 by looking at the LC_ALL, LC_CTYPE and LANG environment variables. (tmuxはターミナルがUTF-8をサポートしていてLC_ALL, LC_CTYPE, LANGが設定されていたらいい感じに言語設定するよ(適当訳))

とあるので、LANGが設定されている以上詰みかと思われた。
が、ふと適当に前述したtmux起動スクリプトを ~/.zshrc の末尾に移動してログインしなおしたところ、日本語が表示された。
neovimの :h やmanもしっかり日本語で読めた。

もしやと思いzshの設定ファイルロード順を確認することにした。

zshの起動処理を確認する

zshのmanによると、ログイン時は以下の順で設定ファイルが読まれている

  1. /etc/zsh/zshenv
  2. $ZDOTDIR/.zshenv
  3. /etc/zsh/zprofile
  4. $ZDOTDIR/.zprofile
  5. /etc/zsh/zshrc
  6. $ZDOTDIR/.zshrc
  7. /etc/zsh/zlogin
  8. $ZDOTDIR/.zlogin

自分の環境では、ZDOTDIR は明示的に指定していないので、~/ になる

今回、zshenv でtmuxを起動したところ、その時点ではLANGが設定されていないような挙動を見せたため、そこを確認したい。
以下のスクリプトをZDOTDIRの各設定ファイルに書けばログイン時の出力で必要最低限の出力が得られるはず。

echo "LANG=$(echo $LANG) in .zprofile"

結果は以下の通りで、やはりzshenvの段階でLANGが読めていなかった。

LANG= in .zshenv
LANG=ja_JP.UTF-8 in .zprofile
LANG=ja_JP.UTF-8 in .zshrc

修正したこと

ここまでの挙動から、/etc/zsh/zshenv, $ZDOTDIR/.zshenv の両方を処理したあとでtmuxを起動するのが確実そう。

以下の記事を読むと、実際zshenvには環境変数の設定だけを書いて、コマンド実行はなるべく避けたほうがよさそう、ということがわかる。
(つまりこれまでの設定はアンチパターンだったということ)

というわけで、以下のスクリプトは最終的に ~/.zlogin で書かれることになった。

# TMUX
if which tmux >/dev/null 2>&1; then
    # if no session is started, start a new session
    test -z ${TMUX} && tmux

    # when quitting tmux, try to attach
    while test -z ${TMUX}; do
        tmux attach || break
    done
fi

結論