Eric Frederickson

E

gd: goto directory

A cd replacement using fuzzy search

(Note: knowledge of shell and command-line basics is a prerequisite to understanding this article.)

Introduction

Navigating to deeply nested directories (also called “folders”) is one of the major inconveniences in personal computing; one of those things that’s annoying in both command-line and GUI workflows, and that seems to pop up as a focus-disruptor at annoying times in the midst of other tasks. I wrote gd to make switching to deep directories (and between distant directories) easy, and it’s done a wonderful job of that task since I authored the script around 2 years ago.

gd stands for “goto directory”; the name is a play on cd (“change directory”), the classic directory-navigation command that gd is implemented on top of.

Mechanics

gd is built on the idea of fuzzy-finding, a term for a kind of approximate (i.e. “fuzzy”) text-matching where a search query can match results that are somehow similar to the query, instead of just results that mirror the query exactly. What “similar” means depends on the fuzzy-matching algorithm, but in the case of gd, which uses the superb fzf command-line fuzzy-finder as its UI, it means that one or more fragments of the query text are found within the candidate in any order. For instance, the phrase “fuzzy find” would fuzzy-match the phrase “fizz buzz”, because the “f” and “zz” fragments from “fuzzy” appear in “fizz”, the “fi” fragment from “find” appears in “fizz”, the “uzz” fragment from “fuzzy” appears in “buzz”, and so on.


"fuzzy find"
---------------------
"f u z z y   f i n d"
 | | | |     | |
"f"| "zz"   "fi"
 | |  |		 |
 | "uzz"	 |
 |   |  _____|
 |   |__|______
 |	   	|	   |
"fi"_____|   "uzz"
  |            |
"f i z z   b u z z"
--------------------
"fizz buzz"

The words “apple” and “box” would not fuzzy-match each other, because there are no such resonances between them.

The idea of gd is to use this fuzzy-matching strategy to search through a list of your commonly-visited directories. This reduces the amount of typing that you need to do to specify your desired directory, because as soon as you type out enough of it to distinguish it from the other directories in the list, it’ll float to the top of the pile as one of the best matches. Courtesy of fzf, the top matches are presented to you as you type, meaning that you can jump to your destination as soon as it’s available.

The directory list (called an “index” in the source code) is kept in a hidden file in the user’s home directory, called .gd_idx. This file is generated by a recursive directory listing (ls -R) command, where the top-level directories are passed in read from a list of preferred directories that the user keeps in their copy of the script’s source code (the list can be left empty if the user wants to include all of their directories in the index, though this may result in a noticeable slow-down on some systems). Invoke the program with the -u option to regenerate the directory listing (e.g. gd -u), something I’d recommend making a cron job for if you use gd a lot.

With the design now fully explained, here’s the full source code of gd:


#!/usr/bin/env bash

# gd: goto directory

# dependencies: fzf

# Note: for this command to work properly, you must prepend it with "."
#       the user is recommended to add the alias gd=". gd" to their
#       shell rc file

[ -f ~/.gd_idx ] || set -- -u

case "$1" in
    -u)
		###
		# Replace the following example arguments to `ls -R` with
		# the subdirectories of your home directory that you want
		# `gd` to include. Make sure to include "\" characters at
		# the end of each line.
		###
		cd; ls -R \
		   downloads desktop .config .emacs.d \
		   scripts foo bar baz \
		    | grep :$ | sed 's/:$//' > .gd_idx
		echo 'index updated'
		cd "$OLDPWD"  ;;
	*)
		cd ~/"$(fzf < ~/.gd_idx)" ;;
esac

Installation

You can install gd from source via the git repository. The following shell commands run through an installation:


git clone https://github.com/bartlebooth-factorial/gd
cd gd
mkdir ~/scripts
cp gd.sh ~/scripts/gd
chmod +x ~/scripts/gd
# Assuming your shell is zsh:
echo 'export PATH=$PATH:$HOME/scripts' >> ~/.zshrc
echo "alias gd='. gd'" >> ~/.zshrc
source ~/.zshrc

(If you’re using a shell other than zsh, then just replace ~/.zshrc with your shell’s rc file, for instance ~/.bashrc if you’re using bash.)

Now just edit ~/scripts/gd to write in the list of directories that you want to be included in the index, and you’re ready to go!


email icon github icon