6

Let's say I have a project in:

~/working_dir

Whenever I run commands from this directory, I need to have certain environment variables set. So I can export them like so:

export VAR=value

However, there are a lot of these and it gets tedious, plus I forget sometimes and run a command only to have it fail because it's missing the environment variables that give it API keys or something.

Is there a way I can get zsh to remember these environment variables for this directory, so that any time I run any command from that directory it runs with those environment variables set?

7 Answers 7

12

Compared with other tools designed for this, direnv is the best of them.

direnv is an environment switcher for the shell. It knows how to hook into bash, zsh, tcsh, fish shell and elvish to load or unload environment variables depending on the current directory. This allows project-specific environment variables without cluttering the ~/.profile file.

What makes direnv distinct between other similar tools:

  • direnv is written in Go, faster compared with its counterpart written in Python
  • direnv supports unloading environment variables when you quit from the specific dir
  • direnv covers many shells

Similar projects

  • Environment Modules - one of the oldest (in a good way) environment-loading systems
  • autoenv - lightweight; doesn't support unloads; slow written in Python
  • zsh-autoenv - a feature-rich mixture of autoenv and smartcd: enter/leave events, nesting, stashing (Zsh-only).
  • asdf - a pure bash solution that has a plugin system
9

It's possible to do this - here's a screencast, using the Grml ZSH configuration.

Further information:

Edit: This is actually pretty easy to do. Here's the relevant portion of your ~/.zshrc:

function chpwd_profiles() {
    local profile context
    local -i reexecute

    context=":chpwd:profiles:$PWD"
    zstyle -s "$context" profile profile || profile='default'
    zstyle -T "$context" re-execute && reexecute=1 || reexecute=0

    if (( ${+parameters[CHPWD_PROFILE]} == 0 )); then
        typeset -g CHPWD_PROFILE
        local CHPWD_PROFILES_INIT=1
        (( ${+functions[chpwd_profiles_init]} )) && chpwd_profiles_init
    elif [[ $profile != $CHPWD_PROFILE ]]; then
        (( ${+functions[chpwd_leave_profile_$CHPWD_PROFILE]} )) \
            && chpwd_leave_profile_${CHPWD_PROFILE}
    fi  
    if (( reexecute )) || [[ $profile != $CHPWD_PROFILE ]]; then
        (( ${+functions[chpwd_profile_$profile]} )) && chpwd_profile_${profile}
    fi  

    CHPWD_PROFILE="${profile}"
    return 0
}
# Add the chpwd_profiles() function to the list called by chpwd()!
chpwd_functions=( ${chpwd_functions} chpwd_profiles )

Activate the profile for each directory you want:

zstyle ':chpwd:profiles:/path/to/directory(|/|/*)' profile NAME

And don't forget to actually make a profile:

chpwd_profile_NAME() {
    [[ ${profile} == ${CHPWD_PROFILE} ]] && return 1
    print "chpwd(): Switching to profile: $profile"

    export VAR=value
}

Edit #2: This would actually be rather neat to couple with named directories [Stackoverflow.net].

3
  • 7
    There is nothing "easy" about this solution.
    – raine
    Feb 27, 2015 at 20:01
  • @rane agreed! Looks pretty complex to me.
    – Bklyn
    Oct 21, 2016 at 20:41
  • I am not an expert here but at the bottom of the .zshrc I added chpwd_profiles so if I open a new terminal tab it also performs a chpwd check.
    – Colin
    May 2, 2019 at 8:06
5

Even though this is an old question, I'd like to add this easy solution from https://coderwall.com/p/a3xreg/per-directory-zsh-config

// Add this to your ~/.zshrc
function chpwd() {
  if [ -r $PWD/.zsh_config ]; then
    source $PWD/.zsh_config
  else
    source $HOME/.zshrc
  fi
}
3
  • 2
    This works great, but you can probably skip lines 5-6 if you're already loading .zshrc when zsh is initialized.
    – jstr
    May 17, 2017 at 0:20
  • 1
    @jstr It is to reset whatever the $PWD/.zsh_config did.
    – aars
    May 18, 2017 at 6:40
  • 1
    @aars it doesn't necessarily reset anything—it would only do that if you specifically ensure that every variable, alias, and function set in .zsh_config is unset or overwritten in .zshrc.
    – iconoclast
    Mar 10, 2022 at 15:47
4

You can use https://github.com/horosgrisa/autoenv to achieve that. I use antigen to active that like this:

antigen bundle horosgrisa/autoenv

Then when you go into a directory with a .env file in it, it will be sourced and you can ser the variables there.

3

This is very easy to do by invoking add-zsh-hook on the chpwd event.

env_on_chdir () {
    case $PWD in
        /home/user/path/to/dir )
            export GO111MODULE=on;
            ;;
        /home/user/other/dir )
            export NO_COLOR=true;
            ;;
        * )
            # change background, when entering any other directory
            export GO111MODULE=off;
            unset NO_COLOR;
            ;;
    esac
}

# add env_on_chdir to chpwd_functions
add-zsh-hook chpwd env_on_chdir

For more, see https://www.refining-linux.org/archives/42-ZSH-Gem-8-Hook-function-chpwd.html.

0

Not per directory, no, but if they are the same you can put them in your startup files (.bash_profile for bash, not sure what it is for zsh). Otherwise, I'd suggest just putting all the settings in a file in that directory and sourcing it before running commands. Not an ideal solution, but I think it's close to the best you'll be able to find.

0

You can create a script that sets up the env and changes you to that directory.. then use it instead of cd working_dir

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .