├── LICENSE ├── Makefile ├── README ├── z.1 └── z.sh /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | readme: 2 | @groff -man -Tascii z.1 | col -bx 3 | 4 | .PHONY: readme 5 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Z(1) User Commands Z(1) 2 | 3 | 4 | 5 | NAME 6 | z - jump around 7 | 8 | SYNOPSIS 9 | z [-chlrtx] [regex1 regex2 ... regexn] 10 | 11 | AVAILABILITY 12 | bash, zsh 13 | 14 | DESCRIPTION 15 | Tracks your most used directories, based on 'frecency'. 16 | 17 | After a short learning phase, z will take you to the most 'frecent' 18 | directory that matches ALL of the regexes given on the command line, in 19 | order. 20 | 21 | For example, z foo bar would match /foo/bar but not /bar/foo. 22 | 23 | OPTIONS 24 | -c restrict matches to subdirectories of the current directory 25 | 26 | -e echo the best match, don't cd 27 | 28 | -h show a brief help message 29 | 30 | -l list only 31 | 32 | -r match by rank only 33 | 34 | -t match by recent access only 35 | 36 | -x remove the current directory from the datafile 37 | 38 | EXAMPLES 39 | z foo cd to most frecent dir matching foo 40 | 41 | z foo bar cd to most frecent dir matching foo, then bar 42 | 43 | z -r foo cd to highest ranked dir matching foo 44 | 45 | z -t foo cd to most recently accessed dir matching foo 46 | 47 | z -l foo list all dirs matching foo (by frecency) 48 | 49 | NOTES 50 | Installation: 51 | Put something like this in your $HOME/.bashrc or $HOME/.zshrc: 52 | 53 | . /path/to/z.sh 54 | 55 | cd around for a while to build up the db. 56 | 57 | PROFIT!! 58 | 59 | Optionally: 60 | Set $_Z_CMD to change the command name (default z). 61 | Set $_Z_DATA to change the datafile (default $HOME/.z). 62 | Set $_Z_MAX_SCORE lower to age entries out faster (default 63 | 9000). 64 | Set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution. 65 | Set $_Z_NO_PROMPT_COMMAND to handle PROMPT_COMMAND/precmd your- 66 | self. 67 | Set $_Z_EXCLUDE_DIRS to an array of directory trees to exclude. 68 | Set $_Z_OWNER to allow usage when in 'sudo -s' mode. 69 | (These settings should go in .bashrc/.zshrc before the line 70 | added above.) 71 | Install the provided man page z.1 somewhere in your MANPATH, 72 | like /usr/local/man/man1. 73 | 74 | Aging: 75 | The rank of directories maintained by z undergoes aging based on a sim- 76 | ple formula. The rank of each entry is incremented every time it is 77 | accessed. When the sum of ranks is over 9000, all ranks are multiplied 78 | by 0.99. Entries with a rank lower than 1 are forgotten. 79 | 80 | Frecency: 81 | Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted 82 | rank that depends on how often and how recently something occurred. As 83 | far as I know, Mozilla came up with the term. 84 | 85 | To z, a directory that has low ranking but has been accessed recently 86 | will quickly have higher rank than a directory accessed frequently a 87 | long time ago. 88 | 89 | Frecency is determined at runtime. 90 | 91 | Common: 92 | When multiple directories match all queries, and they all have a common 93 | prefix, z will cd to the shortest matching directory, without regard to 94 | priority. This has been in effect, if undocumented, for quite some 95 | time, but should probably be configurable or reconsidered. 96 | 97 | Tab Completion: 98 | z supports tab completion. After any number of arguments, press TAB to 99 | complete on directories that match each argument. Due to limitations of 100 | the completion implementations, only the last argument will be com- 101 | pleted in the shell. 102 | 103 | Internally, z decides you've requested a completion if the last argu- 104 | ment passed is an absolute path to an existing directory. This may 105 | cause unexpected behavior if the last argument to z begins with /. 106 | 107 | ENVIRONMENT 108 | A function _z() is defined. 109 | 110 | The contents of the variable $_Z_CMD is aliased to _z 2>&1. If not set, 111 | $_Z_CMD defaults to z. 112 | 113 | The environment variable $_Z_DATA can be used to control the datafile 114 | location. If it is not defined, the location defaults to $HOME/.z. 115 | 116 | The environment variable $_Z_NO_RESOLVE_SYMLINKS can be set to prevent 117 | resolving of symlinks. If it is not set, symbolic links will be 118 | resolved when added to the datafile. 119 | 120 | In bash, z appends a command to the PROMPT_COMMAND environment variable 121 | to maintain its database. In zsh, z appends a function _z_precmd to the 122 | precmd_functions array. 123 | 124 | The environment variable $_Z_NO_PROMPT_COMMAND can be set if you want 125 | to handle PROMPT_COMMAND or precmd yourself. 126 | 127 | The environment variable $_Z_EXCLUDE_DIRS can be set to an array of 128 | directory trees to exclude from tracking. $HOME is always excluded. 129 | Directories must be full paths without trailing slashes. 130 | 131 | The environment variable $_Z_OWNER can be set to your username, to 132 | allow usage of z when your sudo environment keeps $HOME set. 133 | 134 | FILES 135 | Data is stored in $HOME/.z. This can be overridden by setting the 136 | $_Z_DATA environment variable. When initialized, z will raise an error 137 | if this path is a directory, and not function correctly. 138 | 139 | A man page (z.1) is provided. 140 | 141 | SEE ALSO 142 | regex(7), pushd, popd, autojump, cdargs 143 | 144 | Please file bugs at https://github.com/rupa/z/ 145 | 146 | 147 | 148 | z January 2013 Z(1) 149 | -------------------------------------------------------------------------------- /z.1: -------------------------------------------------------------------------------- 1 | .TH "Z" "1" "January 2013" "z" "User Commands" 2 | .SH 3 | NAME 4 | z \- jump around 5 | .SH 6 | SYNOPSIS 7 | z [\-chlrtx] [regex1 regex2 ... regexn] 8 | .SH 9 | AVAILABILITY 10 | bash, zsh 11 | .SH 12 | DESCRIPTION 13 | Tracks your most used directories, based on 'frecency'. 14 | .P 15 | After a short learning phase, \fBz\fR will take you to the most 'frecent' 16 | directory that matches ALL of the regexes given on the command line, in order. 17 | 18 | For example, \fBz foo bar\fR would match \fB/foo/bar\fR but not \fB/bar/foo\fR. 19 | .SH 20 | OPTIONS 21 | .TP 22 | \fB\-c\fR 23 | restrict matches to subdirectories of the current directory 24 | .TP 25 | \fB\-e\fR 26 | echo the best match, don't cd 27 | .TP 28 | \fB\-h\fR 29 | show a brief help message 30 | .TP 31 | \fB\-l\fR 32 | list only 33 | .TP 34 | \fB\-r\fR 35 | match by rank only 36 | .TP 37 | \fB\-t\fR 38 | match by recent access only 39 | .TP 40 | \fB\-x\fR 41 | remove the current directory from the datafile 42 | .SH EXAMPLES 43 | .TP 14 44 | \fBz foo\fR 45 | cd to most frecent dir matching foo 46 | .TP 14 47 | \fBz foo bar\fR 48 | cd to most frecent dir matching foo, then bar 49 | .TP 14 50 | \fBz -r foo\fR 51 | cd to highest ranked dir matching foo 52 | .TP 14 53 | \fBz -t foo\fR 54 | cd to most recently accessed dir matching foo 55 | .TP 14 56 | \fBz -l foo\fR 57 | list all dirs matching foo (by frecency) 58 | .SH 59 | NOTES 60 | .SS 61 | Installation: 62 | .P 63 | Put something like this in your \fB$HOME/.bashrc\fR or \fB$HOME/.zshrc\fR: 64 | .RS 65 | .P 66 | \fB. /path/to/z.sh\fR 67 | .RE 68 | .P 69 | \fBcd\fR around for a while to build up the db. 70 | .P 71 | PROFIT!! 72 | .P 73 | Optionally: 74 | .RS 75 | Set \fB$_Z_CMD\fR to change the command name (default \fBz\fR). 76 | .RE 77 | .RS 78 | Set \fB$_Z_DATA\fR to change the datafile (default \fB$HOME/.z\fR). 79 | .RE 80 | .RS 81 | Set \fB$_Z_MAX_SCORE\fR lower to age entries out faster (default \fB9000\fR). 82 | .RE 83 | .RS 84 | Set \fB$_Z_NO_RESOLVE_SYMLINKS\fR to prevent symlink resolution. 85 | .RE 86 | .RS 87 | Set \fB$_Z_NO_PROMPT_COMMAND\fR to handle \fBPROMPT_COMMAND/precmd\fR yourself. 88 | .RE 89 | .RS 90 | Set \fB$_Z_EXCLUDE_DIRS\fR to an array of directory trees to exclude. 91 | .RE 92 | .RS 93 | Set \fB$_Z_OWNER\fR to allow usage when in 'sudo -s' mode. 94 | .RE 95 | .RS 96 | (These settings should go in .bashrc/.zshrc before the line added above.) 97 | .RE 98 | .RS 99 | Install the provided man page \fBz.1\fR somewhere in your \f$MANPATH, like 100 | \fB/usr/local/man/man1\fR. 101 | .RE 102 | .SS 103 | Aging: 104 | The rank of directories maintained by \fBz\fR undergoes aging based on a simple 105 | formula. The rank of each entry is incremented every time it is accessed. When 106 | the sum of ranks is over 9000, all ranks are multiplied by 0.99. Entries with a 107 | rank lower than 1 are forgotten. 108 | .SS 109 | Frecency: 110 | Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted rank 111 | that depends on how often and how recently something occurred. As far as I 112 | know, Mozilla came up with the term. 113 | .P 114 | To \fBz\fR, a directory that has low ranking but has been accessed recently 115 | will quickly have higher rank than a directory accessed frequently a long time 116 | ago. 117 | .P 118 | Frecency is determined at runtime. 119 | .SS 120 | Common: 121 | When multiple directories match all queries, and they all have a common prefix, 122 | \fBz\fR will cd to the shortest matching directory, without regard to priority. 123 | This has been in effect, if undocumented, for quite some time, but should 124 | probably be configurable or reconsidered. 125 | .SS 126 | Tab Completion: 127 | \fBz\fR supports tab completion. After any number of arguments, press TAB to 128 | complete on directories that match each argument. Due to limitations of the 129 | completion implementations, only the last argument will be completed in the 130 | shell. 131 | .P 132 | Internally, \fBz\fR decides you've requested a completion if the last argument 133 | passed is an absolute path to an existing directory. This may cause unexpected 134 | behavior if the last argument to \fBz\fR begins with \fB/\fR. 135 | .SH 136 | ENVIRONMENT 137 | A function \fB_z()\fR is defined. 138 | .P 139 | The contents of the variable \fB$_Z_CMD\fR is aliased to \fB_z 2>&1\fR. If not 140 | set, \fB$_Z_CMD\fR defaults to \fBz\fR. 141 | .P 142 | The environment variable \fB$_Z_DATA\fR can be used to control the datafile 143 | location. If it is not defined, the location defaults to \fB$HOME/.z\fR. 144 | .P 145 | The environment variable \fB$_Z_NO_RESOLVE_SYMLINKS\fR can be set to prevent 146 | resolving of symlinks. If it is not set, symbolic links will be resolved when 147 | added to the datafile. 148 | .P 149 | In bash, \fBz\fR appends a command to the \fBPROMPT_COMMAND\fR environment 150 | variable to maintain its database. In zsh, \fBz\fR appends a function 151 | \fB_z_precmd\fR to the \fBprecmd_functions\fR array. 152 | .P 153 | The environment variable \fB$_Z_NO_PROMPT_COMMAND\fR can be set if you want to 154 | handle \fBPROMPT_COMMAND\fR or \fBprecmd\fR yourself. 155 | .P 156 | The environment variable \fB$_Z_EXCLUDE_DIRS\fR can be set to an array of 157 | directory trees to exclude from tracking. \fB$HOME\fR is always excluded. 158 | Directories must be full paths without trailing slashes. 159 | .P 160 | The environment variable \fB$_Z_OWNER\fR can be set to your username, to 161 | allow usage of \fBz\fR when your sudo environment keeps \fB$HOME\fR set. 162 | .SH 163 | FILES 164 | Data is stored in \fB$HOME/.z\fR. This can be overridden by setting the 165 | \fB$_Z_DATA\fR environment variable. When initialized, \fBz\fR will raise an 166 | error if this path is a directory, and not function correctly. 167 | .P 168 | A man page (\fBz.1\fR) is provided. 169 | .SH 170 | SEE ALSO 171 | regex(7), pushd, popd, autojump, cdargs 172 | .P 173 | Please file bugs at https://github.com/rupa/z/ 174 | -------------------------------------------------------------------------------- /z.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009 rupa deadwyler. Licensed under the WTFPL license, Version 2 2 | 3 | # maintains a jump-list of the directories you actually use 4 | # 5 | # INSTALL: 6 | # * put something like this in your .bashrc/.zshrc: 7 | # . /path/to/z.sh 8 | # * cd around for a while to build up the db 9 | # * PROFIT!! 10 | # * optionally: 11 | # set $_Z_CMD in .bashrc/.zshrc to change the command (default z). 12 | # set $_Z_DATA in .bashrc/.zshrc to change the datafile (default ~/.z). 13 | # set $_Z_MAX_SCORE lower to age entries out faster (default 9000). 14 | # set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution. 15 | # set $_Z_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself. 16 | # set $_Z_EXCLUDE_DIRS to an array of directories to exclude. 17 | # set $_Z_OWNER to your username if you want use z while sudo with $HOME kept 18 | # 19 | # USE: 20 | # * z foo # cd to most frecent dir matching foo 21 | # * z foo bar # cd to most frecent dir matching foo and bar 22 | # * z -r foo # cd to highest ranked dir matching foo 23 | # * z -t foo # cd to most recently accessed dir matching foo 24 | # * z -l foo # list matches instead of cd 25 | # * z -e foo # echo the best match, don't cd 26 | # * z -c foo # restrict matches to subdirs of $PWD 27 | # * z -x # remove the current directory from the datafile 28 | # * z -h # show a brief help message 29 | 30 | [ -d "${_Z_DATA:-$HOME/.z}" ] && { 31 | echo "ERROR: z.sh's datafile (${_Z_DATA:-$HOME/.z}) is a directory." 32 | } 33 | 34 | _z() { 35 | 36 | local datafile="${_Z_DATA:-$HOME/.z}" 37 | 38 | # if symlink, dereference 39 | [ -h "$datafile" ] && datafile=$(readlink "$datafile") 40 | 41 | # bail if we don't own ~/.z and $_Z_OWNER not set 42 | [ -z "$_Z_OWNER" -a -f "$datafile" -a ! -O "$datafile" ] && return 43 | 44 | _z_dirs () { 45 | [ -f "$datafile" ] || return 46 | 47 | local line 48 | while read line; do 49 | # only count directories 50 | [ -d "${line%%\|*}" ] && echo "$line" 51 | done < "$datafile" 52 | return 0 53 | } 54 | 55 | # add entries 56 | if [ "$1" = "--add" ]; then 57 | shift 58 | 59 | # $HOME and / aren't worth matching 60 | [ "$*" = "$HOME" -o "$*" = '/' ] && return 61 | 62 | # don't track excluded directory trees 63 | if [ ${#_Z_EXCLUDE_DIRS[@]} -gt 0 ]; then 64 | local exclude 65 | for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do 66 | case "$*" in "$exclude"*) return;; esac 67 | done 68 | fi 69 | 70 | # maintain the data file 71 | local tempfile="$datafile.$RANDOM" 72 | local score=${_Z_MAX_SCORE:-9000} 73 | _z_dirs | \awk -v path="$*" -v now="$(\date +%s)" -v score=$score -F"|" ' 74 | BEGIN { 75 | rank[path] = 1 76 | time[path] = now 77 | } 78 | $2 >= 1 { 79 | # drop ranks below 1 80 | if( $1 == path ) { 81 | rank[$1] = $2 + 1 82 | time[$1] = now 83 | } else { 84 | rank[$1] = $2 85 | time[$1] = $3 86 | } 87 | count += $2 88 | } 89 | END { 90 | if( count > score ) { 91 | # aging 92 | for( x in rank ) print x "|" 0.99*rank[x] "|" time[x] 93 | } else for( x in rank ) print x "|" rank[x] "|" time[x] 94 | } 95 | ' 2>/dev/null >| "$tempfile" 96 | # do our best to avoid clobbering the datafile in a race condition. 97 | if [ $? -ne 0 -a -f "$datafile" ]; then 98 | \env rm -f "$tempfile" 99 | else 100 | [ "$_Z_OWNER" ] && chown $_Z_OWNER:"$(id -ng $_Z_OWNER)" "$tempfile" 101 | \env mv -f "$tempfile" "$datafile" || \env rm -f "$tempfile" 102 | fi 103 | 104 | # tab completion 105 | elif [ "$1" = "--complete" -a -s "$datafile" ]; then 106 | _z_dirs | \awk -v q="$2" -F"|" ' 107 | BEGIN { 108 | q = substr(q, 3) 109 | if( q == tolower(q) ) imatch = 1 110 | gsub(/ /, ".*", q) 111 | } 112 | { 113 | if( imatch ) { 114 | if( tolower($1) ~ q ) print $1 115 | } else if( $1 ~ q ) print $1 116 | } 117 | ' 2>/dev/null 118 | 119 | else 120 | # list/go 121 | local echo fnd last list opt typ 122 | while [ "$1" ]; do case "$1" in 123 | --) while [ "$1" ]; do shift; fnd="$fnd${fnd:+ }$1";done;; 124 | -*) opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in 125 | c) fnd="^$PWD $fnd";; 126 | e) echo=1;; 127 | h) echo "${_Z_CMD:-z} [-cehlrtx] args" >&2; return;; 128 | l) list=1;; 129 | r) typ="rank";; 130 | t) typ="recent";; 131 | x) \sed -i -e "\:^${PWD}|.*:d" "$datafile";; 132 | esac; opt=${opt:1}; done;; 133 | *) fnd="$fnd${fnd:+ }$1";; 134 | esac; last=$1; [ "$#" -gt 0 ] && shift; done 135 | [ "$fnd" -a "$fnd" != "^$PWD " ] || list=1 136 | 137 | # if we hit enter on a completion just go there 138 | case "$last" in 139 | # completions will always start with / 140 | /*) [ -z "$list" -a -d "$last" ] && builtin cd "$last" && return;; 141 | esac 142 | 143 | # no file yet 144 | [ -f "$datafile" ] || return 145 | 146 | local cd 147 | cd="$( < <( _z_dirs ) \awk -v t="$(\date +%s)" -v list="$list" -v typ="$typ" -v q="$fnd" -F"|" ' 148 | function frecent(rank, time) { 149 | # relate frequency and time 150 | dx = t - time 151 | return int(10000 * rank * (3.75/((0.0001 * dx + 1) + 0.25))) 152 | } 153 | function output(matches, best_match, common) { 154 | # list or return the desired directory 155 | if( list ) { 156 | if( common ) { 157 | printf "%-10s %s\n", "common:", common > "/dev/stderr" 158 | } 159 | cmd = "sort -n >&2" 160 | for( x in matches ) { 161 | if( matches[x] ) { 162 | printf "%-10s %s\n", matches[x], x | cmd 163 | } 164 | } 165 | } else { 166 | if( common && !typ ) best_match = common 167 | print best_match 168 | } 169 | } 170 | function common(matches) { 171 | # find the common root of a list of matches, if it exists 172 | for( x in matches ) { 173 | if( matches[x] && (!short || length(x) < length(short)) ) { 174 | short = x 175 | } 176 | } 177 | if( short == "/" ) return 178 | for( x in matches ) if( matches[x] && index(x, short) != 1 ) { 179 | return 180 | } 181 | return short 182 | } 183 | BEGIN { 184 | gsub(" ", ".*", q) 185 | hi_rank = ihi_rank = -9999999999 186 | } 187 | { 188 | if( typ == "rank" ) { 189 | rank = $2 190 | } else if( typ == "recent" ) { 191 | rank = $3 - t 192 | } else rank = frecent($2, $3) 193 | if( $1 ~ q ) { 194 | matches[$1] = rank 195 | } else if( tolower($1) ~ tolower(q) ) imatches[$1] = rank 196 | if( matches[$1] && matches[$1] > hi_rank ) { 197 | best_match = $1 198 | hi_rank = matches[$1] 199 | } else if( imatches[$1] && imatches[$1] > ihi_rank ) { 200 | ibest_match = $1 201 | ihi_rank = imatches[$1] 202 | } 203 | } 204 | END { 205 | # prefer case sensitive 206 | if( best_match ) { 207 | output(matches, best_match, common(matches)) 208 | exit 209 | } else if( ibest_match ) { 210 | output(imatches, ibest_match, common(imatches)) 211 | exit 212 | } 213 | exit(1) 214 | } 215 | ')" 216 | 217 | if [ "$?" -eq 0 ]; then 218 | if [ "$cd" ]; then 219 | if [ "$echo" ]; then echo "$cd"; else builtin cd "$cd"; fi 220 | fi 221 | else 222 | return $? 223 | fi 224 | fi 225 | } 226 | 227 | alias ${_Z_CMD:-z}='_z 2>&1' 228 | 229 | [ "$_Z_NO_RESOLVE_SYMLINKS" ] || _Z_RESOLVE_SYMLINKS="-P" 230 | 231 | if type compctl >/dev/null 2>&1; then 232 | # zsh 233 | [ "$_Z_NO_PROMPT_COMMAND" ] || { 234 | # populate directory list, avoid clobbering any other precmds. 235 | if [ "$_Z_NO_RESOLVE_SYMLINKS" ]; then 236 | _z_precmd() { 237 | (_z --add "${PWD:a}" &) 238 | : $RANDOM 239 | } 240 | else 241 | _z_precmd() { 242 | (_z --add "${PWD:A}" &) 243 | : $RANDOM 244 | } 245 | fi 246 | [[ -n "${precmd_functions[(r)_z_precmd]}" ]] || { 247 | precmd_functions[$(($#precmd_functions+1))]=_z_precmd 248 | } 249 | } 250 | _z_zsh_tab_completion() { 251 | # tab completion 252 | local compl 253 | read -l compl 254 | reply=(${(f)"$(_z --complete "$compl")"}) 255 | } 256 | compctl -U -K _z_zsh_tab_completion _z 257 | elif type complete >/dev/null 2>&1; then 258 | # bash 259 | # tab completion 260 | complete -o filenames -C '_z --complete "$COMP_LINE"' ${_Z_CMD:-z} 261 | [ "$_Z_NO_PROMPT_COMMAND" ] || { 262 | # populate directory list. avoid clobbering other PROMPT_COMMANDs. 263 | grep "_z --add" <<< "$PROMPT_COMMAND" >/dev/null || { 264 | PROMPT_COMMAND="$PROMPT_COMMAND"$'\n''(_z --add "$(command pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null)" 2>/dev/null &);' 265 | } 266 | } 267 | fi 268 | --------------------------------------------------------------------------------