I’ve been getting into fzf recently. In case what it does isn’t immediately obvious to you (it wasn’t to me), here’s what it does:
- You send a bunch of things to
fzf, one per line.
fzflets you pick one (or more!) of the things that was sent to it.
fzfprints, to standard output, that thing or those things that you picked.
This has a couple obvious use cases:
- “I want to
cdto a directory, but I only want to type the unique end of the path, not the entire path from here to there.”
- “I want to
sshinto this one server again, but
history | grep ssh | grep horse.exampleis more typing
grepthan I want to do.”
If you install
fzf by hand, you’ll get an opportunity to let it install its own things into your shell’s configuration. If you let it, it’ll set up C-t, C-r, and M-c so that they run a “file widget”, “history widget”, and a “
cd widget”, respectively. Pretty handy, but:
- This installation process puts a symlink from your fish-config directory (likely
/usr/local/something/fzf/key-bindings.fish. What’s worse, that
somethingcould vary depending on your OS. This might not be an issue for most fish users, but I use the same
fishconfiguration on both macOS and FreeBSD computers. If the
key-bindings.fishfile is in different places on both macOS and FreeBSD, I was gonna have a bad time.
- My primary OS is macOS. While the Meta key may be easily used on every other OS, the only hardware Meta key I have is the Option key, and it’s being used to make curly quotes and ellipses, even in Terminal.app. Sure, I can press Escape and then press C, it’s not nearly as pleasant as pressing Alt-C.
I then tried to see if I could get 95% of what I wanted with 0% of the invasive code in my
Searching through history
Normally, when I want to search through my history, I run
hgrep foo, where
hgrep is short for
history | grep.
I ended up with this:
function hfzf set -l to_run (history | fzf) if test -n "$to_run" echo "$to_run" eval "$to_run" end end
fish won’t let you just run
(history | fzf). It’ll give you an error of
fish: Command substitutions not allowed. I’m not sure, but I think this is to keep
fish users from shooting themselves in the foot. However, if you explicitly set that command to a variable and then run
eval on it,
fish will let you do what you want.
echo line is there because it feels weird to run a command that hasn’t been printed to the console in some form.
bash will print out lines that have had command-substitution magic (!!, etc.) applied to them; I wanted the same thing in
Running it is easy. I just type
hfzf, pick the history item from the list, and then hit Enter to run it.
fish already has
cdh for “change to a recently-visited directory”, so what about
cdf for “fuzzy cd”?
function cdf set -l whither if command -sq fd set whither (fd --type d | fzf) else set whither (find . -type d | fzf) end test -n "$whither"; and cd "$whither" end
After declaring a local variable, this tests to see if fd is installed.
fd is a Rust-based reimplementation of
find. Most importantly, it ignores hidden files by default, so it won’t waste time and disk reads by trawling through
.git/objects/ directories looking for directories to change to.
I don’t have
fd installed on all my machines, so I have a fallback case that uses
The final interesting line tests to make sure
$whither actually has something in it before changing directories. If I quit out of fzf without picking anything, I don’t want to run
cd with no arguments. That’ll just drag me back to my home directory, which likely isn’t what I want.
Two functions in two files gives me most of what I want out of
fzf without deeply integrating it into my shell configuration in weird ways. If you dislike it when your utilities get their hooks into your shell, maybe you’ll find all this useful.