如其他答案所述,当您运行脚本时,它会创建一个子外壳。当脚本退出时,对该 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