…事件は、profileのお掃除中に起きた。
(どうやら、この案件は path_helper問題として知られているようです。この記事はそれに辿り着くまでにEnchanが遠回りをするお話。)
みなさんは普段、なんのシェルを使っていますか?
bash, zsh, fish, ....
私はずっとbashを使っていたのですが、少し前にzshに乗り換えた民だったりします。 若干クセがあるように感じますが、正直これはこれでありかなと思っています。
…別方向の議論が始まってしまいそうなのでこの辺でやめておきましょう。
シェルのprofile
ひとくちにシェルといってもさまざまなものがありますが、ほとんど(全て?)のシェルに共通して 設定ファイル という概念があります。一般常識です。
bashなら .bash_profile
や.bashrc
、zshなら.zshenv
, .zshrc
, .zprofile
等々、読み込まれるタイミングや順番などによってさまざまな名前がついており、
コマンドエイリアスや環境変数の設定の際はここに値を書くことが多いかと思います。
今回注目していくのは PATH
です。パスを通す なんてよく言われる例のアレ。
私は .bash_profile
, .zshenv
などに書くことが多いです。(どうやら.zshenv
に書くべきではないようですが…)
どこでコピペしても基本的に動くように、基本構成はこんな感じにしています:
# # zshenv # # Initial PATH export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/Library/Apple/usr/bin ... (略) ... # Python export PATH=/usr/local/opt/python@3.9/bin/:$PATH ... (略) ... # Custom built export PATH=$HOME/local/bin:$PATH # load .zshrc if exists test -r ~/.zshrc && . ~/.zshrc # prompt export PS1="%n@%m %1~ %# " # shell history path export HISTFILE=${HOME}/.zsh_history
パスがなんらかの要因で吹っ飛んでも困らないように 一応の初期値と、そのほか追加で入れたアプリケーションへのパスを先に通しています。
下の方で.zshrc
を読み、プロンプトを設定し、シェル履歴の設定を一通り終わらせたうえで .zshenvの処理が終了するとそういうわけです。
つまり、ログイン後の$PATH
は以下のようになっているはずです:
/Users/user_name/local/bin ... (略) ... /usr/local/opt/python@3.9/bin/ ... (略) ... /usr/local/bin /usr/local/sbin /usr/bin /usr/sbin /bin /sbin /Library/Apple/usr/bin
定義した順にパスが並び、一番最後にPATHの初期値 (# Initial PATH
以降で設定した値) が並んでいるはずです。
というか実際に .zshenv
内で echo $PATH | sed -e "s/:/\n/ig"
を実行するだけで同じ結果が得られます。
ところが。
パスの並びが変わっている
ログイン直後に同様のコマンドを実行すると…
% echo $PATH | sed -e "s/:/\n/ig /usr/local/bin /usr/bin /bin /usr/sbin /sbin /Library/TeX/texbin /Library/Apple/usr/bin /Users/user_name/local/bin ... (略) ... /usr/local/opt/python@3.9/bin/ ... (略) ... /usr/local/sbin
なんだか大変な順番になってしまっています。
PATHの初期値…のようなものが先頭に来てしまっており、
これでは ~/ 以下にインストールしたコマンドをすぐに呼び出せなくなってしまいます。
(~/local/bin
と/usr/local/bin
に同名のコマンドがあった場合、後者が優先されてしまう)
設定ファイルの適用順を整理する
もしかすると私の知らない設定ファイルがあるかもしれないので、一旦zshの設定ファイルを見にいきます。
ArchWikiの「Zsh」の項目によれば…
ログイン時、Zsh は以下のファイルをこの順番で読み込むようです:
/etc/zsh/zshenv
$ZDOTDIR/.zshenv
<--- ここでPATHを初期化/etc/zsh/zprofile
/etc/profile
$ZDOTDIR/.zprofile
/etc/zsh/zshrc
$ZDOTDIR/.zshrc
/etc/zsh/zlogin
$ZDOTDIR/.zlogin
<--- コマンドを実行できる状態になるのはこれ以降/etc/zsh/zlogout
$ZDOTDIR/.zlogout
PATHを設定しているのは ~/.zshenv
なので、そこから$ZDOTDIR/.zlogin
までのファイルで変更が加わる可能性があります。
(なお、手元の環境ではちょっとパスが違っていた(man参照)ので適宜読み替えています)
まず/etc/zsh/zprofile
。macでは /etc/zprofile
にありました。 /usr/libexec/path_helper
に引数を渡して呼び出しているようです。
…
ん?
path_helperって…なんでしたっけ?
macOSのおせっかい(超絶失礼)
manによれば、path_helperとは PATH環境変数を構築するためのヘルパー であり…
path_helperユーティリティは、ディレクトリ /etc/paths.d および /etc/manpaths.d 内のファイルの内容を読み取り、 それらの内容をそれぞれ PATH および MANPATH 環境変数に追加します。(MANPATH 環境変数は、環境にすでに設定されていない限り変更されません。)
これらのディレクトリ内のファイルには、1行に1つのパス要素が含まれている必要があります。 これらのディレクトリを読み取る前に、デフォルトのPATH値とMANPATH値がそれぞれファイル /etc/paths と /etc/manpaths から取得されます。
…とのこと。つまりOSの都合の良いようにPATHを編集する コマンドのようです(超絶失礼)。
つまり.zshenv等をいじることで解決できる問題ではないということになります。 かなりイヤです。
対策
先述の通り、この案件は path_helper問題として知られており、さまざまな方々によって回避や対策が試みられているようです。
/etc/paths
に目的のパスを追加してもよかったのですが、管理者権限ないといじれないファイルに設定散りばめるのもちょっと抵抗があったので…
(あと他ユーザでログインした時に何か問題が発生するのもちょっとうーん)
今回は~/.zshrc
にパス設定を書き換えるコードを追加しました。
# .zshrc export PATH=$HOME/local/bin:$PATH
Homebrewではシンボリックリンクを/usr/local
配下に置くことで対応しているようなので、もしかするとこの方が色々とやりやすいかもしれません。
% ll /usr/local/bin/python3 lrwxr-xr-x 1 user admin 39B 5 23 09:11 /usr/local/bin/python3 -> ../Cellar/python@3.9/3.9.13/bin/python3
なんかちょっとやだなあ
なーんというか、正直あんまり綺麗じゃないなあ というのが正直なところがあり…
これがmacOSの方針であり、Appleの方針である以上我々はそれに従うしかないのはそれはそうですが、シェルの動作くらい自分で完全に決めさせていただきたいものです。
(もし何か良い方法ありましたらTwitterで教えてください)
いっそOSから作ってしまおうか。 そんな思いが頭の片隅にpushされるトラブルなのでした。
fflush(stdout)