GNU Screen を主に使っていますが、ふと気づくと PATH
環境変数の中身がかなり重複してカオスになっていたので、整理しました。
環境は MacOSX と CentOS 6.x です。
また、その後 tmux でも試してみたので、その結果も合わせてまとめています。
※2016/4/19 追記:
Zsh on MacOSX でも動作確認しました。
TL;DR
- tmux や screen を使っていると
~/.bashrc
や~/.zshrc
が複数回読み込まれるのは避けられなさそう。 - Zsh なら
typeset -U path PATH
でいい。 - pathctl というシェル関数のライブラリを作って重複させずにエントリの位置を調整するようにした。
要点をもう少し
- Bash の場合、基本的には
PATH
を更新する設定は~/.bash_profile
に書くのがよさそう-
~/.bashrc
に書くとシェルが起動する度に読み込まれる。
-
- Zsh の場合は
~/.zshenv
,~/.zshrc
ともシェル起動時に読み込まれるようだ。1 -
xxenv
(rbenv など)の初期設定コマンドを~/.zshenv
に書くと/usr/bin
などの後ろになってしまったので、自作した pathctl を使いつつ~/.zshrc
に記述した。
重複を避けたい方へオススメのやり方
(気にしない、という選択肢も割と現実的な気はします。)
気にする方向けには、以下のようなやり方が比較的手軽かなと思いました。
- ふつうに
export PATH=$PATH:/path/to/bin
のように追加していく - Zsh なら
~/.zshenv
か~/.zshrc
のどこかにtypeset -U path PATH
を書いておく - Bash なら
~/.bashrc
の末尾で、以下のようにしてユニークになるようにする
_path=""
for _p in $(echo $PATH | tr ':' ' '); do
case ":${_path}:" in
*:"${_p}":* )
;;
* )
if [ "$_path" ]; then
_path="$_path:$_p"
else
_path=$_p
fi
;;
esac
done
PATH=$_path
unset _p
unset _path
このコードは大抵のシェルで動作すると思います。
※手元の Mac では bash 3系, 4系, zsh, sh(実体はbash), dash で確認しました。
わざわざ書くのが面倒という方は pathctl を読み込んで以下のようにも書けます。
. pathctl.shrc
pathctl_uniq
要点は以上のようなところです。
以下はもう少し詳しい、各トピックについての補足です。
補足
tmux
や screen
で複数回 ~/.bash_profile
等が読み込まれる
screen
x Bash でセッションを起動したばかりのときは、新しいウィンドウで再読み込みされるのは ~/.bashrc
だけでしたが、サーバからログアウトして再ログイン & attach すると、~/.bash_profile
も再読み込みしているようでした。
ひょっとしたら環境起因の問題が有るかもしれません。
tmux
の場合は、pane や window を生成する度に ~/.bash_profile
を読み込み直しているような挙動のようです。
~/.zshenv
, ~/.zshrc
はいずれの場合も繰り返し読み込まれると思います。
pathmunge と pathctl について
RedHat 系の OS なら「pathmunge2 という関数があるよ」とコメントで教えて頂きました。pathmunge は /etc/profile
に定義されている関数で、対象が PATH に含まれてないときのみ、先頭か末尾にエントリを追加します。
ただ、自分の環境では /etc/profile
の末尾に unset -f pathmunge
されており、 /etc/profile
を読み込んでも関数を使うことはできませんでした。
そこで、同等の機能を提供する pathctl というシェルスクリプトを書きました。
pathctl_push(_f) パス
で PATH 末尾に追加、 pathctl_unshift(_f) パス
で PATH 先頭に追加します。
xxenv
について
eval $(pyenv init -)
や eval $(rbenv init -)
は内部的に ~/.(py|rb)env/shims
を PATH
の先頭に追加することを行っているようです。
これは pathmunge 等では回避できないので、該当のパスが PATH に含まれていたら実行しない、のような if 文を書きました。
または、 pathctl_uniq
関数で重複を削除することもできます。
どうしても ~/.bashrc
から読み込みたかったもの
ふつう、そういうものはあまり無いかと思いますが、自分の場合 ~/.bashrc.d/
に置いたものをまとめて読む処理を ~/.bashrc
に書いていたので、必要でした。
※シェル起動時、環境変数やシェル変数は引き継がれますが、alias
などは引き継がれないようなので、そういうものは ~/.bashrc
から読まなければならない、と思っています。
このケースだと、~/.bash_profile.d/
でも作って ~/.bash_profile
から読むようにしてもよかったです。
…が、上で見てきたように ~/.bash_profile
も複数回読み込まれることがあるので、結局は pathctl が必要でした。
Zsh について参考
落ち穂拾い
screen を使っているとき、~/.bash_profile
側でガード変数を使って ~/.bashrc
を2回以上ロードしないように…というのもやってみました。
が、この場合 screen のセッションで ~/.bashrc
が読み込まれなくなり、意図した挙動にはなりませんでした。