2019-06-06
shell
fzf
Navigate through filesystem quickly with fzf
Navigating to the right directory is one of the most unnecessarily
time-consuming tasks while working in the shell. Have you ever found yourself
typing long paths by hand and repeatedly pressing Tab because you forgot the
name of some directory? Personally, I use a couple of tricks to avoid typing
paths. Here’s the most important one.
The goal
Let’s say I want to work on the post that you’re currently reading. Normally, I
would have to type something like this into my terminal:
$ cd ~/projects/website/
$ cd _posts
$ vim navigate-filesystem-fzf.md
That’s quite a bit of typing! And of course, if I misspell anything, I have to
try again. On the other hand, using some tricks, I can get to the right file
in just a few keystrokes and I don’t even have to hit exactly the right keys.
It looks like this:

Of course, the animation is slowed down a great deal. In my normal workflow, it
takes less than 3 seconds. There are a couple of different tricks here, but the
most important one is using the program called fzf. Let’s have a closer
look.
fzf: a quick introduction
The program you see in action in the above screencast is
fzf, which is an abbreviation for “fuzzy
finder”. It basically serves one purpose: choosing an item from a list by
typing parts of its name. The “item” and the “list” can be whatever, which
opens nearly endless possibilities for our creativity.
If you run fzf without any arguments, it serves as a file chooser: it
recursively searches all subdirectories of the current directory for files and
lets you choose one file. It is worth noting that the recursive search is
blazingly fast.
However, the real greatness of fzf comes from its flexibility: if you pass
something on standard input, it is interpreted as a list, one item per line.
Then, it lets you pick an item from this list, instead of a file. The chosen
line is printed on standard output. Try the following:
$ echo -e "apples\nbananas\ngrapes\noranges\npears\npineapple" | fzf
At first, you see the complete list. You can simply navigate it with either
Emacs-style (Ctrl-N/P) or Vi-style (Ctrl-J/K) hotkeys. If you start typing,
the choice is narrowed to the entries matching what you’ve typed. The matching
is - as the program’s name says - “fuzzy”, i.e. not exact. Even if you type
non-consecutive letters, like rg, it will still match oranges. It is also
tolerant for slight misspellings: apl still selects apples and pineapple.
Navigating through the filesystem
In order to use fzf for navigating, I want to call it in a loop. Every time,
it should list the contents of the current directory and let me choose one item
from this list. If it is a directory, it should go to this directory and repeat
the same. If I’ve chosen a file, it should open the file and terminate. To
achieve this behavior, I have written the following function:
fuzzy_open() {
FILENAME=""
while FILENAME=$(cat <(find . -maxdepth 1) <(cat "$HOME/.bookmarks") \
| fzf --height=10); do
if [ -d "$FILENAME" ]; then
cd "$FILENAME";
else
break
fi
done
if [ -z "$FILENAME" ]; then
return 1;
fi
~/.scripts/open "$FILENAME"
}
At its heart lies the following line, which decides which input is piped to
fzf:
cat <(find . -maxdepth 1) <(cat "$HOME/.bookmarks")
It is a concatenation of two different sources. find . -maxdepth 1 lists the
contents of the current directory. In the second part, I additionally pass
entries from a file called .bookmarks in my home directory.
The bookmark file is actually a very important element of the whole thing. It
stores paths that I use all the time, like the ones associated with the
projects that I’m currently working on. For example, ~/projects/website,
which contains my website sources, is listed in the bookmarks, as is
~/Documents/bibliography, where I store all scientific papers.
Every time I find myself in a directory where I’m probably going to return, I
simply run:
And I have this directory available in a few keystrokes from anywhere. Note
that it’s important not to type a single > or bye bye all your bookmarks.
It’s probably better to create an alias for this command.
Turning back to fuzzy_open(), once the file name is chosen, there are three
possible cases:
- It is a name of the directory - change to this directory and repeat.
- It is empty - exit with exit code
1. Note that we still end up in the
directory we’ve chosen in the previous iteration.
- It is a name of a file - open this file. I’ll leave my generic file opening
script (
~/.scripts/open) for another post, but you can guess what it does.
Finally, in order to be able to call fuzzy_open quickly, I set up a
convenient alias:
That’s it! I hope you’ve learned something useful and the next time you need to
get to the right directory in the shell, you’ll save yourself some time,
keystrokes and nerves.
|