{"id":13503200,"url":"https://github.com/ianthehenry/sd","last_synced_at":"2026-01-25T04:07:37.498Z","repository":{"id":41822178,"uuid":"432085144","full_name":"ianthehenry/sd","owner":"ianthehenry","description":"a cozy nest for your scripts","archived":false,"fork":false,"pushed_at":"2024-03-19T21:53:16.000Z","size":29,"stargazers_count":733,"open_issues_count":3,"forks_count":16,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-29T10:13:05.307Z","etag":null,"topics":["bash","organization","scripting","shell","zsh"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ianthehenry.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-26T07:06:03.000Z","updated_at":"2025-05-17T16:23:37.000Z","dependencies_parsed_at":"2024-01-18T02:37:16.251Z","dependency_job_id":"7a01e252-340e-4af9-abce-dc1c063cf6a4","html_url":"https://github.com/ianthehenry/sd","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/ianthehenry/sd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianthehenry%2Fsd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianthehenry%2Fsd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianthehenry%2Fsd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianthehenry%2Fsd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ianthehenry","download_url":"https://codeload.github.com/ianthehenry/sd/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ianthehenry%2Fsd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28742983,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T02:46:29.005Z","status":"ssl_error","status_checked_at":"2026-01-25T02:44:29.968Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","organization","scripting","shell","zsh"],"created_at":"2024-07-31T22:02:41.592Z","updated_at":"2026-01-25T04:07:37.492Z","avatar_url":"https://github.com/ianthehenry.png","language":"Shell","funding_links":[],"categories":["Shell","bash"],"sub_categories":[],"readme":"# `sd`: my `s`cript `d`irectory\n\n- [Usage](#usage)\n- [Installation](#installation)\n- [Changelog](#changelog)\n\nHas this ever happened to you?\n\n*Black and white video plays of someone struggling to find a shell script they wrote a year ago and stuffed into their `~/bin` without giving it a very meaningful name.*\n\nDon't you hate it when you can't find the scripts you need, when you need it? Well now there's a better way!\n\n*Color fills the screen. Someone holds `sd` up to the camera, and flashes a winning smile. They've found the script on their first try.*\n\nIntroducing `sd`, the script directory for the refined, sophisticated professional. Simply organize your scripts in a logical directory hierarchy, and let `sd` take care of the rest!\n\n    $ tree ~/sd\n    /Users/ian/sd\n    ├── blog\n    │   ├── edit\n    │   ├── preview\n    │   └── publish\n    ├── nix\n    │   ├── diff\n    │   ├── info\n    │   └── sync\n    └── tmux\n        └── init\n\nAnd now instead of typing `~/sd/blog/publish`, you can just type `sd blog publish` -- a savings of nearly three whole characters!\n\nBut wait! There's more! You'll wonder how you ever lived without `sd`'s best-in-class tab completion:\n\n    $ sd nix \u003cTAB\u003e\n    diff  -- prints what will happen if you run sync\n    info  -- \u003cpackage\u003e prints package description\n    sync  -- make user environment match ~/dotfiles/user.nix\n\nSimply write a one-line comment in your script, and you'll never be left scratching your head over how you were supposed to call it!\n\n# uhh\n\nHi okay sorry. [Take a look at this blog post for a real introduction and a fancy asciinema demo of how it works.](https://ianthehenry.com/posts/sd-my-script-directory/)\n\n# Usage\n\nThe default behavior for `sd foo bar` is:\n\n- If `~/sd/foo` is an executable file, execute `~/sd/foo bar`.\n- If `~/sd/foo/bar` is an executable file, execute it with no arguments.\n- If `~/sd/foo/bar` is a directory, this is the same is `sd foo bar --help` (it prints usage information).\n- If `~/sd/foo/bar` is a non-executable regular file, this is the same is `sd foo bar --cat` (it just prints the file out).\n\nThere are some special flags that are significant to `sd`. If you supply any one of these arguments, `sd` will not invoke your script, and will do something fancier instead.\n\n    $ sd foo bar --help\n    $ sd foo bar --new\n    $ sd foo bar --edit\n    $ sd foo bar --cat\n    $ sd foo bar --which\n    $ sd foo bar --really\n\n## `--help`\n\nPrint the contents of a help file, or generate a help file from comments in a script.\n\nFor executables, `sd` looks for a file with the same name but the `.help` extension. For example, `sd nix diff --help` would look for a file called `~/sd/nix/diff.help`, and print it out.\n\nFor directories, `sd` looks for a file that's just called `help`. So `sd nix --help` would look for `~/sd/nix/help`.\n\nIf there is no help file for an executable, `sd` will print the first comment block in the file instead. `sd` currently only recognizes bash-style `#` comments.\n\nFor example:\n\n    $ cat ~/sd/nix/sync\n\n```bash\n#!/usr/bin/env bash\n\n# make user environment match ~/dotfiles/user.nix\n#\n# This will remove any packages you've installed with nix-env\n# but have not added to user.nix. To see exactly what this\n# will do, run:\n#\n#     sd nix diff\n\nset -euo pipefail\n\n# maybe this should be configurable\nnix-env -irf ~/dotfiles/user.nix\n```\n\nThat will produce the following help output (note that it only prints the first contiguous comment block):\n\n```\n$ sd nix sync --help\nmake user environment match ~/dotfiles/user.nix\n\nThis will remove any packages you've installed with nix-env\nbut have not added to user.nix. To see exactly what this\nwill do, run:\n\n    sd nix diff\n```\n\nIf you run `--help` for a directory, it will also print out a command listing after the help text:\n\n```\n$ sd nix --help\nnix commands\n\ninstall    -- \u003cpackage\u003e use --latest to install from nixpkgs-unstable\nshell      -- add gcroots for shell.nix\ndiff       -- prints what will happen if you run sync\ninfo       -- \u003cpackage\u003e prints package description\nsync       -- make user environment match ~/dotfiles/user.nix\n```\n\n## `--new`\n\nEverything to the left of `--new` is considered a command path, and everything to the right of `--new` is considered the command body. For example:\n\n    $ sd foo bar --new echo hi\n\nWill try to create a new command at `~/sd/foo/bar` with an initial contents of `echo hi`.\n\nActually, to be more precise, it will create this script:\n\n    $ cat ~/sd/foo/bar\n\n```bash\n#!/usr/bin/env bash\n\nset -euo pipefail\n\necho hi\n```\n\nAssuming the default template.\n\nIf no body is supplied after `--new`, `sd` will open the script for editing.\n\n### custom script templates\n\nYou can customize the template used by `--new` by creating a file called `template`, either in `~/sd` or one of its subdirectories.\n\n`sd` will try to find a template by walking recursively up the directory hierarchy. For example, if you run:\n\n```\n$ sd foo bar baz --new\n```\n\n`sd` will try to find a template at `~/sd/foo/bar/template` first, then fall back to `~/sd/foo/template`, then `~/sd/template`. If it doesn't find any template file, it will use the default bash template shown above.\n\n(There is no need to make your `template` executable -- `sd` will take care of that for you.)\n\nWhen `--new` is used to create an inline script, that script will always go at the *end* of your template file. There is currently no way to customize this.\n\n## `--cat`\n\nPrints the contents of the script. See `SD_CAT` below.\n\n## `--edit`\n\nOpen the script in an editor. See `SD_EDITOR` below.\n\n## `--which`\n\nPrints the path of the script.\n\n## `--really`\n\nSuppress special handling of all of the other special flags. This allows you to pass `--help` or `--new` as arguments to your actual script, instead of being interpreted by `sd`. For example:\n\n    $ sd foo bar --help --really\n\nWill invoke:\n\n    ~/sd/foo/bar --help\n\nThe first occurrence of the `--really` argument will be removed from the arguments passed to the script, so if you need to pass a literal `--really`, you must pass it twice to `sd`. For example:\n\n    $ sd foo bar --help --really --really\n\nWill invoke:\n\n    $ ~/sd/foo/bar --help --really\n\n# Context\n\nWhen a script is invoked, `sd` will set the environment variable `SD` to the directory that the script was found in -- in other words, `$(dirname \"$0\")`.\n\nThis makes it slightly more convenient to refer to shared helper files or other scripts relative to the executing script.\n\n# Options\n\n`sd` respects some environment variables:\n\n- `SD_ROOT`: location of the script directory. Defaults to `$HOME/sd`.\n- `SD_EDITOR`: used by `sd foo --edit` and `sd foo --new`. Defaults to `$VISUAL`, then `$EDITOR`, then finally falls back to `vi` if neither of those are set.\n- `SD_CAT`: program used when printing files, in case you want to use something like [`bat`](https://github.com/sharkdp/bat). Defaults to `cat`.\n\n# Installation\n\nThere are two ways to use `sd`:\n\n1. source the `sd` file, which will define the shell function `sd`\n2. treat `sd` as a regular executable and put it somewhere on your `PATH`\n\nI prefer to use `sd` as a regular executable, but the function approach is more convenient if you already use a shell plugin manager that knows how to set up `fpath` automatically.\n\nNote that you cannot invoke \"recursive `sd`\" (that is, write scripts that themselves invoke `sd`) if you use the function approach, unless you're writing zsh scripts. But you probably shouldn't.\n\n## Installation as a regular script\n\n## Using Nix\n\nAs far as I know, [Nix](https://search.nixos.org/packages?channel=unstable\u0026query=script-directory) is the only package manager with `sd` pre-packaged (as `nixpkgs.script-directory`).\n\n`sd` is also [available in home manager](https://github.com/nix-community/home-manager/blob/master/modules/programs/script-directory.nix). You can install it by adding something like this to your `~/.config/home-manager/home.nix`:\n\n```nix\n{...}: {\n  home.programs.script-directory = {\n    script-directory = {\n      enable = true;\n      settings = {\n        # SD_ROOT = \"${config.home.homeDirectory}/custom-script-directory\";\n        # SD_EDITOR = \"vim\";\n        # SD_CAT = \"bat\";\n      };\n    };\n  };\n}\n```\n\n## Without a package manager\n\n1. Put the `sd` script somewhere on your `PATH`.\n2. Put the `_sd` completion script somewhere on your `fpath`.\n\nI like to symlink `sd` to `~/bin`, which is already on my path. If you've cloned this repo to `~/src/sd`, you can do that by running something like:\n\n    $ ln -s ~/src/sd/sd ~/bin/sd\n\nThere isn't really a standard place in your home directory to put completion scripts, so unless you've made your own, you'll probably want to add your clone directly to your `fpath`. You should add that to your `.zshrc` file before the line where you call `compinit`. It should look something like this:\n\n    # ~/.zshrc\n\n    fpath=(~/src/sd $fpath)\n    autoload -U compinit\n    compinit\n\nIf you use a zsh framework like [`oh-my-zsh`](https://github.com/ohmyzsh/ohmyzsh), it probably calls `compinit` for you. In that case, just set your `fpath` before you source the framework's initialization script.\n\nNote that changes you make to your `~/.zshrc` will only take effect for *future* shells you create, so to start enjoying `sd` immediately you'll also want to run these commands in your existing shells:\n\n    $ fpath=(~/src/sd $fpath)\n    $ compinit\n\n## As a shell function\n\nYou can just source `sd` in your `.zshrc` and set up completion manually (as described [above](#installation-as-a-regular-script)), but `sd` is designed to be compatible with shell plugin managers.\n\n### [Antigen](https://github.com/zsh-users/antigen)\n\nAdd this line to your `.zshrc`:\n\n```shell\nantigen bundle ianthehenry/sd\n```\n\n### [oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh):\n\nClone this repo into your custom plugins directory:\n\n```\n$ git clone https://github.com/ianthehenry/sd.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/sd\n```\n\nAnd then add it to the plugins list in your `~/.zshrc` before you source `oh-my-zsh`:\n\n```\nplugins+=(sd)\nsource \"$ZSH/oh-my-zsh.sh\"\n```\n\n# bash/fish autocompletion support\n\nPatrick Jackson contributed [an unofficial fish completion script](https://gist.github.com/patricksjackson/5065e4a9d8e825dafc7824112f17a5e6), which should be usable with some modification (as written it does not respect `SD_ROOT`, but it should act as a very good starting point if you use fish).\n\nBash doesn't support the fancy completion-with-description feature that is sort of the whole point of `sd`, but there are apparently ways to hack something similar.\n\n\n# Changelog\n\n## v1.1.0 2022-10-30\n\n- fix a bug where `--help` would print every comment in the script\n\n## v1.0.1 2022-04-17\n\n- better error message if `~/sd` does not exist\n- better error message if `~/sd` exists but is not a directory\n\n## v1.0.0 2022-02-27\n\n`sd` is now released under the MIT license. There are no functional changes from the pre-1.0 releases.\n\n## v0.3.0 2022-02-26\n\n- scripts now run with the `SD` environment variable set to the directory they were found in\n- autocompletion now completes arguments to commands instead of just commands\n    - only completes positional file arguments and the built-in flags (like `--help`)\n- `sd` now only forks a subshell when invoked as a function\n- `sd` now `exec`s scripts instead of `fork`+`exec`\n    - this fixes the rare issue where a long-running script could throw errors when it finished if you were editing the `sd` executable itself while the script was running, because `bash` was trying to execute the \"rest\" of the file and apparently doing so by byte index or something (??)\n    - this only affects me\n\n## v0.2.0 2022-02-24\n\n- added per-directory `template` files, to override the `bash` default\n\n## v0.1.1 2021-12-05\n\n- fix a bug where `--new` wouldn't work unless provided with an initial script\n\n## v0.1.0 2021-12-01\n\n- added `--really`\n- `dir.help` files are now `dir/help` files\n\nYou used to be able to provide a description for a directory called `foo/` by writing a file called `foo.help` as a sibling of that directory.\n\nNow directory help summaries are expected in `foo/help` instead.\n\nThis has the sort-of nice effect that `sd foo help` is sometimes similar to `sd foo --help`. Except that the latter also prints out subcommands.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fianthehenry%2Fsd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fianthehenry%2Fsd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fianthehenry%2Fsd/lists"}