10

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.

2
  • 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, 2016 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, 2016 at 17:49

2 Answers 2

7

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
5
  • @cuonglm the asker specifically mentioned blacklisting STORAGE along with other env variables not normally set by the user.
    – Centimane
    Sep 7, 2016 at 16:25
  • @Dave Oh, the question was updated,
    – cuonglm
    Sep 7, 2016 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, 2016 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, 2018 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, 2018 at 21:37
2

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"
}
1
  • This is sadly bash specific, maybe somebody can come up with a better solution.
    – Nonyme
    Sep 7, 2016 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.