22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

zshのパス設定の順序の問題

Posted at

zshでのパス設定

シェルを使うにあたって複数のrubyやpythonにパスが通るのはよくあることだが、
その際デフォルトで呼び出されるのは一番最初にパスが通ったものになる。

具体的には

echo $PATH

で出力されるパスを順に探し最初に見つかったものがデフォルトとなる。

ここでのデフォルトとは、例えば単にrubyと打てば実行されるrubyのことである。

zshでは

export PATH=...

という記法の他に

path=(
  ~/.anyenv/bin(N-/)
  $path
)

というふうにpathという配列でスマートに設定できる。

ところで、上の設定は標準のパス設定よりも優先してanyenvのパスを通してある。
したがって、これを経由してインストールしたrbenvやruby、またはgemのパスを最優先したいという意図である。

これまでこのパスを通す設定を.zshrcに記述していた。
駆け出しの頃oh-my-zshをインストールしたらパスの設定を自動的に.zshrcに書き出してくれたから良かれと思っていた。
しかしインタラクティブシェルでしか.zshrcは読み込まれないため、そうでない場合は時折不便だった。
使用するコマンドに対してフルパスで指定するなどしていた(移植性は高い)が、冗長になるのが悩みだった。

そこでパスを通す設定を全ての場合に読み込まれる.zshenvに記述することにした。

問題

だが問題は起こった。
これまでと読み込まれるパスの順序が変わったのだ。

zshの設定ファイルの読み込み順序と使い方Tipsまとめ

に目を通して見ると
.zshenvと.zshrcの読み込みの間に
/etc/zprofileと/etc/zshrcの読み込みが割り込んでいるのがわかる。

catで読んで見るとどうやら/etc/zprofileが悪さをしていると想像がつく。
/usr/libexec/path_helper
の出力を実行しいくつかのパスを先頭に追加している。

これまでは/etc/zshrcよりも後に呼び出される.zshrcでパスの設定を記述していたので問題が表面化しなかった。

もっとも単純な問題解決法は/etc/zprofileを書き換えることである。

だが、このファイルを書き換えるのはあまり気持ちよくない。
一応、このMacにログインするユーザーたちにとっては自動的に大方必要なパスを通してくれる仕組みだ。

そう、補足しておくと/etcはこのMacで共通で使うものなので、安易に消したり足したりするものではない。

それに標準的な環境を書き換える作業を伴うのは移植性(ポータビリティ)が悪い。

解決策

そこで、

brew install --without-etcdir zsh

を実行し/etc以下を読まないなんとも都合の良い仕様のzshをビルドする。

おまけにMac標準のzshよりもバージョンは更新されているので新しいものが使える。

これをデフォルトシェルにするため

chsh -s /user/local/bin/zsh

を実行する。

が、、、しかしこれは標準なシェルではないという趣旨でエラーとなる。
先ほどのポリシーに反するが、/etc/shellsを編集する。

/etc/shellsの最後に/usr/local/bin/zshを書き足す。
これで/usr/local/bin/zshがデフォルトシェルとして選択できるようになった。

気を取り直してchshする。
パスワードを要求されるが入力すると晴れてこの都合の良いzshがデフォルトシェルとして使えるようになる。

ここで以下のように設定するのがベストだと思うので提案する。

# system-wide environment settings for zsh
if [ -x /usr/libexec/path_helper ]; then
    eval `/usr/libexec/path_helper -s`
fi

# environment paths
path=(
  ~/.anyenv/bin(N-/)
  $path
)

自分がパスを設定するよりも先に際ほど疫病神だった設定を呼び出す。
すると優先順位としては最後の方が良いが一応パスが通っててくれたら助かるものにもパスが通り、なおかつこの後次第では自分の設定を優先にできる。

ちなみに例の設定によって追加されるパスはbrewやcaskによってインストールしたLaTeX環境やMono環境に付随するコマンドがある場所だったりする。

むしろ、今後インストールする環境にも自動的にパスを通していくために設定しない手はない。

今回、パスなどの全てのシチュエーションでロードされるべき設定は.zshenvに移したので、.zshrcはおおよそoh-my-zshの設定くらいが残った。
.zshrcはインタラクティブシェルのための設定ファイルなので非常に整理されて綺麗になったと思う。(oh-my-zshが提供する機能はzshとの対話機能を充実させるためのものばかりだからだ。)

自分は複数のMacでの環境の同期、および新規Macへの移植性を重視するので

OMZ=$HOME/.oh-my-zsh

# install oh-my-zsh if needed
if [[ ! -e $OMZ ]]; then;
  cd ~ &&  sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
fi

を.zshrcの冒頭付近に足しておいた。
これでその環境でzshを初めて起動した際は自動でoh-my-zshがインストールされるので、.zshrc内のそれ以降のoh-my-zshに関する設定行にてエラーを起こさないようになった。

また、.zshenvの冒頭にはこんな仕掛けをしておいた。

# use zsh version of without-etcdir to ignore setting files under /etc directory
if [ $SHELL = '/bin/zsh' ]; then
  echo -e '\e[31mDefault zsh loads /etc/zprofile. It causes changing order of environment path.'
  if [[ ! -x /usr/local/bin/zsh  ]]; then
    echo '\e[32mInstalling zsh without etcdir'
    /usr/local/bin/brew install --without-etcdir zsh
    echo '\e[32mPlease add /usr/local/bin/zsh to /etc/shells to use it as default shell!'
  fi
  chsh -s /usr/local/bin/zsh
  exit
fi

これで標準のzshを使っている場合、/etcなしのバージョンのzshのインストールが自動で行われ、その後デフォルトシェルへの登録を余儀なくされるだろう。

これでMac環境、zshでのパス通しは完璧だ。

最後の詰め

システムによって.zshrcなどの設定ファイルに勝手にパスを追加する行が末尾に挿入されることがあった。
それも最後の方に通してあったはずのパスを頭に持って来させようとする設定行だったりする。

そこで、それらの横暴から環境を守るためのバリケードがこれだ。

# prevent line added Unintentionally
:<< COMMENTOUT

このバリケードをファイルの末尾に置いておく。

もちろん次にCOMMENTOUTと書くわけではないので末尾に何を追加しても、効力を持たない空間に逃してしまう。

これで漢の堅牢なzsh環境が出来上がったのではないだろうか?

22
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?