5

I have been working on several projects, and they require different environment variables (e.g., PATH for different versions of clang executables, PYTHONPATH for several external modules). Whenever I work on one project, I have to modify these environment variables myself (e.g., change .zshrc/.bashrc and source it); and I sometimes forget and make mistakes.

Is there a way/project that helps do this automatically, similar to what virtualenv does in Python?

1

There're mature tools designed to set environment variables for a specific directory.

Compared with other tools designed for this, direnv is the best of them. One of the main benefit is that it supports unloading the environment variables when you exit from that directory.

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
2

Yuch, too much maintenance and too much individual stuff.
I'd recommend putting most of the effort into having appropriately scoped names that are appropriate for the platform so you can just have all of them all of the time. PYTHONPATH is a good example... you're unlikely to want to repurpose it for a Ruby project... You can group and mark the group with comments in the .bashrc to ease maintenance.
It is not always possible to do that, i.e.. when there is conflict (plus it requires editing and not using discrete files) and sometimes you will need a framework specific setup file. One approach to that is to have aliases setup to run them, e.g.

e.g.

alias pp='. ~/pp.setup' # For using Python
alias rb='. /rb.setup'  # For using Ruby

You could also create a function, something like 'switcher' that uses/sets a variable and just switches using a parameter passed in, or just toggles to what isn't current.

  • This is a great point. If possible, install your different versions of clang so you can run them as cc-clang3.2 or whatever, so you can use any version at any time just by running it. – Peter Cordes Apr 20 '15 at 21:28
1

There are management tools such as modules, that allow dynamic modification of a user's environment... though this one is no longer developed; last version is from 2012

Example:

$ module load gcc/3.1.1
$ which gcc
/usr/local/gcc/3.1.1/linux/bin/gcc
0

You can source any file you like, it doesn't have to be .zshrc. You could make an env-setup script for each different project, and keep it in the project directory.

If you want to be able to start new shells and have them load the env settings, you have a couple options:

  • Always start your shells in the project directory, and have your .zshrc [[ -e ./PROJECT-ENV.sh ]] && source ./PROJECT-ENV.sh (which you keep there). DANGEROUS: starting a new shell in a directory with hostile contents runs whatever code is in the file you source.
  • .zshrc: source ~/current-project.sh. update current project with ln -sf "$PWD/THIS-PROJECT-ENV.sh ~/current-project.sh

If only a few commands for each project need the env vars set, you could make wrapper scripts for them that set the variables, and then leave your shell rc file untouched.

Another idea: you could hack something into PROMPT_COMMAND, which runs before every prompt is displayed. Or hook cd/pushd/popd by writing shell functions with those names which check/set env vars and then run builtin cd.

  • Yes, you are correct that wrapper is enough when only a few of commands are needed. child-shell with PROJECT-ENV.sh seems the most efficient way for me and the only trick is that I should remember to exit it. – Hongxu Chen Apr 20 '15 at 6:12
  • or just source a different PROJECT-ENV.sh after you cd, but then you have the problem of remembering to change vars. Maybe have PROJECT-ENV.sh also modify your PS1, or make the build scripts for each project look for something so they can detect that you have the wrong setting when you do something common. Then you can fix your shell so everything works. – Peter Cordes Apr 20 '15 at 8:29
  • I editted in another idea: you could hook cd to check / set your vars. – Peter Cordes Apr 20 '15 at 13:54
  • I think I'll choose to change PS1. Using PROMPT_COMMAND is too noisy; and changing cd is not optimal since sometimes I need to use the project settings but inside other external directories(e.g., for testing purposes). – Hongxu Chen Apr 20 '15 at 14:10
  • I meant putting a function call in PROMPT_COMMAND to a function that checks your current directory, and updates env vars. But if you don't ALWAYS want your env vars to silently change (or non-silently), then that's not what you want. – Peter Cordes Apr 20 '15 at 21:26
0

Inspired by @Peter Cordes and @Michael Durrant's answers, I came up with a limited and lightweight solution for my special case.

Firstly let me re-illustrate my real issues.

I need to use different llvm related executables (clang, llvm-config, etc) that are under project local directories, and I need to use 1 default version for general purpose (compiling, etc). I also need to need to use different python modules (llvm python bindings) inside different local directories.

Since all my llvm related projects are built with similar directory structures, I specify the llvm version related environment variable (MY_LLVM_VERSION) and add the directories to PATH and PYTHONPATH. I used a function named _my_llvm_version that customize the variable interactively. As functions only have effects inside it, I use aliases to do the real source work. Then let MY_LLVM_VERSION defaults to my commonly used one (git). These are all inside ~/.zlocal that will be sourced for an interactive zsh. (Full file is here). By sourcing ~/.zlocal, I'm able to change PATH and PYTHONPATH according to MY_LLVM_VERSION.

function _my_llvm_version() {
    available=("3.3" "3.4" "3.5" "git")
    USAGE="my_llvm_version 3.3|3.4|3.5|git"
    if [ $# -ne 1 ] || ! (( ${available[(I)${1}]} )); then
      echo "usage: $USAGE"
    fi
    MY_LLVM_VERSION="$1"
}
alias my_ll33='_my_llvm_version 3.3;source ~/.zlocal'
alias my_ll34='_my_llvm_version 3.4;source ~/.zlocal'
alias my_ll35='_my_llvm_version 3.5;source ~/.zlocal'
alias my_llgit='_my_llvm_version git;source ~/.zlocal'
: ${MY_LLVM_VERSION:=git}

I think it should be more maintainable and do not invoke a child shell. It should be useful to add llvm version to PS1 area, but currently I do not need that.

Of course, this solution is not general and not a real solution to my original question.

0

You can use direnv to load environment variables on directory change. From its homepage:

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.

I am using it with the xonsh shell (via a small additional plugin) and it works excellent.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.