今日は「z」というディレクトリ移動ツールのお話。
「z-fish」とは「z」の「fish」シェル版です。
「z」は、先に紹介した「autojump」と目的は同じですが、優先するディレクトリの判定方法に違いがあります。
曰く、今まで何回も訪れたディレクトリ同様、最近になって頻繁に訪れているディレクトリも重要だろうと。(たぶん)
「autojump」がpythonで実装されているのに対し、「z」はシェルスクリプトのみで実装しているところも違います。
この「z」が「bash」シェル「zsh」シェル対応なので、「fish」シェル用に書き直されたのが「z-fish」というわけです。
取り敢えず、インストール方法から。
「z」のインストールは簡単で、
$ brew install z
後は「.bashrc」なり「.zshrc」なりに
. `brew –prefix`/etc/profile.d/z.sh
この一行を書いて終わりです。
「z-fish」のインストールは、少し手間がかかります。
ホームディレクトリかどこかで、
$ git clone https://github.com/sjl/z-fish.git
とすると「z-fish」というディレクトリが作成されてファイルがダウンロードされます。
その中の「z.fish」をどこでも適当な場所に置きます。
(私は環境の都合上「/opt/local/etc」に置きました)
それから、「~/.config/fish/config.fish」に以下を書いておきます。
# Read Z extension
##
if test -f /opt/local/etc/z.fish
. /opt/local/etc/z.fish
end
ディレクト名は適時書き換えてくださいね。
後は、「~/.config/fish/functions/fish_prompt.fish」に、
z --add "$PWD"
を書いておきます。
この辺りは、先の記事 をご覧ください。
使い方は、「autojump」の「j」コマンドと同じく「z」コマンドでディレクトリを移動します。
ただ単に「z」と入力した場合は、現在のデータベースの順位順にディレクトリを一覧表示します。
「j」も「z」もデータベースさえ育っていれば、
$ z u l b
で「/usr/local/bin」に移動するかもしれません。
「z」のデータベースは「~/.z」なのですが、ちと構成が理解しづらいところがあります。
私が「autojump」と併用している理由は、他のシェル用に展開するのにデータベースを利用しやすいから、というのと、「z」の真価をまだ評価中だからであって、普通はどちらかだけで良いと思います。
さて・・・
タイトルに書いた「sh/ksh」版を説明します。
最初に触れたように「z」はシェルスクリプトで書かれています。
だから「z-fish」というものも生まれたのですが、「ksh系」シェルや「dash」シェルにはどちらも使えません。
そこでこれらシェル用に、「autojump」と「z」と両方埋め込んだスクリプトを作りました。
本来は差分を取って・・・という事にしないといけないのだと思うのですが、ここでのやり方がまだわかっていませんし、「WTFPL license」(どうぞご勝手に)ということですので、以下そのまま掲載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | # Copyright (c) 2009 rupa deadwyler under the WTFPL license # maintains a jump-list of the directories you actually use # # INSTALL: # * put something like this in your .bashrc/.zshrc: # . /path/to/z.sh # * cd around for a while to build up the db # * PROFIT!! # * optionally: # set $_Z_CMD in .bashrc/.zshrc to change the command (default z). # set $_Z_DATA in .bashrc/.zshrc to change the datafile (default ~/.z). # set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution. # set $_Z_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself. # set $_Z_EXCLUDE_DIRS to an array of directories to exclude. # # USE: # * z foo # cd to most frecent dir matching foo # * z foo bar # cd to most frecent dir matching foo and bar # * z -r foo # cd to highest ranked dir matching foo # * z -t foo # cd to most recently accessed dir matching foo # * z -l foo # list all dirs matching foo (by frecency) # Modified for (K)SH by みろす on 02/Mar/2013 _z() { _z_local_datafile="${_Z_DATA:-$HOME/.z}" # bail out if we don't own ~/.z (we're another user but our ENV is still set) [ -f "$_z_local_datafile" -a ! -O "$_z_local_datafile" ] && return # add entries if [ "$1" = "--add" ]; then shift # $HOME isn't worth matching [ "$*" = "$HOME" ] && return # don't track excluded dirs for _z_local_exclude in "${_Z_EXCLUDE_DIRS[@]}"; do [ "$*" = "$_z_local_exclude" ] && return done # maintain the file _z_local_tempfile="$(mktemp $datafile.XXXXXX)" ¦¦ return while read line; do [ -d "${line%%\¦*}" ] && echo $line done < "$_z_local_datafile" ¦ awk -v path="$*" -v now="$(date +%s)" -F"¦" ' BEGIN { rank[path] = 1 time[path] = now } $2 >= 1 { if( $1 == path ) { rank[$1] = $2 + 1 time[$1] = now } else { rank[$1] = $2 time[$1] = $3 } count += $2 } END { if( count > 6000 ) { for( i in rank ) print i "¦" 0.99*rank[i] "¦" time[i] # aging } else for( i in rank ) print i "¦" rank[i] "¦" time[i] } ' 2>/dev/null >¦ "$_z_local_tempfile" if [ $? -ne 0 -a -f "$_z_local_datafile" ]; then env rm -f "$_z_local_tempfile" else env mv -f "$_z_local_tempfile" "$_z_local_datafile" fi # tab completion elif [ "$1" = "--complete" ]; then while read line; do [ -d "${line%%\¦*}" ] && echo $line done < "$_z_local_datafile" ¦ awk -v q="$2" -F"¦" ' BEGIN { if( q == tolower(q) ) nocase = 1 split(substr(q,3),fnd," ") } { if( nocase ) { for( i in fnd ) tolower($1) !~ tolower(fnd[i]) && $1 = "" } else { for( i in fnd ) $1 !~ fnd[i] && $1 = "" } if( $1 ) print $1 } ' 2>/dev/null else # list/go unset _z_local_fnd unset _z_local_list unset _z_local_typ unset _z_local_last while [ "$1" ]; do case "$1" in -h) echo "z [-h][-l][-r][-t] args" >&2; return;; -l) _z_local_list=1;; -r) _z_local_typ="rank";; -t) _z_local_typ="recent";; --) while [ "$1" ]; do shift; _z_local_fnd="$_z_local_fnd $1";done;; *) _z_local_fnd="$_z_local_fnd $1";; esac; _z_local_last=$1; shift; done [ "$_z_local_fnd" ] ¦¦ _z_local_list=1 # if we hit enter on a completion just go there case "$_z_local_last" in # completions will always start with / /*) [ -z "$_z_local_list" -a -d "$_z_local_last" ] && cd "$_z_local_last" && return;; esac # no file yet [ -f "$_z_local_datafile" ] ¦¦ return _z_local_cd="$(while read line; do [ -d "${line%%\¦*}" ] && echo $line done < "$_z_local_datafile" ¦ awk -v t="$(date +%s)" -v list="$_z_local_list" -v typ="$_z_local_typ" -v q="$_z_local_fnd" -F"¦" ' function frecent(rank, time) { dx = t-time if( dx < 3600 ) return rank*4 if( dx < 86400 ) return rank*2 if( dx < 604800 ) return rank/2 return rank/4 } function output(files, toopen, override) { if( list ) { cmd = "sort -n >&2" for( i in files ) if( files[i] ) printf "%-10s %s\n", files[i], i ¦ cmd if( override ) printf "%-10s %s\n", "common:", override > "/dev/stderr" } else { if( override ) toopen = override print toopen } } function common(matches) { # shortest match for( i in matches ) { if( matches[i] && (!short ¦¦ length(i) < length(short)) ) short = i } if( short == "/" ) return # shortest match must be common to each match. escape special characters in # a copy when testing, so we can return the original. clean_short = short gsub(/[\(\)\[\]\¦]/, "\\\\&", clean_short) for( i in matches ) if( matches[i] && i !~ clean_short ) return return short } BEGIN { split(q, a, " "); oldf = noldf = -9999999999 } { if( typ == "rank" ) { f = $2 } else if( typ == "recent" ) { f = $3-t } else f = frecent($2, $3) wcase[$1] = nocase[$1] = f for( i in a ) { if( $1 !~ a[i] ) delete wcase[$1] if( tolower($1) !~ tolower(a[i]) ) delete nocase[$1] } if( wcase[$1] && wcase[$1] > oldf ) { cx = $1 oldf = wcase[$1] } else if( nocase[$1] && nocase[$1] > noldf ) { ncx = $1 noldf = nocase[$1] } } END { if( cx ) { output(wcase, cx, common(wcase)) } else if( ncx ) output(nocase, ncx, common(nocase)) } ')" [ $? -gt 0 ] && return [ "$_z_local_cd" ] && cd "$_z_local_cd" fi } alias ${_Z_CMD:-z}='_z 2>&1' [ "$_Z_NO_RESOLVE_SYMLINKS" ] ¦¦ _Z_RESOLVE_SYMLINKS="-P" # for Autojump j() { cd `autojump $*` } # Prompt for Z extension and Autojump [ "$_Z_NO_PROMPT_COMMAND" ] ¦¦ { PS1='$(eval "_z --add $(pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null) 2>/dev/null")$(autojump -a $PWD >/dev/null 2>&1)'$PS1 } |
長いですが、ローカル変数の使用をやめたくらいで、実際はほとんど変わっていません。
プロンプトの関係で「autojump」と「z」を同居させた、というのが追加部分です。
これを、例えば「/opt/local/etc/jz.sh」みたいに保存して、「.kshrc」やその他「sh」で読み込む設定ファイルに、
. /opt/local/etc/jz.sh
とでも書いておけば、「dash」シェルや「ksh系」シェルで「j」コマンドと「z」コマンドが使えるようになります。
もし「autojump」が重複するようでしたら、ごめんなさい。
こちらを優先ということでお願いします。
残るは「csh」と「rc」なんだけどこれがなかなか・・・