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

You must log in to answer this question.

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