如何从 bash 脚本内部临时绕过 python 虚拟环境?

sor*_*rin 3 python virtualenv

我确实有一个 bash 脚本,需要在系统上安装一些 Python 包,而不是在执行脚本时可能会或可能不会激活的虚拟环境。

这个脚本是由可能已经激活了 python 虚拟环境的人调用的,我确实想确保对于少数命令我不使用它。

我尝试使用该deactivate命令,但即使 bash 检测到虚拟环境(变量的存在VIRTUAL_ENV),该命令似乎也不可用。

顺便说一句,我不想​​永久禁用虚拟环境。我只想在它之外运行一些命令。我怎样才能做到这一点?

Cha*_*ffy 6

如果在启动脚本之前激活

如果您activate在父 shell 中执行该步骤,而不是在运行脚本本身的 shell 实例中执行该步骤,则非导出变量和函数在其运行时不可用。

要完全清楚定义:

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.
Run Code Online (Sandbox Code Playgroud)

您有三个选择:

  • 在启动脚本之前deactivate,从父 shell导出函数及其依赖项。

    如下所示,看起来像:

    source my-virtualenv/bin/activate
    export VIRTUAL_ENV ${!_OLD_VIRTUAL_@}
    export -f deactivate
    ./my-script-that-needs-to-be-able-to-deactivate
    
    Run Code Online (Sandbox Code Playgroud)

    您可以选择定义一个激活函数来为您执行此操作,如下所示:

    # 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
    
    Run Code Online (Sandbox Code Playgroud)
  • 在脚本中对之前的 Python 环境进行一些猜测。

    由于显而易见的原因,这种说法不太可靠;但是,由于virtualenv不导出包含原始 的 shell 变量PYTHON_HOME,因此子进程 shell 根本无法获得该信息;因此,猜测是最好的选择:

    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
    }
    
    Run Code Online (Sandbox Code Playgroud)

    ...在有限范围内使用为:

    run_python_code_in_virtualenv_here
    (best_guess_deactivate; run_python_code_outside_virtualenv_here)
    run_python_code_in_virtualenv_here
    
    Run Code Online (Sandbox Code Playgroud)
  • 在首次获取的 shell 的分叉子级中运行脚本,activate无需干预exec()调用

    也就是说,不要将脚本作为常规子进程调用,而是使用:

    # New shell instance, does not inherit non-exported (aka regular shell) variables
    ./my-shell-script
    
    Run Code Online (Sandbox Code Playgroud)

    ...将其源到当前 shell 的分叉副本中,如下所示

    # Forked copy of existing shell instance, does inherit variables
    (source ./my-shell-script)
    
    Run Code Online (Sandbox Code Playgroud)

    ...或者,如果您相信它在执行后将控制权交还给您的交互式 shell,而不会过多地弄乱状态(我不建议这样做),只需:

    # Probably a bad idea
    source ./my-shell-script
    
    Run Code Online (Sandbox Code Playgroud)

    所有这些方法都存在一定的风险:因为它们不使用调用execve,所以它们不遵守脚本中的任何 shebang 行,因此如果它是专门为 ksh93、zsh 或与您所使用的 shell 不同的其他 shell 编写的交互使用时,他们可能会行为不端。

如果在脚本内激活

最有可能的情况是,您运行的 shelldeactivate不是来源 shell 的直接fork()ed 子代(没有中间exec-family 调用)activate,因此既没有继承创建的函数也没有继承(非导出)shell 变量通过那个脚本。

避免这种情况的一种方法是导出deactivate源脚本的 shell 中的函数activate,如下所示:

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.
Run Code Online (Sandbox Code Playgroud)

我的上述输出如下:

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
Run Code Online (Sandbox Code Playgroud)

假设您已经解决了这个问题,让我们再次运行一次,使用子 shell 来限制停用范围,使其成为临时的:

. 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
Run Code Online (Sandbox Code Playgroud)