如其他答案所述,当您运行脚本时,它会创建一个子外壳。当脚本退出时,对该 shell 的所有修改都将丢失。
实际上,我们需要的是在虚拟环境处于活动状态的情况下运行一个新的 shell,而不是从中退出。请注意,这是一个新的shell,而不是在您运行脚本之前正在使用的 shell。这意味着,如果您输入exit
它,它将退出子shell,并返回到前一个(运行脚本的那个),它不会关闭您的 xterm 或其他任何东西,正如您可能预期的那样。
麻烦的是,当我们执行 bash 时,它会读取它的 rc 文件(/etc/bash.bashrc,~/.bashrc),这会改变 shell 环境。解决方案是为 bash 提供一种照常设置 shell 的方法,同时激活虚拟环境。为此,我们创建一个临时文件,重新创建原始 bash 行为,并添加一些我们需要启用 venv 的东西。然后我们要求 bash 使用它而不是它通常的 rc 文件。
拥有一个“专用”给我们的 venv 的新 shell 的一个有益副作用是,要停用虚拟环境,唯一需要做的就是退出 shell。我在下面公开的脚本中使用它来提供“停用”选项,该选项通过向新外壳 ( ) 发送信号来起作用kill -SIGUSR1
,该信号被拦截 ( trap ...
) 并引发外壳退出。注意:我使用 SIGUSR1 以不干扰可以在“正常”行为中设置的任何内容。
我使用的脚本:
#!/bin/bash
PYTHON=python3
myname=$(basename "$0")
mydir=$(cd $(dirname "$0") && pwd)
venv_dir="${mydir}/.venv/dev"
usage() {
printf "Usage: %s (activate|deactivate)\n" "$myname"
}
[ $# -eq 1 ] || { usage >&2; exit 1; }
in_venv() {
[ -n "$VIRTUAL_ENV" -a "$VIRTUAL_ENV" = "$venv_dir" -a -n "$VIRTUAL_ENV_SHELL_PID" ]
}
case $1 in
activate)
# check if already active
in_venv && {
printf "Virtual environment already active\n"
exit 0
}
# check if created
[ -e "$venv_dir" ] || {
$PYTHON -m venv --clear --prompt "venv: dev" "$venv_dir" || {
printf "Failed to initialize venv\n" >&2
exit 1
}
}
# activate
tmp_file=$(mktemp)
cat <<EOF >"$tmp_file"
# original bash behavior
if [ -f /etc/bash.bashrc ]; then
source /etc/bash.bashrc
fi
if [ -f ~/.bashrc ]; then
source ~/.bashrc
fi
# activating venv
source "${venv_dir}/bin/activate"
# remove deactivate function:
# we don't want to call it by mistake
# and forget we have an additional shell running
unset -f deactivate
# exit venv shell
venv_deactivate() {
printf "Exitting virtual env shell.\n" >&2
exit 0
}
trap "venv_deactivate" SIGUSR1
VIRTUAL_ENV_SHELL_PID=$$
export VIRTUAL_ENV_SHELL_PID
# remove ourself, don't let temporary files laying around
rm -f "${tmp_file}"
EOF
exec "/bin/bash" --rcfile "$tmp_file" -i || {
printf "Failed to execute virtual environment shell\n" >&2
exit 1
}
;;
deactivate)
# check if active
in_venv || {
printf "Virtual environment not found\n" >&2
exit 1
}
# exit venv shell
kill -SIGUSR1 $VIRTUAL_ENV_SHELL_PID || {
printf "Failed to kill virtual environment shell\n" >&2
exit 1
}
exit 0
;;
*)
usage >&2
exit 1
;;
esac