Switching profiles with Fish shell

2 minute read

Context

How can I automatically switch profiles in my fish shell when I enter specific directories (e.g. ~/work, ~/personal, …)?. This is the idea that came to my mind yesterday evening, so I set out to find a way to implement it… While I’m aware that tools like direnv would solve part of my problem, I wanted to refresh/improve my fi(sh) scripting skills and I didn’t want to be limited to manipulating environment variables.

Problem

When I enter one of my marked directories (e.g. ~/work), I want to modify my fish shell environment, for example by appending a directory to $fish_user_paths, loading a ssh identity, and so on. Such changes are persisted for the duration of the session, unless I switch to another marked directory (e.g. ~/personal).

Solution

First attempt: redefine cd

I originally set out to define a custom cd function that would check the value of $PWD and switch profiles accordingly (when applicable). I got to a working solution but eventually figured out that cd - had stopped working; this turned out to be caused by the fact that fish itself provides a cd function that wraps the builtin cd command to enable that behaviour (by keeping the history of recently visited directories); after a few unsuccessful attempts at wrapping my own function around that one, I set out to find another solution.

Second attempt: listen for changes to $PWD

Digging through the fish documentation, I found out about one of its amazing features: enabling the definition of functions that react to events or changes to variables' values (--on-event and --on-variable, respectively; see man function for more details). The latter looked (and proved to be) perfect for my use case, since it would allow me to react to changes to $PWD! After several iterations, I ended up with:

  • functions manipulating ssh identities (adding and removing them)
  • functions manipulating $fish_user_paths (adding and removing elements from it)
  • a script, invoked when I enter a given directory, that applies changes to my environment
  • a function (__switchenv.fish) reacting to changes to $PWD and triggering the functions that undo any previous modifications, and eventually executes the above script

The last step, which got me puzzled for 30 good minutes, was to make the switch-env.fish function autoload on every new shell. Since I had never written event handling functions before, I expected it to work by just dropping them in ~/.config/fish/functions/ as usual, but this was not the case; digging in the documentation, I eventually reached the funcsave documentation), which states that

[…] because fish loads functions on-demand, saved functions will not function as event handlers until they are run or sourced otherwise. To activate an event handler for every new shell, add the function to your shell initialization file instead of using funcsave.

Based on this, I added the following instruction to ~/.config/fish/config.fish:

functions -c __switchenv switchenv

This instruction creates a copy of the __scriptenv function named switchenv, forcing fish to load it.

Complete example

A complete example is available in this Github repository.