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.
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.
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.
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).
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.
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.
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.
> 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.
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.
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".
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)
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.
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.
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.
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 ;)
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.
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.
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
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 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.)
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"
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.
Eventually I came up with an alternative using fd¹ and fzf². I have a variant of this in my .zshrc:
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