5

I would like to be able to save my current environment in a file (for a running interactive session), so that I can:

  • Save it, export/modify/delete variables at will in the running session, then restore the saved environment
  • Switch at will between multiple environment
  • Detect differences between two environment

I am only interested in exported variables. As I want to be able to restore the environment it have to be a shell function, I am using bash. Ideally, it would not depends on external programs, and would work on versions of bash from v3.2.25 to current.

For now, to save my environment I use the following function:

env_save () {
    export -p > "$STORAGE/$1.sh"
}

That I use as env_save <filename> in a running session. I have some boilerplate code to keep backups, but let's ignore that.

However, I then have difficulties with loading the environment back:

env_restore () {
    source "$STORAGE/$1.sh"
}

As this would not remove spurious variables that I created in the mean time. That is, calling export -p after env_restore <filename> might not give the same output than cat $STORAGE/$1.sh.

Is there a clean way to handle that problem? I will probably need to blacklist some variables such as PWD, OLDPWD, SHELL, SHLVL, USER, SSH_*, STORAGE, etc... That is, those variable should not be saved and should not be changed when restoring as they are special variables. I cannot use a whitelist as I do not know what variables will be there.

  • Does the env(1) command do what you want? The export command is a shell builtin that, as far as I know, is not well suited to your use. You want may want something simpler like env(1), which does not interact with your shell. – thb Sep 7 '16 at 15:32
  • env(1) would be fine. My problem is how to load back the environment. I need to clean it up, somehow. – Nonyme Sep 7 '16 at 17:49
5

POSIXly, you can do:

# save
export -p > saved-env

...

# restore
blacklisted () {
  case $1 in
    PWD|OLDPWD|SHELL|STORAGE|-*) return 0 ;;
    *) return 1 ;;
  esac
}

eval '
  export() {
    blacklisted "${1%%=*}" || unset -v "${1%%=*}"
  }
  '"$(export -p)"
export() {
  blacklisted "${1%%=*}" || command export "$@"
}
. saved-env
unset -f export

Note that for bash not invoked as sh, you'd need to issue a set -o posix for that to work properly. Also with bash versions prior to 4.4, sourcing the output of export -p is potentially unsafe:

$ env -i 'a;reboot;=1' /bin/bash -o posix -c 'export -p'
export OLDPWD
export PWD="/"
export SHLVL="1"
export a;reboot;

ksh93 has a similar problem. yash doesn't have that particular one, but still has problems with variable names starting with -:

$ env -i -- '-p=' yash -c 'export -p'
export '-p'=''
export OLDPWD
export PWD='/'

Also beware of potential problems if you're not in the same locale when saving and restoring the variables.

bash-4.3$ locale charmap
ISO-8859-15
bash-4.3$ export Stéphane=1
bash-4.3$ export -p > a
bash-4.3$ LC_ALL=en_GB.UTF-8 bash -c '. ./a'
./a: line 5: export: `Stéphane=1': not a valid identifier
  • @cuonglm the asker specifically mentioned blacklisting STORAGE along with other env variables not normally set by the user. – Centimane Sep 7 '16 at 16:25
  • @Dave Oh, the question was updated, – cuonglm Sep 7 '16 at 16:46
  • Excellent remarks about the pitfalls. I more or less assumed the environment variables where in a correct format, but that is how security problems are created! – Nonyme Sep 7 '16 at 19:17
  • A couple bourne shells I tried don't like overloading 'export' as a custom function. dash: 1: Syntax error: Bad function name. FreeBSD sh: Syntax error: Cannot override a special builtin with a function. – Juan Apr 20 '18 at 21:26
  • Trying to overload export() doesn't work with mksh, pdksh and zsh either. Invoking 'export' after defining a custom export() function just runs the builtin export. – Juan Apr 20 '18 at 21:37
1

I found a way by using compgen -A export to get the list of environment variables:

blacklisted () {
    case $1 in
        PWD|OLDPWD|SHELL|STORAGE) return 0 ;;
        *) return 1 ;;
    esac
}

env_save () { # Assume "$STORAGE/#1.sh" is empty
    local VAR
    for VAR in $(compgen -A export); do
        blacklisted $VAR || \
            echo "export $VAR='${!VAR}'" >> "$STORAGE/$1.sh"
    done
}

env_restore () {
    local VAR
    for VAR in $(compgen -A export); do
        blacklisted $VAR || \
            unset $VAR
    done
    source "$STORAGE/$1.sh"
}
  • This is sadly bash specific, maybe somebody can come up with a better solution. – Nonyme Sep 7 '16 at 14:39

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.