19 KiB
Zsh-z
Zsh-z is a command line tool that allows you to jump quickly to directories that you have visited frequently in the past, or recently -- but most often a combination of the two (a concept known as "frecency"). It works by keeping track of when you go to directories and how much time you spend in them. It is then in the position to guess where you want to go when you type a partial string, e.g., z src
might take you to ~/src/zsh
. z zsh
might also get you there, and z c/z
might prove to be even more specific -- it all depends on your habits and how much time you have been using Zsh-z to build up a database. After using Zsh-z for a little while, you will get to where you want to be by typing considerably less than you would need if you were using cd
.
Zsh-z is a native Zsh port of rupa/z, a tool written for bash
and Zsh that uses embedded awk
scripts to do the heavy lifting. It was quite possibly my most used command line tool for a couple of years. I decided to translate it, awk
parts and all, into pure Zsh script, to see if by eliminating calls to external tools (awk
, sort
, date
, sed
, mv
, rm
, and chown
) and reducing forking through subshells I could make it faster. The performance increase is impressive, particularly on systems where forking is slow, such as Cygwin, MSYS2, and WSL. I have found that, in those environments, switching directories using Zsh-z can be over 100% faster than it is using rupa/z
.
There is a noteworthy stability increase as well. Race conditions have always been a problem with rupa/z
, and users of that utility will occasionally lose their .z
databases. By having Zsh-z only use Zsh (rupa/z
uses a hybrid shell code that works on bash
as well), I have been able to implement a zsh/system
-based file-locking mechanism similar to the one @mafredri once proposed for rupa/z
. It is now nearly impossible to crash the database, even through extreme testing.
There are other, smaller improvements which I try to document in Improvements and Fixes. These include the new default behavior of sorting your tab completions by frecency rather than just letting Zsh sort the raw results alphabetically (a behavior which can be restored if you like it -- see below).
Zsh-z is a drop-in replacement for rupa/z
and will, by default, use the same database (~/.z
), so you can go on using rupa/z
when you launch bash
.
Table of Contents
- News
- Installation
- Command Line Options
- Settings
- Case Sensitivity
ZSHZ_UNCOMMON
- Making
--add
work for you - Other Improvements and Fixes
- Migrating from Other Tools
COMPLETE_ALIASES
- Known Bugs
News
Here are the latest features and updates.
- August 24, 2023
- Zsh-z will now run when
setopt NO_UNSET
has been enabled (props @ntninja).
- Zsh-z will now run when
- August 23, 2023
- Better logic for loading
zsh/files
(props @z0rc)
- Better logic for loading
- August 2, 2023
- Zsh-z still uses the
zsh/files
module when possible, but will fall back on the standardchown
,mv
, andrm
commands in its absence.
- Zsh-z still uses the
- April 27, 2023
- Zsh-z now allows the user to specify the directory-changing command using the
ZSHZ_CD
environment variable (default:builtin cd
; props @basnijholt).
- Zsh-z now allows the user to specify the directory-changing command using the
- January 27, 2023
- If the datafile directory specified by
ZSHZ_DATA
or_Z_DATA
does not already exist, create it (props @mattmc3).
- If the datafile directory specified by
- June 29, 2022
- Zsh-z is less likely to leave temporary files sitting around (props @mafredri).
- June 27, 2022
- A bug was fixed which was preventing paths with spaces in them from being updated (#61).
- If writing to the temporary database file fails, the database will not be clobbered (props @mafredri).
- December 19, 2021
- ZSH-z will now display tildes for
HOME
during completion whenZSHZ_TILDE=1
has been set.
- ZSH-z will now display tildes for
- November 11, 2021
- A bug was fixed which was preventing ranks from being incremented.
--add
has been made to work with relative paths and has been documented for the user.
- October 14, 2021
- Completions were being sorted alphabetically, rather than by rank; this error has been fixed.
- September 25, 2021
- Orthographical change: "Zsh," not "ZSH."
- September 23, 2021
z -xR
will now remove a directory and its subdirectories from the database.z -x
andz -xR
can now take an argument; without one,PWD
is assumed.
- September 7, 2021
- Fixed the unload function so that it removes the
$ZSHZ_CMD
alias (default:z
).
- Fixed the unload function so that it removes the
- August 27, 2021
- Using
print -v ... -f
instead ofprint -v
to work around longstanding bug in Zsh involvingprint -v
and multibyte strings.
- Using
- August 13, 2021
- Fixed the explanation string printed during completion so that it may be formatted with
zstyle
. - Zsh-z now declares
ZSHZ_EXCLUDE_DIRS
as an array with unique elements so that you do not have to.
- Fixed the explanation string printed during completion so that it may be formatted with
- July 29, 2021
- Temporarily disabling use of
print -v
, which seems to be mangling CJK multibyte strings.
- Temporarily disabling use of
- July 27, 2021
- Internal escaping of path names now works with older versions of ZSH.
- Zsh-z now detects and discards any incomplete or incorrectly formatted database entries.
- July 10, 2021
- Setting
ZSHZ_TRAILING_SLASH=1
makes it so that a search pattern ending in/
can match the end of a path; e.g.z foo/
can match/path/to/foo
.
- Setting
- June 25, 2021
- Setting
ZSHZ_TILDE=1
displays theHOME
directory as~
.
- Setting
- May 7, 2021
- Setting
ZSHZ_ECHO=1
will cause Zsh-z to display the new path when you change directories. - Better escaping of path names to deal paths containing the characters
\`()[]
.
- Setting
- February 15, 2021
- Ranks are displayed the way
rupa/z
now displays them, i.e. as large integers. This should help Zsh-z to integrate with other tools.
- Ranks are displayed the way
- January 31, 2021
- Zsh-z is now efficient enough that, on MSYS2 and Cygwin, it is faster to run it in the foreground than it is to fork a subshell for it.
_zshz_precmd
simply returns ifPWD
isHOME
or inZSH_EXCLUDE_DIRS
, rather than waiting forzshz
to do that.
- January 17, 2021
- Made sure that the
PUSHD_IGNORE_DUPS
option is respected.
- Made sure that the
- January 14, 2021
- The
z -h
help text now breaks at spaces. z -l
was not working for Zsh version < 5.
- The
- January 11, 2021
- Major refactoring of the code.
z -lr
andz -lt
work as expected.EXTENDED_GLOB
has been disabled within the plugin to accommodate old-fashioned Windows directories with names such asProgra~1
.- Removed
zshelldoc
documentation.
- January 6, 2021
- I have corrected the frecency routine so that it matches
rupa/z
's math, but for the present, Zsh-z will continue to display ranks as 1/10000th of what they are inrupa/z
-- they had to multiply theirs by 10000 to work aroundbash
's inadequacies at dealing with decimal fractions.
- I have corrected the frecency routine so that it matches
- January 5, 2021
- If you try
z foo
, andfoo
is not in the database but${PWD}/foo
is a valid directory, Zsh-z willcd
to it.
- If you try
- December 22, 2020
ZSHZ_CASE
: when set toignore
, pattern matching is case-insensitive; when set tosmart
, patterns are matched case-insensitively when they are all lowercase and case-sensitively when they have uppercase characters in them (a behavior very much like Vim'ssmartcase
setting).ZSHZ_KEEP_DIRS
is an array of directory names that should not be removed from the database, even if they are not currently available (useful when a drive is not always mounted).- Symlinked datafiles were having their symlinks overwritten; this bug has been fixed.
Installation
General observations
This plugin can be installed simply by putting the various files in a directory together and by sourcing zsh-z.plugin.zsh
in your .zshrc
:
source /path/to/zsh-z.plugin.zsh
For tab completion to work, _zshz
must be in the same directory as zsh-z.plugin.zsh
, and you will want to have loaded compinit
. The frameworks handle this themselves. If you are not using a framework, put
autoload -U compinit; compinit
in your .zshrc somewhere below where you source zsh-z.plugin.zsh
.
If you add
zstyle ':completion:*' menu select
to your .zshrc
, your completion menus will look very nice. This zstyle
invocation should work with any of the frameworks below as well.
For antigen users
Add the line
antigen bundle agkozak/zsh-z
to your .zshrc
, somewhere above the line that says antigen apply
.
For Oh My Zsh users
Zsh-z is now included as part of Oh My Zsh! As long as you are using an up-to-date installation of Oh My Zsh, you can activate Zsh-z simply by adding z
to your plugins
array in your .zshrc
, e.g.,
plugins=( git z )
It is as simple as that.
If, however, you prefer always to use the latest version of Zsh-z from the agkozak/zsh-z
repo, you may install it thus:
git clone https://github.com/agkozak/zsh-z ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-z
and activate it by adding zsh-z
to the line of your .zshrc
that specifies plugins=()
, e.g., plugins=( git zsh-z )
.
For prezto users
Execute the following command:
git clone https://github.com/agkozak/zsh-z.git ~/.zprezto-contrib/zsh-z
Then edit your ~/.zpreztorc
file. Make sure the line that says
zstyle ':prezto:load' pmodule-dirs $HOME/.zprezto-contrib
is uncommented. Then find the section that specifies which modules are to be loaded; it should look something like this:
zstyle ':prezto:load' pmodule \
'environment' \
'terminal' \
'editor' \
'history' \
'directory' \
'spectrum' \
'utility' \
'completion' \
'prompt'
Add a backslash to the end of the last line add 'zsh-z'
to the list, e.g.,
zstyle ':prezto:load' pmodule \
'environment' \
'terminal' \
'editor' \
'history' \
'directory' \
'spectrum' \
'utility' \
'completion' \
'prompt' \
'zsh-z'
Then relaunch zsh
.
For zcomet users
Simply add
zcomet load agkozak/zsh-z
to your .zshrc
(below where you source zcomet.zsh
and above where you run zcomet compinit
).
For zgen users
Add the line
zgen load agkozak/zsh-z
somewhere above the line that says zgen save
. Then run
zgen reset
zsh
to refresh your init script.
For Zim
Add the following line to your .zimrc
:
zmodule https://github.com/agkozak/zsh-z
Then run
zimfw install
and restart your shell.
For Zinit users
Add the line
zinit load agkozak/zsh-z
to your .zshrc
.
zsh-z
supports zinit
's unload
feature; just run zinit unload agkozak/zshz
to restore the shell to its state before zsh-z
was loaded.
For Znap users
Add the line
znap source agkozak/zsh-z
somewhere below the line where you source
Znap itself.
For zplug users
Add the line
zplug "agkozak/zsh-z"
somewhere above the line that says zplug load
. Then run
zplug install
zplug load
to install zsh-z
.
Command Line Options
--add
Add a directory to the database-c
Only match subdirectories of the current directory-e
Echo the best match without going to it-h
Display help-l
List all matches without going to them-r
Match by rank (i.e. how much time you spend in directories)-t
Time -- match by how recently you have been to directories-x
Remove a directory (by default, the current directory) from the database-xR
Remove a directory (by default, the current directory) and its subdirectories from the database
Settings
Zsh-z has environment variables (they all begin with ZSHZ_
) that change its behavior if you set them; you can also keep your old ones if you have been using rupa/z
(they begin with _Z_
).
ZSHZ_CMD
changes the command name (default:z
)ZSHZ_CD
specifies the default directory-changing command (default:builtin cd
)ZSHZ_COMPLETION
can be'frecent'
(default) or'legacy'
, depending on whether you want your completion results sorted according to frecency or simply sorted alphabeticallyZSHZ_DATA
changes the database file (default:~/.z
)ZSHZ_ECHO
displays the new path name when changing directories (default:0
)ZSHZ_EXCLUDE_DIRS
is an array of directories to keep out of the database (default: empty)ZSHZ_KEEP_DIRS
is an array of directories that should not be removed from the database, even if they are not currently available (useful when a drive is not always mounted) (default: empty)ZSHZ_MAX_SCORE
is the maximum combined score the database entries can have before they begin to age and potentially drop out of the database (default: 9000)ZSHZ_NO_RESOLVE_SYMLINKS
prevents symlink resolution (default:0
)ZSHZ_OWNER
allows usage when insudo -s
mode (default: empty)ZSHZ_TILDE
displays the name of theHOME
directory as a~
(default:0
)ZSHZ_TRAILING_SLASH
makes it so that a search pattern ending in/
can match the final element in a path; e.g.,z foo/
can match/path/to/foo
(default:0
)ZSHZ_UNCOMMON
changes the logic used to calculate the directory jumped to; see below (default:0
)
Case sensitivity
The default behavior of Zsh-z is to try to find a case-sensitive match. If there is none, then Zsh-z tries to find a case-insensitive match.
Some users prefer simple case-insensitivity; this behavior can be enabled by setting
ZSHZ_CASE=ignore
If you like Vim's smartcase
setting, where lowercase patterns are case-insensitive while patterns with any uppercase characters are treated case-sensitively, try setting
ZSHZ_CASE=smart
ZSHZ_UNCOMMON
A common complaint about the default behavior of rupa/z
and Zsh-z involves "common prefixes." If you type z code
and the best matches, in increasing order, are
/home/me/code/foo
/home/me/code/bar
/home/me/code/bat
Zsh-z will see that all possible matches share a common prefix and will send you to that directory -- /home/me/code
-- which is often a desirable result. But if the possible matches are
/home/me/.vscode/foo
/home/me/code/foo
/home/me/code/bar
/home/me/code/bat
then there is no common prefix. In this case, z code
will simply send you to the highest-ranking match, /home/me/code/bat
.
You may enable an alternate, experimental behavior by setting ZSHZ_UNCOMMON=1
. If you do that, Zsh-z will not jump to a common prefix, even if one exists. Instead, it chooses the highest-ranking match -- but it drops any subdirectories that do not include the search term. So if you type z bat
and /home/me/code/bat
is the best match, that is exactly where you will end up. If, however, you had typed z code
and the best match was also /home/me/code/bat
, you would have ended up in /home/me/code
(because code
was what you had searched for). This feature is still in development, and feedback is welcome.
Making --add
Work for You
Zsh-z internally uses the --add
option to add paths to its database. @zachriggle pointed out to me that users might want to use --add
themselves, so I have altered it a little to make it more user-friendly.
A good example might involve a directory tree that has Git repositories within it. The working directories could be added to the Zsh-z database as a batch with
for i in $(find $PWD -maxdepth 3 -name .git -type d); do
z --add ${i:h}
done
(As a Zsh user, I tend to use **
instead of find
, but it is good to see how deep your directory trees go before doing that.)
Other Improvements and Fixes
z -x
works, with the help ofchpwd_functions
.- Zsh-z works on Solaris.
- Zsh-z uses the "new"
zshcompsys
completion system instead of the oldcompctl
one. - There is no error message when the database file has not yet been created.
- There is support for special characters (e.g.,
[
) in directory names. - If
z -l
only returns one match, a common root is not printed. - Exit status codes increasingly make sense.
- Completions work with options
-c
,-r
, and-t
. - If
~/foo
and~/foob
are matches,~/foo
is not the common root. Only a common parent directory can be a common root. z -x
and the new, recursivez -xR
can take an argument so that you can remove directories other thanPWD
from the database.
Migrating from Other Tools
Zsh-z's database format is identical to that of rupa/z
. You may switch freely between the two tools (I still use rupa/z
for bash
). fasd
also uses that database format, but it stores it by default in ~/.fasd
, so you will have to cp ~/.fasd ~/.z
if you want to use your old directory history.
If you are coming to Zsh-z (or even to the original rupa/z
, for that matter) from autojump
, try using my jumpstart-z
tool to convert your old database to the Zsh-z format, or simply run
awk -F "\t" '{printf("%s|%0.f|%s\n", $2, $1, '"$(date +%s)"')}' < /path/to/autojump.txt > ~/.z
COMPLETE_ALIASES
z
, or any alternative you set up using $ZSH_CMD
or $_Z_CMD
, is an alias. setopt COMPLETE_ALIASES
divorces the tab completion for aliases from the underlying commands they invoke, so if you enable COMPLETE_ALIASES
, tab completion for Zsh-z will be broken. You can get it working again, however, by adding under
setopt COMPLETE_ALIASES
the line
compdef _zshz ${ZSHZ_CMD:-${_Z_CMD:-z}}
That will re-bind z
or the command of your choice to the underlying Zsh-z function.
Known Bugs
It is possible to run a completion on a string with spaces in it, e.g., z us bi<TAB>
might take you to /usr/local/bin
. This works, but as things stand, after the completion the command line reads
z us /usr/local/bin.
You get where you want to go, but the detritus on the command line is annoying. This is also a problem in rupa/z
, but I am keen on eventually eliminating this glitch. Advice is welcome.