Cygwin環境なのですが,PATHに入っているパス(コマンドサーチパス)に重複があります.
$ echo $PATH /home/takehiko/Lib/ruby/bin:/cygdrive/c/Program Files/SlikSvn/bin:/cygdrive/c/Program Files/TortoiseSVN/bin:/home/takehiko/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/cygdrive/c/tex/bin:/cygdrive/c/Program Files/SlikSvn/bin/:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:(以下略)
/usr/local/bin, /usr/bin, /binが重複しています.しかし,~/.zshrc他を見ても,どこで重複されているかはわかりません.
見苦しいし,ついでにzshの勉強を兼ねて,この重複を除去することにしました.
実は10年以上前に,重複を取り除くPerlスクリプトを書いたことがあります.
#!/usr/bin/perl # pathpack : $path あるいは $PATH のうち、無駄なパス名を省く # 使用方法 : set path=(`pathpack $path`) # PATH=`pathpack $PATH` $sep = " "; # パス名間の区切り子 @path = @ARGV; # パス名配列 if ($#path == $[) { $_ = $path[$[]; if (/:/) { @path = split(/:/, $_); $sep = ":"; } elsif (/;/) { @path = split(/;/, $_); $sep = ";"; } } %path = (); # パス配列用の連想配列 for ($pathnum = $#path; $pathnum >= $[; $pathnum--) { $p = $path[$pathnum]; $path{$p} = $pathnum; } @newpath = (); for ($pathnum = $#path; $pathnum >= $[; $pathnum--) { $p = $path[$pathnum]; if ($path{$p} == $pathnum) { unshift(@newpath, $p); } } $" = $sep; print "@newpath\n"; exit;
パスの環境変数がPATHのsh系でも,pathのcsh系でも実行可能です(ちなみにzshでもうまくいきました).しかしPerlから離れてだいぶ過ぎまして,コードの保守もままなりません.
zshで書くにあたり,sedなどの外部コマンドは一切使用しないという制約を設けました.シェル起動時に実行しても,時間をかけないようにしたいのです.
それで,コードです.
function debug_mode() { return 1 } # 0のときデバッグモード function pathpack() { local q=':' for p in $path do debug_mode && echo $p [ "$q" '==' "${q/:$p:/}" ] && q="$q$p:" debug_mode && echo $q done q=${q#:} q=${q%:} debug_mode && echo $q debug_mode || PATH=$q }
何をしているのかというと,forループで,パスを先頭から順に取り出して,初出のパスなら,文字列をとる変数qに付け加えています.初期値を ":" とし,追加する際には "パス:" を付け加えます.最終的には ":パス1:パス2:…:パス最後:" という形の文字列になります.先頭と末尾の ":" を取り除き,PATHに代入すれば,完了です.
パスの取り出しには,文字列の環境変数PATHからパラメータ分割するのではなく,配列のpathを使用しました.
「[ "$q" '==' "${q/:$p:/}" ]」が肝心なところですが,すでにパスpが登録されていれば,右辺ではqから該当箇所のパスを除去した文字列ができ,両辺が異なるため偽となります.パスpが登録されていなければ,除去できないので両辺の文字列は等しく,真となるので,&&以降の処理すなわちパス追加をします.なお,「==」をクォートしていなければ,「zsh: = not found」というエラーが出ます.また,「${q/:$p:/}」のところを「${q#:$p:}」としても,うまくいきませんでした.
最後に,debug_modeですが,デバッグモードを簡潔に表すために定義した関数です.以下の効果があります.
- debug_modeがreturn 0のときに,「debug_mode && コマンド」はコマンドを実行する
- debug_modeがreturn 0のときに,「debug_mode || コマンド」はコマンドを実行しない
- debug_modeがreturn 1のときに,「debug_mode && コマンド」はコマンドを実行しない
- debug_modeがreturn 1のときに,「debug_mode || コマンド」はコマンドを実行する
動作確認しときましょう.
$ echo $PATH /home/takehiko/Lib/ruby/bin:/cygdrive/c/Program Files/SlikSvn/bin:/cygdrive/c/Program Files/TortoiseSVN/bin:/home/takehiko/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/cygdrive/c/tex/bin:/cygdrive/c/Program Files/SlikSvn/bin/:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:(以下略) $ echo $PATH | md5sum 40824a1dfb341d08282ce4fa4fa2b5c4 *- $ perl ~/bin/pathpack "$PATH" /home/takehiko/Lib/ruby/bin:/cygdrive/c/Program Files/SlikSvn/bin:/cygdrive/c/Program Files/TortoiseSVN/bin:/home/takehiko/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/cygdrive/c/tex/bin:/cygdrive/c/Program Files/SlikSvn/bin/:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:(以下略) $ perl ~/bin/pathpack "$PATH" | md5sum 6a37db66349e004af218f887f7040dca *- $ function debug_mode() { return 0 } $ pathpack | tail -n 1 $ echo $PATH /home/takehiko/Lib/ruby/bin:/cygdrive/c/Program Files/SlikSvn/bin:/cygdrive/c/Program Files/TortoiseSVN/bin:/home/takehiko/bin:/usr/local/bin:/usr/bin:/bin:/usr/X11R6/bin:/cygdrive/c/tex/bin:/cygdrive/c/Program Files/SlikSvn/bin/:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:(以下略) $ pathpack | tail -n 1 | md5sum 6a37db66349e004af218f887f7040dca *-
最後に,~/.zshrcに,debug_modeとpathpackの定義のあとに,「pathpack」を書けば,設定完了です.