Hacker News new | past | comments | ask | show | jobs | submit login
Z – Jump around (github.com/rupa)
139 points by lovestaco 9 months ago | hide | past | favorite | 75 comments



I tried this and a handful of alternatives a while back, but would often get frustrated because they’d jump to something different than what I intended or were missing some directory I wanted.

Eventually I came up with an alternative using fd¹ and fzf². I have a variant of this in my .zshrc:

  function n {
    cd "$(fd . "${HOME}" --type d --color never | fzf --select-1 --query "${*}")"
    ls
  }
Call it with `n` and get an interactive fuzzy search for your directories. If you do `n <argument>`, it’ll start the find with `<argument>` already filled in (and if there’s only one match, jump to it directly). The `ls` is optional but I find that I like having the contents visible as soon as I change directories.

In my personal setup I’m also including iCloud Drive while excluding the rest of the Library directory³ as that is too noisy. I have a separate `nl` function which searches just inside `~/Library` for when I need it, as well as other specialised `n<char>` functions that search inside specific places that I use frequently.

¹ https://github.com/sharkdp/fd

² https://github.com/junegunn/fzf

³ Change the fd command to: fd . "${HOME}" "${HOME}/Library/Mobile Documents/com~apple~CloudDocs" --exclude '/Library/' --type d --color never


Yes, I made a similar keybinding for xonsh, using fd and fzf. I press Alt-c, and fzf shows me all the subdirectories rooted where I'm at.

That's a good intermediary solution. But the one that totally changed my flow was to combine autojump[1] and fzf. autojump is similar to Z (this submission). It stores all the directories you've visited in an SQLite DB and can show them to you (ordered by visit frequency) with a command line argument. So I pipe that to fzf.

Now I can extremely quickly jump to any directory I've been to before - it really helps that they're sorted by visit frequency. I honestly use this more than any other approach - and I probably go for days on end without using the usual TAB autocompletion.

[1] https://github.com/wting/autojump


In most shells, you can just add those entries as shell completions to `cd` or `z`.

In fish for example, it's as easy as the following (using zoxide query)

    complete -c z -f -k -a "(zoxide query -l)"


I don’t just want specific directories to autocomplete. Whatever I navigate to is likely something I never even opened but was generated by a script and I now want to do manual changes to the contents. This approach makes for me the rare case as usable as the common one.


Hey thanks! I've been wanting something like this for ages, and didn't really think about combining fzf with fd.

I've tweaked it to search a lot less folders (depth 5) and to restrict to a pattern beginning by X...

    cd "$(fd "^${*}" "${HOME}" --max-depth 4 --type d --color never | fzf --select-1 --query "${*}")"
This makes it super snappy. I probably have waaay too many folders with similar names.


Same reflex. As much as I appreciate full depth, I realize I really have many deep trees and it makes things laggy.

ps: out of fun, I wanted to toy with fzf --multi mode

    function zzg {
        local selection="$(fd . "${HOME}" --type d --color never --max-depth 4 | fzf -m --query "${*}")";
        local term="xterm"
        for dir in $selection
        do
            "${term}" -title "zzg({$dir})" -e "cd ${dir} && bash" &
        done
    }


I was frustrated with jumping to frecent directories, so I "reverted" back to jumping back to bookmarked directories [0] (which I find much more predictable).

0: https://github.com/techwizrd/fishmarks


This seems pretty great! I love tips like this.

I'm going to try out a version of this now.


I use this Rust clone which works great, no complaints: https://github.com/ajeetdsouza/zoxide

Although, I don't know what the difference is, other than the language of choice.


The README is an order of magnitude more useful. The man page aesthetic of z's README hits me in the feels, but I prefer the better documentation of zoxide.


Zoxide’s zi command does an interactive z. It is one of my favorite commands


Fuzziness does not seem to be on point for me though, any tips? If I want to `cd` into a directory called `something-with-dashes`, if I do `zi` and then start with `smtw` (omitting the `-`), it doesn't find it. Only when I add `-` it shows up.


Sadly, no :(. I always just look for exact substrings. Might be worth trying to get working though…


Thanks for this! I had no idea zoxide has this! Made my day.


Came to post zoxide. Also if you use `ranger`[1] (vim inspired file manager) then you might like to add the `ranger-zoxide` plugin[2].

1. https://github.com/ranger/ranger

2. https://github.com/jchook/ranger-zoxide


I used to use z, then z from omz but eventually switched fo zoxide because it also works on PowerShell.


It seems like the Rust community is quite happy to support alternative shells. I’ve seen couple of projects, now, that support way more esoteric shells than I would expect, like ’xonsh’. Starship (https://starship.rs/) immediately comes to mind.


biggest difference is probably that it works on Windows, which is something that I really appreciate about most of the Rust clones. Almost all of them are pretty platform agnostic.


I like to use Z as a fish shell plugin. [1]

> A pure-fish port means z is fast and fish-friendly, with tab-completions and lazy-loading. Top that off with great customizability and a small amount of added functionality.

[1] https://github.com/jethrokuan/z


This is what I use. I am tempted by zoxide but I've never been unhappy with this, so the temptation remains quite low.


Alternatives: zoxide, autojump, fasd, and probably couple other


https://github.com/skywind3000/z.lua was another that I used for a long time, although I've lately started using https://github.com/jethrokuan/z

A lot of reinventing the wheel in the z space it seems


+1 zoxide


I actually thought the OP was about zoxide, as the latter also installs as z.


zoxide is great - in fact I tend to just alias cd to zoxide in my bashrc


Theory: The likelihood of me using a command line tool goes down roughly exponentially with the number of keystrokes I have to make to call.

Z is really good in this regard, but I still find myself relatively reaching for fzf's Alt+C keybinding, as I outlined in https://andrew-quinn.me/fzf way back. I think I need to come across some killer thing `z` does that I suddenly can't live without to overcome the activation energy.


Map it to whatever shortcut pleases you. It's your shell :)

I use this tiny function:

  Z() {
    [ $# -gt 0 ] && _z "$*" && return
    cd "$(_z -l 2>&1 | fzf --height 40% --nth 2.. --reverse --inline-info +s --tac --query "${*##-* }" | sed 's/^[0-9,.]* *//')"
  }


> I think I need to come across some killer thing `z` does that I suddenly can't live without to overcome the activation energy.

See my other comment on combining z and fzf: https://news.ycombinator.com/item?id=39030391


I'll avoid any tools or software that "squats" the one, two or three letter space. Almost all one and many of the two-letters are already "taken" by some script, alias or both. "z" is hibernate for me.

e.g. a timetracker tool "squatted" the "t". I'm the one to decide if this tool is that important to get a one-letter. In this case I had to hack around, fork even, to be able to use t (which is my todo.txt stuff: t, tt, ta, tl, tp, etc) and to use the timetracker (b - no idea why I started using the b for this, decades ago: b, bl, bs, etc).

If a developer of a tool decides to "squat" one of the 27 letters in the alphabet, I think they have delusions of grandeur: You are quite certainly not building one of the 27 most important tools ever built.


Wouldn't this easily be solved by an alias or setting your script's directory earlier in your $PATH in your shell's RC file? I'm not sure how it's becoming an issue at all. It's just an executable name, in the end, you control your environment.


Yes, fiddling with the $PATH works, until it doesn't.

In this case the app was installed via RBENV, so it works but only if I decide that all of the RBENV stuff must be after anything else, which breaks some other hacks/tricks. So it would work, until I forget about it and years later need some rbenv-installed tool to have precedence over something that was globally installed. After which that alias I forgot about now suddenly breaks.

I hate it when I'm working on A and get distracted because somehow now B fails and some rabbit hole is luring me in. I just want to check my TODO's for today: not fiddle around with $PATHS and aliases.

Aliasing somewhat helps, but is clumsy too:

alias b=t alias t=todotxt alias bl=b list --all

Sure, better organization solves this. But everything would be so much easier if the person writing "t" had stopped and rather just taken 'timetrack', 'ttrack', trac or whatever instead of "t". Somehow we probably won't like it if such a tool suddenly takes over "tr" or "time" either. I feel the same about "t".


There's also unalias.


I’m glad I’m not the only one who feels weird about shadowing existing executable names. It’s actually fun to find commands I have never used before this way and read about them… then move on picking a different alias name. My dumb rule is anything I would have made a one letter alias, just use that same letter three times (ex: ggg=lazydocker)


What is the 27th letter?


Sorry, Dutch here, We use the IJ, which isn't officially a separate letter, but somehow I learned we have 27 letters (when sorting, the IJ comes before the Z. When capitalizing, IJsselmeer, is right, Ijsselmeer is wrong). ]

26 would be for the English alphabet and therefore the terminal. I was mistaken.


You don't have to write the I and J separately, if you have easy access to IJ/ij:

    U+0132 LATIN CAPITAL LIGATURE IJ
    U+0133 LATIN SMALL LIGATURE IJ
(On the EurKEY layout[1] this is as easy as AltGr + k)

[1]: https://eurkey.steffen.bruentjen.eu/


My lowtech solution was to make a function with an array of hardcoded paths, it would then present a menu where you could select 0-N and it would jump. It also started with Terminal.


My lowtech solution was just a bunch of aliases for directories i use a lot. Like:

  alias .a='cd ~/src/autosqueezer'
  alias .g='cd ~/src/gcc-squeeze-plugin'
  alias .u='cd ~/src/squeeze-utils'
etc. You have to manage them by hand, but that's easy, and makes the result much more predictable than anything based on history.

I also have contextual companions to these, defined as functions, like:

  .r() {
    local project_root=$(somehow work out the root of the current project)
    cd $project_root
  }

  .b() {
    .r
    cd build
  }


A slightly different take is available if you're a zsh user, as you can take advantage of named directories to replace the aliases. If you do this the directories you've added will show up in regular completions and be available as arguments to commands too. For example, `hash -d name=location` allows you to use `~name` for the path.

`run-help hash` for the basic interface, or zshexpn(1) for the gory details including fancy dynamic naming.


Are you aware of wd? (https://github.com/quackduck/WarpDrive)

``` cd ~/src/autosqueezer wd add a # later, somewhere else wd a ```


Anyone who have used both z and autojump? What is the difference?

https://github.com/wting/autojump


autojump allows you to open file manager, … while z is truly “autojump”, just cd into the directory.


autojump is first on my list of command line things I can't live without.


It would be good to have a log of the last N directories visited in variables like cd1, cd2, cd3, ... cd9.

These could be interpolated into commands:

  $ cd somewhere # found via CDPATH
  /path/to/somewhere
  $ cp $cd1/file.txt .   # copy file from where we were just before the cd, to here
cd1 would be always the most recent, cd2 second most recent.

Here it is up to cd4:

  if [ -z "$c1" ] ; then
    c4=
    c3=
    c2=
  fi

  cdlog_chdir()
  {
    local cur=$PWD
    if command cd "$@" ; then
      c4=$c3
      c3=$c2
      c2=$c1
      c1=$cur
    fi
  }

  cdlog_chdir()
  {
    local cur=$PWD

    if command cd "$@" && [ "$PWD" != "$cur" ]; then
      # only if we successfully change to a different
      # directory do the following

      if [ "$cur" == "$c2" ] && [ "$PWD" == "$c1" ] ; then
        # Special case: if we changed to the directory that is second
        # in the cdlog, then just swap between those two.
        c2=$PWD
        c1=$cur
      else
        # Otherwise rotate through all four.
        c4=$c3
        c3=$c2
        c2=$c1
        c1=$cur
      fi
    fi
  }
Tip: in bash use ESC Ctrl-E to expand variables in the command line before running it. Then the command is recorded with concrete paths, rather than variables that are changing. You can recall the command from history to repeat it.

Handy aliases:

  alias cd='cdlog_chdir -P'
  alias cs='cdlog_chdir "$c1"'
"cs" goes back to the previous; if that is repeated, it triggers the swapping behavior in cdlog_chdir, hence the "cs" name.


In bash you can probably use "pushd" instead of "cd" (it builds a stack and can swap top two entries, as well as rotate the stack) and abuse "dirs" (to create the variables with something like "c3=$( dirs +3 2>/dev/null )").


I've used the pushd stuff for 30 years, but this system I just came up with is way more ergonomic.

The dirs command is a poorly considered interface to what should just be a handful of simple variables.

The whole feature is basically mistake, copied from tcsh in order to help tcsh users switch to Bash or something.

pushd with no arguments swaps between the most recent two. That's an important use case and I nailed that requirement with the "cs" alias.

I just added a function to cdlog_pop aliased to pd, which changes to $c1 and removes it, rotating the entries in the other direction. I extended the history to 9 items (with c5 to c9 being hidden entries not shown by cdlog).

  if [ -z "$c1" ] ; then
    c9=
    c8=
    c7=
    c6=
    c5=
    c4=
    c3=
    c2=
  fi

  cdlog_chdir()
  {
    local cur=$PWD

    if command cd "$@" && [ "$PWD" != "$cur" ]; then
      # only if we successfully change to a different
      # directory do the following

      if [ "$cur" == "$c2" ] && [ "$PWD" == "$c1" ] ; then
        # Special case: if we changed to the directory that is second
        # in the cdlog, then just swap between those two.
        c2=$PWD
        c1=$cur
      else
        # Otherwise rotate through all
        c9=$c8
        c8=$c7
        c7=$c6
        c6=$c5
        c5=$c4
        c4=$c3
        c3=$c2
        c2=$c1
        c1=$cur
      fi
    fi
  }

  cdlog_pop()
  {
    if [ -n "$c1" ] && command cd "$c1"; then
      c1=$c2
      c2=$c3
      c3=$c4
      c4=$c5
      c5=$c6
      c6=$c7
      c7=$c8
      c8=$c9
      c9=
    fi
  }

  cdlog()
  {
    # c5 through c9 are hidden
    printf "c1: %s\n" "$c1"
    printf "c2: %s\n" "$c2"
    printf "c3: %s\n" "$c3"
    printf "c4: %s\n" "$c4"
  }

  alias cs='cdlog_chdir "$c1"'
  alias cd='cdlog_chdir -P'
  alias pd='cdlog_pop'


pushd also gives you simple access via `~-n` in both bash and zsh out of the box. The bonus being that `~-[n]` is correctly handled when used as an argument to other commands too, so that `cp myfile ~-4/` does what you'd expect. The zsh completion for cd functions really nicely for this too, given that you can use zstyle to annotate your entries or group dynamic completions separately.

If you're using zsh you can implement the GPs idea by hooking chpwd, so that you don't need to change your muscle memory on cd. You can even combine them by enabling the auto_pushd option and just carrying on with regular cd whilst gaining access to `~-n` with no other changes.


~-2 is shorter than messing with the dirs command but it does not improve over $c2. It does not rescue the whole directory stack misfeature which has no reason for existing as built-in C code inside Bash, with dedicated expansion syntax. It interferes with existing syntax, too; according to POSIX, that refers to the home directory of a user named -2.

We can "export c1" and the others to make them visible to child processes. That makes it possible to launch a subshell in which the parent shell's directory log is readily available.

Things that can be very effectively and ergonomically achieved with a bit of scripting in the user's environment should never be built into the command interpreter. That just adds to the executable size, documentation, and maintenance.


Personally, I'd find the completion output from `~-<Tab>` a worthy improvement, although admittedly with zsh you can configure completion to display both name and value for `$c<Tab>` too I guess(can't recall if that is possible with bash).

User names should not begin with a hyphen¹, so it shouldn't interfere with home directory references.

The child process point is an interesting one, I feel like you could workaround it by using a chpwd hook to export $dirstack and set it at startup(or a precmd function and $DIRSTACK if using bash). Something like `export DIRSTACK=${(pj#:#)dirstack}` in the hook and `dirstack=(${(ps#:#)DIRSTACK})` in the startup file. I'll concede this point though, as while a fun thought experiment the end result would be ghastly ;)

¹ https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...


I'm noticing that in Bash, when I complete on ~-1 without a trailing slash, nothing happens. When I use $c1 and hit tab, it adds the slash for me! So in that situation, $c1 saves a keystroke. Well, not a keystroke per se, but having to use a different character rather than repeated Tab.

Also, I can shorten these variables to one letter, like $p, $q, ...

Bash has an option so that tab completion will expand out the directory.

  shopt -s direxpand
this is useful because variables like $c1 or syntax like ~-1 entered into the history is not useful for recall.

I know about M-x C-e to expand everything, but that is far too clumsy compared to just the tab completion doing it.

Here is something: the direxpand option is not working for ~-n/[Tab] completions. Only for variable names!


My description above wasn't great. I meant completion would be an improvement in the sense that under zsh `~-<Tab>` can show not only the possible completion options, but also the value they expand to. For example, mine is set to display in the manner of a colorful version of the text below:

    $ cd ~-
    0 -- ~
    1 -- ~/Downloads
    2 -- ~/Desktop
The handling of tilde expansion under bash is a readline option, so users would configure it in their inputrc. It is pretty useful in general, because then it is also available to tools like imv¹ beyond just bash. Unfortunately, as it is handled separately from word completion you'd need to change the completion function to make it match the mark-directories behaviour you like.

If I wanted the text expanded for history use I'd probably expand it before writing using a zshaddhistory hook, but I don't think there is an equivalent in bash so changing the line buffer probably is the best option.

¹ https://www.nongnu.org/renameutils/


FYI for windows, I made this a long time ago:

https://github.com/tkellogg/Jump-Location

Which was fun and all, but eventually replaced by a pure PowerShell implementation that's become far more active:

https://github.com/vors/ZLocation


I've experimented in this area. I once made a "pcd" (parent cd). The idea is that "cd" (if relative) changes the last component of your directory: if you're in foo/bar and type "cd baz/xyzzy", then the /bar component is rewritten to /baz/xyzzy".

My "pcd" would similarly rewrite the parent component instead. If you're in "a/b/c" and do "pcd x" you change to "a/x/c"; and "pcd x/y" would change to "a/x/y/c". Useful for when you have some similar directories with parallel structures.

I gave it a numeric argument defaulting to 1. "pcd 0" is like cd, "pcd 1" is like pcd, and then higher integer switch higher components.



Heavy user of `z` for many years... that is until it corrupted its own database one final time. There's nothing more frustrating than a dropped or corrupted directory database just as you've got the damn thing to remember all your favourite spots on the disk.

These days I use https://github.com/gsamokovarov/jump which I've mapped to `z`. Happy days.


Fish does something similar but it's not recency/frequency related. I can type just a small part of each path leaf, including slashes, and hit tab, and Fish will auto complete the entire path unless there's an ambiguity.

99% of the time it gets it right, 0.9% of the time it's ambiguous, and only a handful of times did it find something I didn't intend (shocking twist: I had misspelled something).


I used something different, but it didn't work as expected because I have multiple directories that start with the same letter. Consequently, the shortcuts changed their paths and I never ended up where I wanted.

I built my own tool called Zee, that takes a simplified approach by only utilizing explicit user-defined shortcuts: https://github.com/dnsv/zee


If you’re using oh-my-zsh you can add z to your plugins list and you’ll get the functionality without needing to install anything.

It is a must have


zsh's has "cdr" as part of the standard distribution, which I have been using for years. See zshcontrib.

I wonder if anyone can make a comparison. From an usage perspective, they seem equivalent. cdr's configuration is more zsh-like.


I've been using enhancd for years now.

https://github.com/babarot/enhancd


Honestly with fzf you can achieve the same thing. I’m not having anything against enhancd, but it’s surplus nowadays.


Is there history or a meme that holds that z is for jump?

Just wondering if that is why the hotkey for the jump action in Baldur's Gate 3 is z


Speaking with no actual knowledge on Z’s naming, there is a long history and important relationship in CPU design between the “Zero flag” (Z), and “jump” instructions.

The zero flag stores a status from the last instruction the cpu ran, which the jump cpu instructions then reference to affect what happens to code execution. This logic is the most basic form of conditionals, and underpins the implementations of higher level flow control like if statements and loops.

I’d guess that this relationship between Z and jumping is where it comes from. As for Baulders Gate I have no idea, but lean to that being a coincidence.


I have been using z for many years now, and I cannot recommend it enough. This is one of the most useful tools ever.


Z is part of my dotfiles. Everywhere I clone them, Z would be available immediately.


I just use batch files '1', '2', '3' ... to cd to commonly-used directories. No need for anything special beyond a "qcd.exe" program to write the batch files (e.g., "qcd 1" allows "1" to return to the current working directory.)


Why is this better than CDPATH that is already built into my shell?


CDPATH is kind of flawed. It wants to be a mapping feature.

So that is to say, when I type "cd foo", I want foo to be "cd alias" for some "/path/to/foo" or possibly even "/path/to/bar". I know where I want to go; I don't need a search to find it!

Fortunately, there is a way to get that with CDPATH. You can make some directory, say /home/yourself/.cdfarm.

Inside .cdfarm/, you have symlinks to various directories

foo -> /path/to/foo bar -> /path/to/elsewhere/bar

and you put this .cdfarm into your CDPATH:

CDPATH=:~/.cdfarm

and that's it, all other management is via ~/.cdfarm.

Unfortunately, the symlinks appear in the PWD, unless you use "cd -P"; for that you can "alias cd='cd -P".

Even if you use -P, when Bash's cd resolves something via CDPATH, it prints the target path name with the /home/yourself/.cdpath/... in it, as it appears before -P resolution. That's not necessarily a bad thing; it reminds you of how you are getting to where you are.


Think of this as an auto-CDPATH. With CDPATH you have to explicitly add the directories. And even with CDPATH, it won't work if you need to go deep down.

As an example, say I have ~/Programs/Firefox/Profiles/kdskdj/Cache.

Most people would put just ~/Programs or ~/Programs/Firefox into CDPATH. If you want to get to the Cache directory, you still need to know "Profiles/kdskdj/Cache". With z, if you're lucky, you'll just do "z Cache". Worst case, "z Fire Cache"


Do you know of completion for the cd command that works with CDPATH? So that when you type "cd prefix[Tab]" it will give completions from CDPATH?


for one, it is not built into either of my shells: Windows gitbash, or OSX terminal.


OS X Terminal isn't your shell, by default zsh is your shell on Mac OS, and it does support cdpath: https://zsh.sourceforge.io/Intro/intro_13.html

I assume Windows Linux thing uses Bash? It works even more simply, exactly like $PATH.


I've been using for years, can recomend.


This looks great, I love finding simple productivity boosts like this. Not simple in implementation but simple in how easily it becomes part of your workflow.


Been using for probably 8 years, amazing




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: