Update 05.04.2026

This commit is contained in:
eof
2026-04-05 17:51:45 +05:00
parent fcc904df1e
commit b363a93ea5
680 changed files with 16892 additions and 16586 deletions

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2022 Alexandros Kozak
Copyright (c) 2018-2025 Alexandros Kozak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,14 +1,20 @@
# 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"](https://en.wikipedia.org/wiki/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`.
[![MIT License](img/mit_license.svg)](https://opensource.org/licenses/MIT)
![Zsh version 4.3.11 and higher](img/zsh_4.3.11_plus.svg)
[![GitHub stars](https://img.shields.io/github/stars/agkozak/zsh-z.svg)](https://github.com/agkozak/zsh-z/stargazers)
Zsh-z is a native Zsh port of [rupa/z](https://github.com/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`.
![Zsh-z demo](img/demo.gif)
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`](https://github.com/rupa/z/pull/199). It is now nearly impossible to crash the database, even through extreme testing.
Zsh-z is a command-line tool that allows you to jump quickly to directories that you have visited frequently or recently -- but most often a combination of the two (a concept known as ["frecency"](https://en.wikipedia.org/wiki/Frecency)). It works by keeping track of when you go to directories and how much time you spend in them. Based on this data, it predicts where you want to go when you type a partial string. For example, `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 long 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 to if you were using `cd`.
There are other, smaller improvements which I try to document in [Improvements and Fixes](#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](#settings)).
Zsh-z is a native Zsh port of [`rupa/z`](https://github.com/rupa/z), a tool written for `bash` and Zsh that uses embedded `awk` scripts to do the heavy lifting. `rupa/z` was 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`.
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`.
There is also a significant stability improvement. Race conditions have always been a problem with `rupa/z`, and users of that utility occasionally lose their `~/.z` databases. By having Zsh-z only use Zsh (`rupa/z` uses a hybrid shell code standard 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`](https://github.com/rupa/z/pull/199). It is now nearly impossible to crash the database.
There are other, smaller improvements which I document below in [Improvements and Fixes](#improvements-and-fixes). For instance, tab completions are now sorted by frecency by default rather than alphabetically (the latter behavior can be restored if you like it -- [see below](#settings)).
Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same database (`~/.z`, or whatever database file you specify), so you can go on using `rupa/z` when you launch `bash`.
## Table of Contents
- [News](#news)
@@ -28,6 +34,16 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
<details>
<summary>Here are the latest features and updates.</summary>
- August 24, 2023
+ Zsh-z will now run when `setopt NO_UNSET` has been enabled (props @ntninja).
- August 23, 2023
+ Better logic for loading `zsh/files` (props @z0rc).
- August 2, 2023
+ Zsh-z still uses the `zsh/files` module when possible but will fall back on the standard `chown`, `mv`, and `rm` commands in its absence.
- 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).
- January 27, 2023
+ If the database file directory specified by `ZSHZ_DATA` or `_Z_DATA` does not already exist, create it (props @mattmc3).
- June 29, 2022
+ Zsh-z is less likely to leave temporary files sitting around (props @mafredri).
- June 27, 2022
@@ -53,10 +69,10 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
+ 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.
- July 29, 2021
+ Temporarily disabling use of `print -v`, which seems to be mangling CJK multibyte strings.
+ Temporarily disabling the use of `print -v`, which was mangling CJK multibyte strings.
- 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 formattted database entries.
+ 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`.
- June 25, 2021
@@ -77,7 +93,7 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
- January 11, 2021
+ Major refactoring of the code.
+ `z -lr` and `z -lt` work as expected.
+ `EXTENDED_GLOB` has been disabled within the plugin to accomodate old-fashioned Windows directories with names such as `Progra~1`.
+ `EXTENDED_GLOB` has been disabled within the plugin to accommodate old-fashioned Windows directories with names such as `Progra~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 in `rupa/z` -- [they had to multiply theirs by 10000](https://github.com/rupa/z/commit/f1f113d9bae9effaef6b1e15853b5eeb445e0712) to work around `bash`'s inadequacies at dealing with decimal fractions.
@@ -86,7 +102,7 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
- December 22, 2020
+ `ZSHZ_CASE`: when set to `ignore`, pattern matching is case-insensitive; when set to `smart`, 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's `smartcase` 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.
+ Symlinked database files were having their symlinks overwritten; this bug has been fixed.
</details>
@@ -94,15 +110,15 @@ Zsh-z is a drop-in replacement for `rupa/z` and will, by default, use the same d
### General observations
This script can be installed simply by downloading it and sourcing it from your `.zshrc`:
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, you will want to have loaded `compinit`. The frameworks handle this themselves. If you are not using a framework, put
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
autoload -U compinit; compinit
in your .zshrc somewhere below where you source `zsh-z.plugin.zsh`.
in your `.zshrc` somewhere below where you source `zsh-z.plugin.zsh`.
If you add
@@ -118,13 +134,19 @@ Add the line
to your `.zshrc`, somewhere above the line that says `antigen apply`.
### For [oh-my-zsh](http://ohmyz.sh/) users
### For [Oh My Zsh](http://ohmyz.sh/) users
Execute the following command:
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 add `zsh-z` to the line of your `.zshrc` that specifies `plugins=()`, e.g., `plugins=( git 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](https://github.com/sorin-ionescu/prezto) users
@@ -206,7 +228,7 @@ Add the line
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.
Zsh-z supports `zinit`'s `unload` feature; just run `zinit unload agkozak/zsh-z` to restore the shell to its state before Zsh-z was loaded.
### For [Znap](https://github.com/marlonrichert/zsh-snap) users
@@ -227,7 +249,7 @@ somewhere above the line that says `zplug load`. Then run
zplug install
zplug load
to install `zsh-z`.
to install Zsh-z.
## Command Line Options
@@ -241,11 +263,12 @@ to install `zsh-z`.
- `-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
## 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_`).
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` (whose environment variables 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 alphabetically
* `ZSHZ_DATA` changes the database file (default: `~/.z`)
* `ZSHZ_ECHO` displays the new path name when changing directories (default: `0`)
@@ -301,19 +324,18 @@ A good example might involve a directory tree that has Git repositories within i
(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 of `chpwd_functions`.
* Zsh-z works on Solaris.
* Zsh-z is compatible with Solaris.
* Zsh-z uses the "new" `zshcompsys` completion system instead of the old `compctl` 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, recursive `z -xR` can take an argument so that you can remove directories other than `PWD` from the database.
* No error message is displayed when the database file has not yet been created.
* Special characters (e.g., `[`) in directory names are now supported.
* If `z -l` returns only one match, a common root is not printed.
* Exit status codes are more logical.
* Completions now work with options `-c`, `-r`, and `-t`.
* If `~/foo` and `~/foob` are matches, `~/foo` is no longer considered the common root. Only a common parent directory can be a common root.
* `z -x` and the new, recursive `z -xR` can now accept an argument so that you can remove directories other than `PWD` from the database.
## Migrating from Other Tools
@@ -335,7 +357,7 @@ the line
That will re-bind `z` or the command of your choice to the underlying Zsh-z function.
## Known Bugs
## Known Bug
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.

View File

@@ -1,4 +0,0 @@
readme:
@groff -man -Tascii z.1 | col -bx
.PHONY: readme

View File

@@ -1,148 +0,0 @@
Z(1) User Commands Z(1)
NAME
z - jump around
SYNOPSIS
z [-chlrtx] [regex1 regex2 ... regexn]
AVAILABILITY
bash, zsh
DESCRIPTION
Tracks your most used directories, based on 'frecency'.
After a short learning phase, z will take you to the most 'frecent'
directory that matches ALL of the regexes given on the command line, in
order.
For example, z foo bar would match /foo/bar but not /bar/foo.
OPTIONS
-c restrict matches to subdirectories of the current directory
-e echo the best match, don't cd
-h show a brief help message
-l list only
-r match by rank only
-t match by recent access only
-x remove the current directory from the datafile
EXAMPLES
z foo cd to most frecent dir matching foo
z foo bar cd to most frecent dir matching foo, then 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)
NOTES
Installation:
Put something like this in your $HOME/.bashrc or $HOME/.zshrc:
. /path/to/z.sh
cd around for a while to build up the db.
PROFIT!!
Optionally:
Set $_Z_CMD to change the command name (default z).
Set $_Z_DATA to change the datafile (default $HOME/.z).
Set $_Z_MAX_SCORE lower to age entries out faster (default
9000).
Set $_Z_NO_RESOLVE_SYMLINKS to prevent symlink resolution.
Set $_Z_NO_PROMPT_COMMAND to handle PROMPT_COMMAND/precmd your-
self.
Set $_Z_EXCLUDE_DIRS to an array of directory trees to exclude.
Set $_Z_OWNER to allow usage when in 'sudo -s' mode.
(These settings should go in .bashrc/.zshrc before the line
added above.)
Install the provided man page z.1 somewhere in your MANPATH,
like /usr/local/man/man1.
Aging:
The rank of directories maintained by z undergoes aging based on a sim-
ple formula. The rank of each entry is incremented every time it is
accessed. When the sum of ranks is over 9000, all ranks are multiplied
by 0.99. Entries with a rank lower than 1 are forgotten.
Frecency:
Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted
rank that depends on how often and how recently something occurred. As
far as I know, Mozilla came up with the term.
To z, a directory that has low ranking but has been accessed recently
will quickly have higher rank than a directory accessed frequently a
long time ago.
Frecency is determined at runtime.
Common:
When multiple directories match all queries, and they all have a common
prefix, z will cd to the shortest matching directory, without regard to
priority. This has been in effect, if undocumented, for quite some
time, but should probably be configurable or reconsidered.
Tab Completion:
z supports tab completion. After any number of arguments, press TAB to
complete on directories that match each argument. Due to limitations of
the completion implementations, only the last argument will be com-
pleted in the shell.
Internally, z decides you've requested a completion if the last argu-
ment passed is an absolute path to an existing directory. This may
cause unexpected behavior if the last argument to z begins with /.
ENVIRONMENT
A function _z() is defined.
The contents of the variable $_Z_CMD is aliased to _z 2>&1. If not set,
$_Z_CMD defaults to z.
The environment variable $_Z_DATA can be used to control the datafile
location. If it is not defined, the location defaults to $HOME/.z.
The environment variable $_Z_NO_RESOLVE_SYMLINKS can be set to prevent
resolving of symlinks. If it is not set, symbolic links will be
resolved when added to the datafile.
In bash, z appends a command to the PROMPT_COMMAND environment variable
to maintain its database. In zsh, z appends a function _z_precmd to the
precmd_functions array.
The environment variable $_Z_NO_PROMPT_COMMAND can be set if you want
to handle PROMPT_COMMAND or precmd yourself.
The environment variable $_Z_EXCLUDE_DIRS can be set to an array of
directory trees to exclude from tracking. $HOME is always excluded.
Directories must be full paths without trailing slashes.
The environment variable $_Z_OWNER can be set to your username, to
allow usage of z when your sudo environment keeps $HOME set.
FILES
Data is stored in $HOME/.z. This can be overridden by setting the
$_Z_DATA environment variable. When initialized, z will raise an error
if this path is a directory, and not function correctly.
A man page (z.1) is provided.
SEE ALSO
regex(7), pushd, popd, autojump, cdargs
Please file bugs at https://github.com/rupa/z/
z January 2013 Z(1)

View File

@@ -5,7 +5,7 @@
#
# https://github.com/agkozak/zsh-z
#
# Copyright (c) 2018-2022 Alexandros Kozak
# Copyright (c) 2018-2023 Alexandros Kozak
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal

BIN
zsh/plugins/z/img/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="78" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="78" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h47v20H0z"/><path fill="#97CA00" d="M47 0h31v20H47z"/><path fill="url(#b)" d="M0 0h78v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">license</text><text x="245" y="140" transform="scale(.1)" textLength="370">license</text><text x="615" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="210">MIT</text><text x="615" y="140" transform="scale(.1)" textLength="210">MIT</text></g> </svg>

After

Width:  |  Height:  |  Size: 949 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="84" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="84" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h29v20H0z"/><path fill="#e05d44" d="M29 0h55v20H29z"/><path fill="url(#b)" d="M0 0h84v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="155" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="190">zsh</text><text x="155" y="140" transform="scale(.1)" textLength="190">zsh</text><text x="555" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="450">4.3.11+</text><text x="555" y="140" transform="scale(.1)" textLength="450">4.3.11+</text></g> </svg>

After

Width:  |  Height:  |  Size: 949 B

View File

@@ -1,173 +0,0 @@
.TH "Z" "1" "January 2013" "z" "User Commands"
.SH
NAME
z \- jump around
.SH
SYNOPSIS
z [\-chlrtx] [regex1 regex2 ... regexn]
.SH
AVAILABILITY
bash, zsh
.SH
DESCRIPTION
Tracks your most used directories, based on 'frecency'.
.P
After a short learning phase, \fBz\fR will take you to the most 'frecent'
directory that matches ALL of the regexes given on the command line, in order.
For example, \fBz foo bar\fR would match \fB/foo/bar\fR but not \fB/bar/foo\fR.
.SH
OPTIONS
.TP
\fB\-c\fR
restrict matches to subdirectories of the current directory
.TP
\fB\-e\fR
echo the best match, don't cd
.TP
\fB\-h\fR
show a brief help message
.TP
\fB\-l\fR
list only
.TP
\fB\-r\fR
match by rank only
.TP
\fB\-t\fR
match by recent access only
.TP
\fB\-x\fR
remove the current directory from the datafile
.SH EXAMPLES
.TP 14
\fBz foo\fR
cd to most frecent dir matching foo
.TP 14
\fBz foo bar\fR
cd to most frecent dir matching foo, then bar
.TP 14
\fBz -r foo\fR
cd to highest ranked dir matching foo
.TP 14
\fBz -t foo\fR
cd to most recently accessed dir matching foo
.TP 14
\fBz -l foo\fR
list all dirs matching foo (by frecency)
.SH
NOTES
.SS
Installation:
.P
Put something like this in your \fB$HOME/.bashrc\fR or \fB$HOME/.zshrc\fR:
.RS
.P
\fB. /path/to/z.sh\fR
.RE
.P
\fBcd\fR around for a while to build up the db.
.P
PROFIT!!
.P
Optionally:
.RS
Set \fB$_Z_CMD\fR to change the command name (default \fBz\fR).
.RE
.RS
Set \fB$_Z_DATA\fR to change the datafile (default \fB$HOME/.z\fR).
.RE
.RS
Set \fB$_Z_MAX_SCORE\fR lower to age entries out faster (default \fB9000\fR).
.RE
.RS
Set \fB$_Z_NO_RESOLVE_SYMLINKS\fR to prevent symlink resolution.
.RE
.RS
Set \fB$_Z_NO_PROMPT_COMMAND\fR to handle \fBPROMPT_COMMAND/precmd\fR yourself.
.RE
.RS
Set \fB$_Z_EXCLUDE_DIRS\fR to an array of directory trees to exclude.
.RE
.RS
Set \fB$_Z_OWNER\fR to allow usage when in 'sudo -s' mode.
.RE
.RS
(These settings should go in .bashrc/.zshrc before the line added above.)
.RE
.RS
Install the provided man page \fBz.1\fR somewhere in your \f$MANPATH, like
\fB/usr/local/man/man1\fR.
.RE
.SS
Aging:
The rank of directories maintained by \fBz\fR undergoes aging based on a simple
formula. The rank of each entry is incremented every time it is accessed. When
the sum of ranks is over 9000, all ranks are multiplied by 0.99. Entries with a
rank lower than 1 are forgotten.
.SS
Frecency:
Frecency is a portmanteau of 'recent' and 'frequency'. It is a weighted rank
that depends on how often and how recently something occurred. As far as I
know, Mozilla came up with the term.
.P
To \fBz\fR, a directory that has low ranking but has been accessed recently
will quickly have higher rank than a directory accessed frequently a long time
ago.
.P
Frecency is determined at runtime.
.SS
Common:
When multiple directories match all queries, and they all have a common prefix,
\fBz\fR will cd to the shortest matching directory, without regard to priority.
This has been in effect, if undocumented, for quite some time, but should
probably be configurable or reconsidered.
.SS
Tab Completion:
\fBz\fR supports tab completion. After any number of arguments, press TAB to
complete on directories that match each argument. Due to limitations of the
completion implementations, only the last argument will be completed in the
shell.
.P
Internally, \fBz\fR decides you've requested a completion if the last argument
passed is an absolute path to an existing directory. This may cause unexpected
behavior if the last argument to \fBz\fR begins with \fB/\fR.
.SH
ENVIRONMENT
A function \fB_z()\fR is defined.
.P
The contents of the variable \fB$_Z_CMD\fR is aliased to \fB_z 2>&1\fR. If not
set, \fB$_Z_CMD\fR defaults to \fBz\fR.
.P
The environment variable \fB$_Z_DATA\fR can be used to control the datafile
location. If it is not defined, the location defaults to \fB$HOME/.z\fR.
.P
The environment variable \fB$_Z_NO_RESOLVE_SYMLINKS\fR can be set to prevent
resolving of symlinks. If it is not set, symbolic links will be resolved when
added to the datafile.
.P
In bash, \fBz\fR appends a command to the \fBPROMPT_COMMAND\fR environment
variable to maintain its database. In zsh, \fBz\fR appends a function
\fB_z_precmd\fR to the \fBprecmd_functions\fR array.
.P
The environment variable \fB$_Z_NO_PROMPT_COMMAND\fR can be set if you want to
handle \fBPROMPT_COMMAND\fR or \fBprecmd\fR yourself.
.P
The environment variable \fB$_Z_EXCLUDE_DIRS\fR can be set to an array of
directory trees to exclude from tracking. \fB$HOME\fR is always excluded.
Directories must be full paths without trailing slashes.
.P
The environment variable \fB$_Z_OWNER\fR can be set to your username, to
allow usage of \fBz\fR when your sudo environment keeps \fB$HOME\fR set.
.SH
FILES
Data is stored in \fB$HOME/.z\fR. This can be overridden by setting the
\fB$_Z_DATA\fR environment variable. When initialized, \fBz\fR will raise an
error if this path is a directory, and not function correctly.
.P
A man page (\fBz.1\fR) is provided.
.SH
SEE ALSO
regex(7), pushd, popd, autojump, cdargs
.P
Please file bugs at https://github.com/rupa/z/

View File

@@ -4,7 +4,7 @@
#
# https://github.com/agkozak/zsh-z
#
# Copyright (c) 2018-2022 Alexandros Kozak
# Copyright (c) 2018-2025 Alexandros Kozak
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -52,6 +52,7 @@
# ZSHZ_CASE -> if `ignore', pattern matching is case-insensitive; if `smart',
# pattern matching is case-insensitive only when the pattern is all
# lowercase
# ZSHZ_CD -> the directory-changing command that is used (default: builtin cd)
# ZSHZ_CMD -> name of command (default: z)
# ZSHZ_COMPLETION -> completion method (default: 'frecent'; 'legacy' for
# alphabetic sorting)
@@ -99,20 +100,29 @@ With no ARGUMENT, list the directory history in ascending rank.
}
# Load zsh/datetime module, if necessary
(( $+EPOCHSECONDS )) || zmodload zsh/datetime
# Load zsh/files, if necessary
[[ ${builtins[zf_chown]} == 'defined' &&
${builtins[zf_mv]} == 'defined' &&
${builtins[zf_rm]} == 'defined' ]] ||
zmodload -F zsh/files b:zf_chown b:zf_mv b:zf_rm
# Load zsh/system, if necessary
[[ ${modules[zsh/system]} == 'loaded' ]] || zmodload zsh/system &> /dev/null
(( ${+EPOCHSECONDS} )) || zmodload zsh/datetime
# Global associative array for internal use
typeset -gA ZSHZ
# Fallback utilities in case Zsh lacks zsh/files (as is the case with MobaXterm)
ZSHZ[CHOWN]='chown'
ZSHZ[MV]='mv'
ZSHZ[RM]='rm'
# Try to load zsh/files utilities
if [[ ${builtins[zf_chown]-} != 'defined' ||
${builtins[zf_mv]-} != 'defined' ||
${builtins[zf_rm]-} != 'defined' ]]; then
zmodload -F zsh/files b:zf_chown b:zf_mv b:zf_rm &> /dev/null
fi
# Use zsh/files, if it is available
[[ ${builtins[zf_chown]-} == 'defined' ]] && ZSHZ[CHOWN]='zf_chown'
[[ ${builtins[zf_mv]-} == 'defined' ]] && ZSHZ[MV]='zf_mv'
[[ ${builtins[zf_rm]-} == 'defined' ]] && ZSHZ[RM]='zf_rm'
# Load zsh/system, if necessary
[[ ${modules[zsh/system]-} == 'loaded' ]] || zmodload zsh/system &> /dev/null
# Make sure ZSHZ_EXCLUDE_DIRS has been declared so that other scripts can
# simply append to it
(( ${+ZSHZ_EXCLUDE_DIRS} )) || typeset -gUa ZSHZ_EXCLUDE_DIRS
@@ -129,6 +139,7 @@ is-at-least 5.3.0 && ZSHZ[PRINTV]=1
# Globals:
# ZSHZ
# ZSHZ_CASE
# ZSHZ_CD
# ZSHZ_COMPLETION
# ZSHZ_DATA
# ZSHZ_DEBUG
@@ -143,15 +154,25 @@ is-at-least 5.3.0 && ZSHZ[PRINTV]=1
zshz() {
# Don't use `emulate -L zsh' - it breaks PUSHD_IGNORE_DUPS
setopt LOCAL_OPTIONS NO_KSH_ARRAYS NO_SH_WORD_SPLIT EXTENDED_GLOB
setopt LOCAL_OPTIONS NO_KSH_ARRAYS NO_SH_WORD_SPLIT EXTENDED_GLOB UNSET
(( ZSHZ_DEBUG )) && setopt LOCAL_OPTIONS WARN_CREATE_GLOBAL
local REPLY
local -a lines
# Allow the user to specify the datafile name in $ZSHZ_DATA (default: ~/.z)
# Allow the user to specify a custom datafile in $ZSHZ_DATA (or legacy $_Z_DATA)
local custom_datafile="${ZSHZ_DATA:-$_Z_DATA}"
# If a datafile was provided as a standalone file without a directory path
# print a warning and exit
if [[ -n ${custom_datafile} && ${custom_datafile} != */* ]]; then
print "ERROR: You configured a custom Zsh-z datafile (${custom_datafile}), but have not specified its directory." >&2
exit
fi
# If the user specified a datafile, use that or default to ~/.z
# If the datafile is a symlink, it gets dereferenced
local datafile=${${ZSHZ_DATA:-${_Z_DATA:-${HOME}/.z}}:A}
local datafile=${${custom_datafile:-$HOME/.z}:A}
# If the datafile is a directory, print a warning and exit
if [[ -d $datafile ]]; then
@@ -161,7 +182,7 @@ zshz() {
# Make sure that the datafile exists before attempting to read it or lock it
# for writing
[[ -f $datafile ]] || touch "$datafile"
[[ -f $datafile ]] || { mkdir -p "${datafile:h}" && touch "$datafile" }
# Bail if we don't own the datafile and $ZSHZ_OWNER is not set
[[ -z ${ZSHZ_OWNER:-${_Z_OWNER}} && -f $datafile && ! -O $datafile ]] &&
@@ -265,7 +286,7 @@ zshz() {
if (( ret != 0 )); then
# Avoid clobbering the datafile if the write to tempfile failed
zf_rm -f "$tempfile"
${ZSHZ[RM]} -f "$tempfile"
return $ret
fi
@@ -273,16 +294,26 @@ zshz() {
owner=${ZSHZ_OWNER:-${_Z_OWNER}}
if (( ZSHZ[USE_FLOCK] )); then
zf_mv "$tempfile" "$datafile" 2> /dev/null || zf_rm -f "$tempfile"
# An unsual case: if inside Docker container where datafile could be bind
# mounted
if [[ -r '/proc/1/cgroup' && "$(< '/proc/1/cgroup')" == *docker* ]]; then
print "$(< "$tempfile")" > "$datafile" 2> /dev/null
${ZSHZ[RM]} -f "$tempfile"
# All other cases
else
${ZSHZ[MV]} "$tempfile" "$datafile" 2> /dev/null ||
${ZSHZ[RM]} -f "$tempfile"
fi
if [[ -n $owner ]]; then
zf_chown ${owner}:"$(id -ng ${owner})" "$datafile"
${ZSHZ[CHOWN]} ${owner}:"$(id -ng ${owner})" "$datafile"
fi
else
if [[ -n $owner ]]; then
zf_chown "${owner}":"$(id -ng "${owner}")" "$tempfile"
${ZSHZ[CHOWN]} "${owner}":"$(id -ng "${owner}")" "$tempfile"
fi
zf_mv -f "$tempfile" "$datafile" 2> /dev/null || zf_rm -f "$tempfile"
${ZSHZ[MV]} -f "$tempfile" "$datafile" 2> /dev/null ||
${ZSHZ[RM]} -f "$tempfile"
fi
# In order to make z -x work, we have to disable zsh-z's adding
@@ -294,7 +325,7 @@ zshz() {
}
############################################################
# Read the curent datafile contents, update them, "age" them
# Read the current datafile contents, update them, "age" them
# when the total rank gets high enough, and print the new
# contents to STDOUT.
#
@@ -620,7 +651,7 @@ zshz() {
*)
# Frecency routine
(( dx = EPOCHSECONDS - time_field ))
rank=$(( 10000 * rank_field * (3.75/((0.0001 * dx + 1) + 0.25)) ))
rank=$(( 10000 * rank_field * (3.75/( (0.0001 * dx + 1) + 0.25)) ))
;;
esac
@@ -756,6 +787,26 @@ zshz() {
[[ $output_format != 'completion' ]] && output_format='list'
}
#########################################################
# Allow the user to specify directory-changing command
# using $ZSHZ_CD (default: builtin cd).
#
# Globals:
# ZSHZ_CD
#
# Arguments:
# $* Path
#########################################################
zshz_cd() {
setopt LOCAL_OPTIONS NO_WARN_CREATE_GLOBAL
if [[ -z $ZSHZ_CD ]]; then
builtin cd "$*"
else
${=ZSHZ_CD} "$*"
fi
}
#########################################################
# If $ZSHZ_ECHO == 1, display paths as you jump to them.
# If it is also the case that $ZSHZ_TILDE == 1, display
@@ -773,7 +824,7 @@ zshz() {
if [[ ${@: -1} == /* ]] && (( ! $+opts[-e] && ! $+opts[-l] )); then
# cd if possible; echo the new path if $ZSHZ_ECHO == 1
[[ -d ${@: -1} ]] && builtin cd ${@: -1} && _zshz_echo && return
[[ -d ${@: -1} ]] && zshz_cd ${@: -1} && _zshz_echo && return
fi
# With option -c, make sure query string matches beginning of matches;
@@ -830,12 +881,12 @@ zshz() {
print -- "$cd"
else
# cd if possible; echo the new path if $ZSHZ_ECHO == 1
[[ -d $cd ]] && builtin cd "$cd" && _zshz_echo
[[ -d $cd ]] && zshz_cd "$cd" && _zshz_echo
fi
else
# if $req is a valid path, cd to it; echo the new path if $ZSHZ_ECHO == 1
if ! (( $+opts[-e] || $+opts[-l] )) && [[ -d $req ]]; then
builtin cd "$req" && _zshz_echo
zshz_cd "$req" && _zshz_echo
else
return $ret2
fi
@@ -852,6 +903,9 @@ alias ${ZSHZ_CMD:-${_Z_CMD:-z}}='zshz 2>&1'
# ZSHZ
############################################################
_zshz_precmd() {
# Protect against `setopt NO_UNSET'
setopt LOCAL_OPTIONS UNSET
# Do not add PWD to datafile when in HOME directory, or
# if `z -x' has just been run
[[ $PWD == "$HOME" ]] || (( ZSHZ[DIRECTORY_REMOVED] )) && return
@@ -899,10 +953,10 @@ add-zsh-hook chpwd _zshz_chpwd
# Completion
############################################################
# Standarized $0 handling
# (See https://github.com/agkozak/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc)
0=${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}
0=${${(M)0:#/*}:-$PWD/$0}
# Standardized $0 handling
# https://zdharma-continuum.github.io/Zsh-100-Commits-Club/Zsh-Plugin-Standard.html
0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
0="${${(M)0:#/*}:-$PWD/$0}"
(( ${fpath[(ie)${0:A:h}]} <= ${#fpath} )) || fpath=( "${0:A:h}" "${fpath[@]}" )
@@ -926,7 +980,7 @@ ZSHZ[FUNCTIONS]='_zshz_usage
# Enable WARN_NESTED_VAR for functions listed in
# ZSHZ[FUNCTIONS]
############################################################
(( ZSHZ_DEBUG )) && () {
(( ${+ZSHZ_DEBUG} )) && () {
if is-at-least 5.4.0; then
local x
for x in ${=ZSHZ[FUNCTIONS]}; do

View File

@@ -1,267 +0,0 @@
# Copyright (c) 2009 rupa deadwyler. Licensed under the WTFPL license, Version 2
# 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_MAX_SCORE lower to age entries out faster (default 9000).
# 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.
# set $_Z_OWNER to your username if you want use z while sudo with $HOME kept
#
# 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 matches instead of cd
# * z -e foo # echo the best match, don't cd
# * z -c foo # restrict matches to subdirs of $PWD
# * z -x # remove the current directory from the datafile
# * z -h # show a brief help message
[ -d "${_Z_DATA:-$HOME/.z}" ] && {
echo "ERROR: z.sh's datafile (${_Z_DATA:-$HOME/.z}) is a directory."
}
_z() {
local datafile="${_Z_DATA:-$HOME/.z}"
# if symlink, dereference
[ -h "$datafile" ] && datafile=$(readlink "$datafile")
# bail if we don't own ~/.z and $_Z_OWNER not set
[ -z "$_Z_OWNER" -a -f "$datafile" -a ! -O "$datafile" ] && return
_z_dirs () {
[ -f "$datafile" ] || return
local line
while read line; do
# only count directories
[ -d "${line%%\|*}" ] && echo "$line"
done < "$datafile"
return 0
}
# add entries
if [ "$1" = "--add" ]; then
shift
# $HOME and / aren't worth matching
[ "$*" = "$HOME" -o "$*" = '/' ] && return
# don't track excluded directory trees
if [ ${#_Z_EXCLUDE_DIRS[@]} -gt 0 ]; then
local exclude
for exclude in "${_Z_EXCLUDE_DIRS[@]}"; do
case "$*" in "$exclude"*) return;; esac
done
fi
# maintain the data file
local tempfile="$datafile.$RANDOM"
local score=${_Z_MAX_SCORE:-9000}
_z_dirs | awk -v path="$*" -v now="$(date +%s)" -v score=$score -F"|" '
BEGIN {
rank[path] = 1
time[path] = now
}
$2 >= 1 {
# drop ranks below 1
if( $1 == path ) {
rank[$1] = $2 + 1
time[$1] = now
} else {
rank[$1] = $2
time[$1] = $3
}
count += $2
}
END {
if( count > score ) {
# aging
for( x in rank ) print x "|" 0.99*rank[x] "|" time[x]
} else for( x in rank ) print x "|" rank[x] "|" time[x]
}
' 2>/dev/null >| "$tempfile"
# do our best to avoid clobbering the datafile in a race condition.
if [ $? -ne 0 -a -f "$datafile" ]; then
env rm -f "$tempfile"
else
[ "$_Z_OWNER" ] && chown $_Z_OWNER:"$(id -ng $_Z_OWNER)" "$tempfile"
env mv -f "$tempfile" "$datafile" || env rm -f "$tempfile"
fi
# tab completion
elif [ "$1" = "--complete" -a -s "$datafile" ]; then
_z_dirs | awk -v q="$2" -F"|" '
BEGIN {
q = substr(q, 3)
if( q == tolower(q) ) imatch = 1
gsub(/ /, ".*", q)
}
{
if( imatch ) {
if( tolower($1) ~ q ) print $1
} else if( $1 ~ q ) print $1
}
' 2>/dev/null
else
# list/go
local echo fnd last list opt typ
while [ "$1" ]; do case "$1" in
--) while [ "$1" ]; do shift; fnd="$fnd${fnd:+ }$1";done;;
-*) opt=${1:1}; while [ "$opt" ]; do case ${opt:0:1} in
c) fnd="^$PWD $fnd";;
e) echo=1;;
h) echo "${_Z_CMD:-z} [-cehlrtx] args" >&2; return;;
l) list=1;;
r) typ="rank";;
t) typ="recent";;
x) sed -i -e "\:^${PWD}|.*:d" "$datafile";;
esac; opt=${opt:1}; done;;
*) fnd="$fnd${fnd:+ }$1";;
esac; last=$1; [ "$#" -gt 0 ] && shift; done
[ "$fnd" -a "$fnd" != "^$PWD " ] || list=1
# if we hit enter on a completion just go there
case "$last" in
# completions will always start with /
/*) [ -z "$list" -a -d "$last" ] && builtin cd "$last" && return;;
esac
# no file yet
[ -f "$datafile" ] || return
local cd
cd="$( < <( _z_dirs ) awk -v t="$(date +%s)" -v list="$list" -v typ="$typ" -v q="$fnd" -F"|" '
function frecent(rank, time) {
# relate frequency and time
dx = t - time
return int(10000 * rank * (3.75/((0.0001 * dx + 1) + 0.25)))
}
function output(matches, best_match, common) {
# list or return the desired directory
if( list ) {
if( common ) {
printf "%-10s %s\n", "common:", common > "/dev/stderr"
}
cmd = "sort -n >&2"
for( x in matches ) {
if( matches[x] ) {
printf "%-10s %s\n", matches[x], x | cmd
}
}
} else {
if( common && !typ ) best_match = common
print best_match
}
}
function common(matches) {
# find the common root of a list of matches, if it exists
for( x in matches ) {
if( matches[x] && (!short || length(x) < length(short)) ) {
short = x
}
}
if( short == "/" ) return
for( x in matches ) if( matches[x] && index(x, short) != 1 ) {
return
}
return short
}
BEGIN {
gsub(" ", ".*", q)
hi_rank = ihi_rank = -9999999999
}
{
if( typ == "rank" ) {
rank = $2
} else if( typ == "recent" ) {
rank = $3 - t
} else rank = frecent($2, $3)
if( $1 ~ q ) {
matches[$1] = rank
} else if( tolower($1) ~ tolower(q) ) imatches[$1] = rank
if( matches[$1] && matches[$1] > hi_rank ) {
best_match = $1
hi_rank = matches[$1]
} else if( imatches[$1] && imatches[$1] > ihi_rank ) {
ibest_match = $1
ihi_rank = imatches[$1]
}
}
END {
# prefer case sensitive
if( best_match ) {
output(matches, best_match, common(matches))
exit
} else if( ibest_match ) {
output(imatches, ibest_match, common(imatches))
exit
}
exit(1)
}
')"
if [ "$?" -eq 0 ]; then
if [ "$cd" ]; then
if [ "$echo" ]; then echo "$cd"; else builtin cd "$cd"; fi
fi
else
return $?
fi
fi
}
alias ${_Z_CMD:-z}='_z 2>&1'
[ "$_Z_NO_RESOLVE_SYMLINKS" ] || _Z_RESOLVE_SYMLINKS="-P"
if type compctl >/dev/null 2>&1; then
# zsh
[ "$_Z_NO_PROMPT_COMMAND" ] || {
# populate directory list, avoid clobbering any other precmds.
if [ "$_Z_NO_RESOLVE_SYMLINKS" ]; then
_z_precmd() {
(_z --add "${PWD:a}" &)
: $RANDOM
}
else
_z_precmd() {
(_z --add "${PWD:A}" &)
: $RANDOM
}
fi
[[ -n "${precmd_functions[(r)_z_precmd]}" ]] || {
precmd_functions[$(($#precmd_functions+1))]=_z_precmd
}
}
_z_zsh_tab_completion() {
# tab completion
local compl
read -l compl
reply=(${(f)"$(_z --complete "$compl")"})
}
compctl -U -K _z_zsh_tab_completion _z
elif type complete >/dev/null 2>&1; then
# bash
# tab completion
complete -o filenames -C '_z --complete "$COMP_LINE"' ${_Z_CMD:-z}
[ "$_Z_NO_PROMPT_COMMAND" ] || {
# populate directory list. avoid clobbering other PROMPT_COMMANDs.
grep "_z --add" <<< "$PROMPT_COMMAND" >/dev/null || {
PROMPT_COMMAND="$PROMPT_COMMAND"$'\n''(_z --add "$(command pwd '$_Z_RESOLVE_SYMLINKS' 2>/dev/null)" 2>/dev/null &);'
}
}
fi