If activating before starting the script
If you did the activate step in a parent shell, not in the shell instance running the script itself, then non-exported variables and functions are unavailable during its runtime.
To be entirely clear about definitions:
source my-virtualenv/bin/activate # this runs in the parent shell
./my-shell-script # the shell script itself is run in a child process created by
# fork()+execve(); does not inherit shell variables / functions, so
# deactivate WILL NOT WORK here.
(source my-shell-script) # creates a subshell with fork(), then directly invokes
# my-shell-script inside that subshell; this DOES inherit shell
# variables / functions, and deactivate WILL WORK here.
You have three options:
Export the deactivate function and its dependencies from the parent shell, before starting the script.
This is as given below, and looks something like:
source my-virtualenv/bin/activate
export VIRTUAL_ENV ${!_OLD_VIRTUAL_@}
export -f deactivate
./my-script-that-needs-to-be-able-to-deactivate
You could optionally define an activation function that does this for you, like so:
# put this in your .bashrc
activate() {
source "$1"/bin/activate && {
export VIRTUAL_ENV ${!_OLD_VIRTUAL_@}
export -f deactivate
}
}
# ...and then activate virtualenvs like so:
activate my-virtualenv
Make some guesses, within the script, about what the prior Python environment looked like.
This is less reliable, for obvious reasons; however, as virtualenv does not export the shell variables containing the original PYTHON_HOME, that information is simply unavailable to child-process shells; a guess is thus the best option available:
best_guess_deactivate() {
if [[ $VIRTUAL_ENV && $PATH =~ (^|:)"$VIRTUAL_ENV/bin"($|:) ]]; then
PATH=${PATH%":$VIRTUAL_ENV/bin"}
PATH=${PATH#"$VIRTUAL_ENV/bin:"}
PATH=${PATH//":$VIRTUAL_ENV/bin:"/}
unset PYTHONHOME VIRTUAL_ENV
fi
}
...used within a limited scope as:
run_python_code_in_virtualenv_here
(best_guess_deactivate; run_python_code_outside_virtualenv_here)
run_python_code_in_virtualenv_here
Run the script in a forked child of the shell that first sourced activate with no intervening exec() call
That is, instead of invoking your script as a regular subprocess, with:
# New shell instance, does not inherit non-exported (aka regular shell) variables
./my-shell-script
...source it into a forked copy of the current shell, as
# Forked copy of existing shell instance, does inherit variables
(source ./my-shell-script)
...or, if you trust it to hand back control to your interactive shell after execution without messing up state too much (which I don't advise), simply:
# Probably a bad idea
source ./my-shell-script
All of these approaches have some risk: Because they don't use an execve call, they don't honor any shebang line on the script, so if it's written specifically for ksh93, zsh, or another shell that differs from the one you're using interactively, they're likely to misbehave.
If activating within the script
The most likely scenario is that the shell where you're running deactivate isn't a direct fork()ed child (with no intervening exec-family call) of the one where activate was sourced, and thus has inherited neither functions or (non-exported) shell variables created by that script.
One means to avoid this is to export the deactivate function in the shell that sources the activate script, like so:
printf 'Pre-existing interpreter: '; type python
. venv-dir/bin/activate
printf 'Virtualenv interpreter: '; type python
# deactivate can be run in a subshell without issue, scoped to same
printf 'Deactivated-in-subshell interpreter: '
( deactivate && type python ) # this succeeds
# however, it CANNOT be run in a child shell not forked from the parent...
printf 'Deactivated-in-child-shell (w/o export): '
bash -c 'deactivate && type python' # this fails
# ...unless the function is exported with the variables it depends on!
export -f deactivate
export _OLD_VIRTUAL_PATH _OLD_VIRTUAL_PYTHONHOME _OLD_VIRTUAL_PS1 VIRTUAL_ENV
# ...after which it then succeeds in the child.
printf 'Deactivated-in-child-shell (w/ export): '
bash -c 'deactivate && type python'
My output from the above follows:
Pre-existing interpreter: python is /usr/bin/python
Virtualenv interpreter: python is /Users/chaduffy/test.venv/bin/python
Deactivated-in-subshell interpreter: python is /usr/bin/python
Deactivated-in-child-shell (w/o export): bash: deactivate: command not found
Deactivated-in-child-shell (w/ export): python is /usr/bin/python
Assuming you've fixed that, let's run once more through using a subshell to scope deactivation to make it temporary:
. venv-dir/activate
this-runs-in-venv
# minor performance optimization: exec the last item in the subshell to balance out
# ...the performance cost of creating that subshell in the first place.
(deactivate; exec this-runs-without-venv)
this-runs-in-venv
/usr/bin/python.