~/sd is my script directory. It looks like this:

$ tree ~/sd
/Users/ian/sd
├── blog
│   ├── preview
│   └── publish
├── book
│   ├── open
│   ├── pdf
│   ├── progress
│   └── typeset
├── cat
├── dim
│   └── visualize
├── edit
├── git
│   └── ugh
├── help
├── new
├── nix
│   ├── diff
│   ├── info
│   └── sync
├── tmux
│   ├── init
│   └── restore
└── which

sd is a command on my PATH. It dispatches its arguments to my script directory.

$ sd blog publish

Will run the script ~/sd/blog/publish.

This sounds like a dumb way to save myself having to type a few characters. Which it is. But it’s also slightly more than that:

sd has nice tab completion. That’s the only real difference between sd and ~/sd: those little descriptions of what the commands do. In case you skipped the video, it looks something like this:

$ sd nix <TAB>
diff  -- prints what will happen if you run sync
info  -- <package> prints package description
sync  -- make user environment match ~/dotfiles/user.nix

It’s not much. But it’s nice.

Those descriptions come from comments in the command files themselves, or from separate command.help files (in case there is a lot to say, or the command is not a shell script).

For example, in the demo above, I run sd help nix sync, which ends up running cat ~/sd/nix/sync.help. The first line is all that ends up in the completion menu, but the rest of the text is there in case I forget how a command works.

a boring story about sd

I wrote sd one afternoon when I couldn’t find a script I wanted in my ~/bin. I knew it was there – and I knew what it did – but I couldn’t remember what I’d named it.

I had close to a hundred different scripts in ~/bin, all with names that made perfect sense when I added them: visualize, transplant, deploy, etc.

I decided enough was enough, so I added a little structure, and sd was born.

It’s much easier to remember what commands do when they’re namespaced like this. And it’s much easier to remember how to use them when you write their usage down. I’ve used sd ever since, and I don’t think I could go back to a flat ~/bin.

I only have a few sd commands on my laptop, because I don’t find myself writing a lot of complicated scripts at home. But sd was written for work, where I had dozens of helpers all neatly categorized by project or tool or domain. It was good.

do you want to use sd?

It’s not really a thing. It’s more of an idea. You can implement it yourself in one sitting, if you want.

But of course you don’t want to figure out how to write your own shell completion. Shell completion is some arcane nonsense. You want me to have already done it. But here’s the thing: I only wrote zsh completions because I only ever use zsh interactively.

I’ve sometimes thought about rewriting sd as something other than a pile of shell and publishing it and publicizing it and adding bash support and what have you. But you know what? I probably won’t ever do that. But maybe someone else will, if they like the idea enough.1

You’re welcome to use my implementation, though, if you use zsh. It works on macOS and Linux. It lives in my dotfiles repo because I want it on every machine I use and I’m very lazy. See also bin/completions/_sd and sd-common/. sd-common are the scripts like which and cat that must exist in ~/sd or something will probably break (I install them as symlinks to my dotfiles repo).

If you want completions to work, you have to add this in your .zshrc file:

fpath=(~/bin/completions $fpath)

Where ~/bin/completions is wherever you end up putting the _sd script.

One last note: sd only looks for executables, so you can add non-executable helpers that you can source from multiple scripts in the same directory. When sd invokes a command, $0 is the absolute path to the command, so you can use dirname "$0" to get at your helpers. Like you’d expect.

how has sd affected you

Being able to type sd new foo command instead of vim ~/bin/command, typing the shebang, trying to run it, realizing I forgot to make it executable, running it again… it’s made my life slightly better. And because it’s so easy to save snippets for later, I find that I’m much more likely to do it – and a lot less likely to find myself combing through ctrl-r a few months later.

Otherwise it’s, you know, it’s not lifechanging. It won’t help you to appreciate the smaller beauties all around you. You’ll still wake up feeling groggy. Lately I’ve been using it to make user-friendly wrappers around nix-env as part of my series on How to Learn Nix, which has been nice.

what would you have done differently

  • Make sd new support a -- escape argument and treat the rest of the arguments as the script, for easily stashing oneliners: sd new foo bar -- echo foobar.
  • Figure out how to share logic between sd and the autocomplete script _sd. Lotta copy-paste right now.
  • Some nice way to generate command completion for command arguments, instead of just for commands themselves. Especially for commands like sd cat and sd edit which are much harder to use than they should be.
  • bash support I guess
  • In the case that $0 is not sd, treat it as part of the script path, so you can make very lightweight command wrappers that work without having to type sd. Basically ln -s $(which sd) ~/bin/some-command, and then you can type some-command foo instead of sd some-command foo.
  • Support sd foo bar --help instead of sd help foo bar, because it’s slightly annoying to reposition your cursor. But that’s kind of a whole can of worms.
    • yes the real solution here is to not use shell and find some nice alternative that actually understands how command line arguments work but there really just isn’t you know it’s 2021 and biden is president and bash is still the only thing we have don’t talk to me about oil shell i know all about oil shell

But sd is like a 95/20 situation. It’s pretty good even in its neglected, janky state. As long as you’re using zsh.


  1. If you do this, please tell me, so I can switch to your implementation. ↩︎