144

如何创建 Bash 脚本来激活 Python virtualenv?

我有一个目录结构,如:

.env
    bin
        activate
        ...other virtualenv files...
src
    shell.sh
    ...my code...

我可以通过以下方式激活我的 virtualenv:

user@localhost:src$ . ../.env/bin/activate
(.env)user@localhost:src$

但是,从 Bash 脚本中执行相同操作不会执行任何操作:

user@localhost:src$ cat shell.sh
#!/bin/bash
. ../.env/bin/activate
user@localhost:src$ ./shell.sh
user@localhost:src$ 

我究竟做错了什么?

4

10 回答 10

105

当您获取资源时,您正在将激活脚本加载到您的活动 shell 中。

当您在脚本中执行此操作时,您将其加载到该 shell 中,该 shell 在您的脚本完成时退出并且您返回到原始的未激活 shell。

您最好的选择是在函数中执行此操作

activate () {
  . ../.env/bin/activate
}

或别名

alias activate=". ../.env/bin/activate"

希望这可以帮助。

于 2012-10-29T13:02:45.787 回答
90

您应该使用源代码调用 bash 脚本。

这是一个例子:

#!/bin/bash
# Let's call this script venv.sh
source "<absolute_path_recommended_here>/.env/bin/activate"

在你的 shell 上就这样称呼它:

> source venv.sh

或者正如@outmind 建议的那样:(请注意,这不适用于 zsh)

> . venv.sh

好了,shell 指示将放置在您的提示符上。

于 2016-02-02T14:53:54.733 回答
17

虽然它没有在 shell 提示符中添加“(.env)”前缀,但我发现这个脚本可以按预期工作。

#!/bin/bash
script_dir=`dirname $0`
cd $script_dir
/bin/bash -c ". ../.env/bin/activate; exec /bin/bash -i"

例如

user@localhost:~/src$ which pip
/usr/local/bin/pip
user@localhost:~/src$ which python
/usr/bin/python
user@localhost:~/src$ ./shell
user@localhost:~/src$ which pip
~/.env/bin/pip
user@localhost:~/src$ which python
~/.env/bin/python
user@localhost:~/src$ exit
exit
于 2012-10-29T14:46:36.390 回答
11

Sourcing 在您当前的 shell 中运行 shell 命令。当您像上面那样在脚本内部获取源时,您正在影响该脚本的环境,但是当脚本退出时,环境更改将被撤消,因为它们实际上已经超出了范围。

如果您的意图是在 virtualenv 中运行 shell 命令,您可以在获取激活脚本后在脚本中执行此操作。如果您的意图是与 virtualenv 中的 shell 交互,那么您可以在脚本中生成一个继承环境的子 shell。

于 2012-10-29T13:04:41.043 回答
6

这是我经常使用的脚本。运行它$ source script_name

#!/bin/bash -x
PWD=`pwd`
/usr/local/bin/virtualenv --python=python3 venv
echo $PWD
activate () {
    . $PWD/venv/bin/activate
}

activate
于 2019-06-24T19:01:23.940 回答
4

您也可以使用子shell 来更好地控制您的使用 - 这是一个实际示例:

#!/bin/bash

commandA --args

# Run commandB in a subshell and collect its output in $VAR
# NOTE
#  - PATH is only modified as an example
#  - output beyond a single value may not be captured without quoting
#  - it is important to discard (or separate) virtualenv activation stdout
#    if the stdout of commandB is to be captured
#
VAR=$(
    PATH="/opt/bin/foo:$PATH"
    . /path/to/activate > /dev/null  # activate virtualenv
    commandB  # tool from /opt/bin/ which requires virtualenv
)

# Use the output from commandB later
commandC "$VAR"

这种风格在以下情况下特别有用

  • 不同版本的commandAcommandC存在于/opt/bin
  • commandB存在于系统中PATH或很常见
  • 这些命令在 virtualenv 下失败
  • 需要各种不同的虚拟环境
于 2018-06-13T05:54:25.327 回答
3

采购 bash 脚本的目的是什么?

  1. 如果您打算在多个 virtualenv 之间切换或快速进入一个 virtualenv,您尝试过virtualenvwrapper吗?它提供了很多实用程序,例如workon venvmkvirtualenv venv等等。

  2. 如果您只是在某个 virtualenv 中运行 python 脚本,请使用/path/to/venv/bin/python script.py它来运行它。

于 2012-10-29T13:18:20.263 回答
1

当我学习 venv 时,我创建了一个脚本来提醒我如何激活它。

#!/bin/sh
# init_venv.sh
if [ -d "./bin" ];then
  echo "[info] Ctrl+d to deactivate"
  bash -c ". bin/activate; exec /usr/bin/env bash --rcfile <(echo 'PS1=\"(venv)\${PS1}\"') -i"
fi

这样做的好处是它改变了提示。

于 2020-04-10T12:52:23.090 回答
1

如其他答案所述,当您运行脚本时,它会创建一个子外壳。当脚本退出时,对该 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
于 2021-11-19T14:51:39.443 回答
0

You should use multiple commands in one line. for example:

os.system(". Projects/virenv/bin/activate && python Projects/virenv/django-project/manage.py runserver")

when you activate your virtual environment in one line, I think it forgets for other command lines and you can prevent this by using multiple commands in one line. It worked for me :)

于 2019-07-05T15:38:55.773 回答