sd is a tool for running scripts in your script directory.

$ tree ~/sd
/Users/ian/sd
├── blog
│   ├── edit
│   ├── preview
│   └── publish
├── book
│   ├── open
│   ├── words
│   └── typeset
├── nix
│   ├── diff
│   ├── info
│   ├── install
│   ├── shell
│   └── sync
└── video
    └── fix

Directories become command groups; executables become subcommands. Instead of a flat ~/bin, you have a cozy nest for your scripts. And you can type:

$ sd blog publish --deploy

To run:

$ ~/sd/blog/publish --deploy

Which, okay, sure. That’s not that exciting.

The exciting thing is that sd supports fancy autocompletion. Take a look:

$ sd nix <TAB>
diff     -- prints what will happen if you run sync
info     -- <package> prints package description
install  -- <package> use --latest to install from nixpkgs-unstable
shell    -- add gcroots for shell.nix
sync     -- make user environment match ~/dotfiles/user.nix

sd pulls those descriptions from comments in the scripts themselves:

$ sd nix info --cat
#!/usr/bin/env bash

# <package> prints package description

set -euo pipefail

nix-env -qaA "nixpkgs.$1" --json \
| jq -r '.[] | .name + " " + .meta.description,
         "",
         (.meta.longDescription | rtrimstr("\n"))'

So they’re very easy to write.

sd also makes it easy to create new scripts:

$ sd blog laud --new 'echo "good blog ian"'

$ sd blog laud
good blog ian

So you can easily stash oneliners, and you don’t have to remember to chmod +x. (You can also call --new without providing an initial script, to go straight into your text editor.)

You can also modify the template for new scripts, on a per-directory basis. Although new scripts default to a fairly reasonable bash:

$ sd blog laud --edit
 1 #!/usr/bin/env bash
 2
 3 set -euo pipefail
 4
 5 echo 'good blog ian'
~
~
"~/sd/blog/laud" 5L, 61B

who cares

Take a peek at your ~/bin. How many scripts there have names like process or run or deploy? How many of them have you forgotten ever writing?

If the answer is “none of them,” sd might not be for you. It sounds like you’ve got this figured out already. Good work.

But sd is for script hoarders, who want to rage against the chaos of their ~/bin. And sd is for people who spend a lot of time combing through ctrl-r, looking for that oneliner that they never thought to make into a script, because it was too much of a hassle and who knows if they’ll ever run it again.

Give it a name! Give it a meaningful command path. Hoard more. The cozy nest is never crowded.

sd: a history

A year ago I published my first post about sd.

At the time, sd was a bundle of assorted shell scripts that I cobbled together one afternoon in 2018. I only “publicized” it all because I wanted to refer to it from another blog post I was writing – I didn’t even really launch it in any usable form; I just described the idea and linked to my dotfiles. I didn’t even bother giving it its own repo!

But even in that barely-baked form, the project got a small amount of traction. People reached out to me about it – people who actually wanted to use sd. Which gave me a very good excuse to buckle down and rewrite it, and I’m happy to announce that sd recently reached “1.0” status. It is officially a thing that exists that you can use, instead of “an idea I had that you can implement.”

And the thing that exists is much better than the thing I described a year ago. In a few ways:

First off, autocomplete is much faster than the original version. It used to run find, but now it uses some fancy shell globbing instead. There used to be the tinest delay after pressing tab, but now autocomplete feels instantenous. Which is how autocomplete should feel.

Second off, sd now works as a shell function in addition to working as a standalone executable – mostly because it’s very easy to install it and set up completion and everything with a shell plugin manager. sd is not officially packaged anywhere that I’m aware of, so this is a pretty convenient way to install it, if you already use a zsh plugin manager.

But the biggest change is an ergonomic one.

The original sd didn’t support any flags: the only thing it could do was run scripts in the ~/sd directory. It came with some “built-in” commands, like sd edit, but those were just scripts like any other: ~/sd/edit. This was elegant, I suppose, but I really just did it this way because it was easy to implement and I was lazy.

There were a lot of downsides to that approach. It made installation nontrivial, because you had to bootstrap your ~/sd directory with these “built-in” commands. There was no code sharing between these scripts – except by assuming that certain scripts existed in ~/sd, and failing horribly if they didn’t. There was no autocomplete for the built-in commands, so if you ran sd edit some command you had to type some command yourself. And when you were autocompleting a command at the top-level, you had to tab past all of these “built-in” scripts before you got to the command group that you wanted.

The right answer was clearly to support sd some command --edit, but I hesitated adding argument parsing for a while, because I didn’t like the idea of hijacking arguments. What if you wrote a script that expected a --help?

I got over this, eventually. Mostly because I have never written a script that used --help in my ~/sd. Writing a script that parsed its arguments would imply that I was writing something other than a quick bash script, which would imply that sd might not be the best place for it.

But I added an escape hatch just in case: you can run sd some command --help --really, and sd will execute ~/sd/some/command --help. (And yes, you can also run sd some command --help --really --really if your script expects a --really flag. Have you ever written a script that expects a --really flag? I want to hear about that. That sounds like a fun script.)

the future of sd

I dunno, it’s just a cute little program? It doesn’t have much of a future. Check it out. Nourish my fragile ego by lavishing stars upon the repo.