As a long-time user of Unix-like systems, I prefer to do as much work in the command-line as possible. I store data in plain text whenever appropriate. I edit in vim and take advantage of the pipeline to manipulate the data with powerful tools like awk and grep.
Notes are one such instance where plain text makes sense. All of my notes – which are scratch-pads for ideas, reference material, logs, and whatnot – are kept as individual text files in the directory
~/documents/notes. The entire
~/documents directory is synced between my laptop and my work computer, and of course it is backed up with tarsnap.
When I want to read, edit or create a note, my habit is simply to open the file in vim.
When I want to view a list of my notes, I can just
ls the directory. I pass along the
-r flags. The first flag sorts the files by modification date, newest first. The second flag reverses the order. The result is that the most recently modified files end up at the bottom of the list, nearest the prompt. This allows me to quickly see which notes I have recently created or changed. These notes are generally active – they’re the ones I’m currently doing something with, so they’re the ones I want to see. Using
ls to see which files have been most recently modified is incredibly useful, and a behaviour that I use often enough to have created an alias for it.
The first function,
n(), takes the name of the note as an argument – minus the file extension – and opens it.
1 2 3
I liked the idea. It would allow me to open a note from anywhere in the filesystem without specifying the full path. After changing the editor and the path, I could open the same note as before with far fewer keystrokes.
I needed to make a few changes to the function to increase its flexibility.
First, the extension. Most of my notes have
.txt extensions. Some have a
.gpg extension.1 Some have no extension. I didn’t want to force the
.txt extension in the function.
If I specified a file extension, that extension should be used. If I failed to specify the extension, I wanted the function to open the file as I specified it only if it existed. Otherwise, I wanted it to look for that file with a
.gpg extension and open that if it was found. As a last resort, I wanted it to open the file with a
.txt extension regardless of whether it existed or not. I implemented this behaviour in a separate function,
buildfile(), so that I could take advantage of it wherever I wanted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
I then rewrote the original note function to take advantage of this.
1 2 3 4 5 6 7 8 9 10 11 12 13
Now I can edit the note
~/documents/notes/todo.txt by simply passing along the filename without an extension.
If I want to specify the extension, that will work too.
If I have a note called
~/documents/notes/world-domination.gpg, I can edit that without specifying the extension.
If I have a note called
~/documents/notes/readme, I can edit that and the function will respect the lack of an extension.
Finally, if I want to create a note, I can just specify the name of the note and a file with that name will be created with a
The other change I made was to make the function print a reverse-chronologically sorted list of my notes if it was called with no arguments. This allows me to view my notes by typing a single character.
The original blog post also included a function
ns() for searching notes by their title.
1 2 3
I thought this was a good idea, but I considered the behaviour to be finding a note rather than searching a note. I renamed the function to reflect its behaviour, took advantage of my
ls alias, and made the search case-insensitive. I also modified it so that if no argument was given, it would simply print an ordered list of the notes, just like
1 2 3 4 5 6 7 8
I thought it would be nice to also have a quick way to search within notes. That was accomplished with
ns(), the simplest function of the trio.
I chose to change into the note directory before searching so that the results do not have the full path prefixed to the filename. Thanks to the first function I don’t need to specify the full path of the note to open it, and this makes the output easier to read.
1 2 3
To finish it up, I added completion to zsh so that I can tab-complete filenames when using
If you’re interested in seeing some of the other way that I personalize my working environment, all of my dotfiles are on GitHub. The note functions are in shellrc, which is my shell-agnostic configuration file that I source from both zshrc and bashrc2.