sd is a tool for running scripts in your
$ 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
$ ~/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
$ 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
Take a peek at your
~/bin. How many scripts there have names like
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.
sd is for script hoarders, who want to rage against the chaos of their
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
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.
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.
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
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
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.