はじめに
.zshenv とかで path にディレクトリを追加するとき、こんなふうに普通に追加してる人がいると思う。
# path の先頭に $HOME/bin を追加する(あんまり良くない例)
path=($HOME/bin $path)
これでも間違いというわけではないんだけど、追加するディレクトリが存在しない場合も path に追加されて、結果として無駄なディレクトリが含まれている状態になってしまう。あんまりうれしくない。
でも zsh にはちゃんとそれを防ぐ方法があって、ディレクトリ名の後ろに (N-/)
を付ければうまく解決できる。
# 良い例
path=($HOME/bin(N-/) $path)
これで $HOME/bin
が存在しているときだけ path に追加する、という動作になる。
ディレクトリを複数同時に追加するときも同じように並べて書けば OK。
path=($HOME/bin(N-/) /usr/local/bin(N-/) $path)
解説
なんにも考えずにコピペで使ってもいいんだけど、もうちょっと詳しく説明してみる。
この (N-/)
はファイル名修飾子というやつで、ファイル名に条件つけて絞り込みができるようになる。この場合は N
, -
, /
という3つの条件を指定してる。
まず /
というのは「ディレクトリだけ」という意味で、ディレクトリが存在するときだけ ()
の左側の値に展開される。
# /tmp というディレクトリ、/etc/passwd という通常のファイルが存在するとする
# 存在するディレクトリ名は OK
% echo /tmp(/)
/tmp
# 存在しないディレクトリの場合はエラーになる
% echo /hoge(/)
zsh: no matches found: /hoge(/)
# ディレクトリじゃなくて通常ファイルの場合もエラーになる
% echo /etc/passwd(/)
zsh: no matches found: /etc/passwd(/)
で、N
を付けるとマッチしない(ディレクトリが存在しない)時にエラーの代わりに黙って空文字列に展開されるようになる。
# ディレクトリが存在する場合は N 無しと同じ
% echo /tmp(N/)
/tmp
# ディレクトリが存在しない場合は空文字列
% echo /hoge(N/)
これを使うと $HOME/bin が存在しない状態で path=($HOME/bin(N/) $path)
とした時に $HOME/bin(N/)
の部分が空文字列になるので、結果として path には何も追加されない、という動作になる。
これでだいたい問題ないんだけど、$HOME/bin
がシンボリックリンクだったときに困る。$HOME/bin
がシンボリックリンクで存在するディレクトリを指している場合は本当は path に追加して欲しいんだけど、(N/)
は「ディレクトリであった場合だけ展開する」という意味なので、空文字列になってしまう。
そういう時のために -
っていうのがある。これを付けるとシンボリックリンクの実態を追いかけて条件をチェックするようになる。つまり、(N-/)
は「ディレクトリが存在する、またはシンボリックリンクで存在するディレクトリを指している」という条件指定になる。
# $HOME/bin はシンボリックリンクで、$HOME/scripts というディレクトリを指しているとする
% ls -l $HOME
bin -> scripts
scripts/
# $HOME/bin はディレクトリではないので空文字列になる
% echo $HOME/bin(N/)
# - を付けるとシンボリックリンクの実態に対して条件をチェックする
% echo $HOME/bin(N-/)
/home/mollifier/bin
というわけで、結論として (N-/)
を付けるとディレクトリが存在するときだけ path に追加する、という動作になる。
path=($HOME/bin(N-/) $path)
こうやっておくと .zshrc をいろんな環境で使いまわす時に変なのが path に追加されなくて便利。path 以外でも fpath とか manpath とかディレクトリを追加する系の環境変数の場合は同じようにしたほうが良いと思う。
if 文とかでチェックしてもいいんだけど、こっちのほうが1行で済んでスマートだと思うのでぜひ試してみてください!