June 10th, 2024 12:24 UTC · 1 month ago

macOSWindowsLinuxRustShell

withd

A simple command to run another command in a different directory

withd is simple:

$ pwd
/home/gavin
$ withd /usr/share pwd
/usr/share

The first argument is the directory to cd to, the rest are the command and its arguments.

Why is this useful?

Many commands – such as git, npm, and cargo – require you to run them from a specific directory. This can be done by cding into the directory and back out again:

cd /path/to/repo
git status
cd -

This is cumbersome. An alternative is to use a subshell to isolate the change:

( cd /path/to/repo && git status )

This can be confusing or problematic when also trying to work with shell variables in a script, for example, since the subshell cannot propagate changes to the parent shell. It’s also easy to forget.

Then there’s CDPATH (in Bash; this feature might be named something else in the shell you’re using, or not exist at all). If this is set, cd’s behaviour changes, meaning cd foo might put you in a directory other than the one you intended. This also happens in scripts. I’ve seen this be a source of confusion – and a disruptive and difficult to diagnose bug.

withd does not have these problems. It’s simple and predictable.

Other features

There are a few:

  • When no command is given, spawn a new shell (using SHELL from the environment),
  • -c/--create will cause it to create the directory and all its parents before cding to it,
  • -t/--temporary will create a temporary directory (meaning it will delete it after the command has completed).

Especially for -t/--temporary, read the --help text to understand how the directory argument is interpreted. I think it’s neat.

Example of -t/--temporary

$ withd -t ".foo.XXXXXX" bash
$ pwd
/private/var/folders/…/T/foo.MPNdDN
$ wget … # download, process, etc.
$ mv results.txt ~/

Why did I make this?

It’s a valid question, since this is a simple command that anyone can live without quite easily.

I did it:

  • to learn about UNIX signal handling and process groups,
  • to write a cross-platform utility in Rust,
  • to get more experience with GitHub Actions,
  • to have another utility to distribute in a HomeBrew tap and thus to learn about how to create one, and gain more experience writing HomeBrew formulae.

In short: I did it to learn.

But since I have it installed on my computers now – thanks to Cargo and HomeBrew – there’s a good chance that I will use it, refine it, add features, and so on.

If you have any ideas for features to add, or you want to report a bug, please create a new issue on GitHub.

Install withd

With cargo:

If you’ve done any Rust development, you might have cargo already1:

$ cargo install withd

This should work on Linux, macOS, and Windows.

With HomeBrew on macOS:

If you’re on macOS, you can use HomeBrew:

$ brew install allenap/utils/withd

This will also install completions for Bash, though you’ll need to install the bash-completion@2 formula to make use of those.

Download directly:

The GitHub releases page has prebuilt binaries for macOS (Universal, plus Apple Silicon and Intel) and Linux (ARM64 and x86_64/AMD64) for all releases. There are no Windows builds yet.


1

Optionally, install the Rust development toolchain (which includes cargo).