Our man Andrew Peng has a look at shell extension direnv.net and how it can improve your development environment.
direnv is an extension for your shell. It augments existing shells with a new feature that can load and unload environment variables depending on the current directory.
direnv allows you to do fancy things like creating isolated development environments using tools such as Nix but in this blog post we're going to look at a simpler example that involves modifying the PATH environment variable within a particular directory.
Imagine we're working on a Dockerised project and we want to be able to easily run command-line tools (such as our old friend
rubocop in the container environment from our usual shell on the host system.
We might start with something like
$ docker-compose run --rm web bash -c "bundle exec rubocop --version" 0.83.0
We can simplify this a bit by wrapping it in an executable shell script.
#!/usr/bin/env bash docker-compose run --rm web bash -c "bundle exec rubocop $*"
We can save this as
rubocop and run
chmod +x rubocop mkdir bin mv rubocop bin/
to make it executable and move it into a subdirectory.
$ bin/rubocop --version 0.83.0
This is much shorter but we still need to remember where we put the wrapper script whenever we want to run it. It would be nice if we could just run the command
rubocop and have this automatically resolve to the wrapper script.
We could prepend the
bin directory to the PATH environment variable which would make this possible but would also mean that we would end up using the wrapper script whenever we ran the command
rubocop from anywhere, including from outside of this project.
$ cd ~/test-project $ ls bin/ rubocop $ export PATH=$PWD/bin:$PATH $ which rubocop /Users/andrew/test-project/bin/rubocop # success! $ rubocop --version 0.83.0 $ cd ~ $ which rubocop /Users/andrew/test-project/bin/rubocop # :( $ rubocop --version ERROR: Can't find a suitable configuration file in this directory or any parent. Are you in the right directory? Supported filenames: docker-compose.yml, docker-compose.yaml
We can solve this problem quite nicely by only prepending the
bin directory to PATH when we're in the project directory; when we're in the project directory then
rubocop will resolve to the wrapper script but when we're somewhere else then
rubocop will resolve to whatever would be found in PATH normally.
Conveniently, this is exactly what direnv allows us to do.
direnv is able to automatically update PATH whenever we change into or out of the project directory.
Before each prompt, direnv checks for the existence of a .envrc file in the current and parent directories. If the file exists (and is authorized), it is loaded into a bash sub-shell and all exported variables are then captured by direnv and then made available to the current shell.
This might look something like
$ cd ~ $ echo $PATH /Users/andrew/bin:... $ cd ~/test-project direnv: loading ~/test-project/.envrc direnv: export ~PATH $ echo $PATH /Users/andrew/test-project/bin:/Users/andrew/bin:... $ cd ~ direnv: unloading $ echo $PATH /Users/andrew/bin:...
To configure direnv for our use case, we need to create a
.envrc file in the project directory and add the following
PATH_add is a helper function provided by direnv which prepends the supplied directory to PATH when changing into the project directory and removes it when changing out of the project directory.
If we change into the project directory then we should see
$ cd ~/test-project direnv: error /Users/andrew/test-project/.envrc is blocked. Run `direnv allow` to approve its content
direnv will start managing PATH within this directory once we have authorised the
$ direnv allow direnv: loading ~/test-project/.envrc direnv: export ~PATH $ which rubocop /Users/andrew/test-project/bin/rubocop # success! $ rubocop --version 0.83.0 $ cd ~ direnv: unloading # global installation $ which rubocop /Users/andrew/bin/rubocop # phew $ rubocop --version 0.83.0
And we're done!
In practice, my home directory looks something like
$HOME |-- bin | |-- docker | | |-- rubocop | | |-- ... other tools |-- docker-projects | |-- .envrc | |-- project1 | |-- project2 | |-- ... other projects
which allows me to use the same set of wrapper scripts across all Dockerised projects without needing to add any additional configuration for new projects.
How Ruby if statements can help you write better code by Dave Cocks
How do you solve a problem like caching in Progressive Web Apps? by Dave Quilter
Rails 6: Seeing Action Text in... action by Stephen Giles
A Quick Comment on Git Stash by Karen Fielding