如何在Bash脚本中调用virtualenv activate命令

192

如何创建一个Bash脚本来激活Python虚拟环境?

我的目录结构如下:

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

我可以通过以下方式激活我的虚拟环境:

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$ 

我做错什么了吗?


16
运行一个 shell 脚本实际上是创建了一个新的 shell。使用 source 的目的是在当前的 shell 中进行更改。你可以使用完整路径 ./env/bin/python 来使用 virtualenv 中的 Python。 - Pablo Navarro
3
@NgureNyaga,不,那个问题和我的不一样。他们正在询问如何从任意位置进行源控制。我已经知道如何做到这一点。我正在询问如何在自定义bash脚本中进行源控制并维护源代码。 - Cerin
12个回答

130

当你进行源代码时,你会将激活脚本加载到你的活动shell中。

当你在脚本中执行它时,你会将其加载到该脚本所在的shell中,在脚本完成后退出,并返回到原始的未激活的shell。

最好的选择是在一个函数中执行它。

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

或者是别名

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

2
对于Windows系统,在命令行中输入以下命令以激活虚拟环境:c:\tutorial>.\env\Scripts\activate - max4ever
9
当我执行源代码时,我绝对不知道正在发生什么。这极大地改善了我的Bash脚本编写。谢谢! - Robert Townley
3
您的别名想法对我也很有帮助。只是需要注意:我必须将它(别名 abcdef="source .../bin/activate")放在我的.zshrc脚本中(或者对于Bash用户来说是.bashrc),才能让它起作用。 - happyhuman
1
如果您的虚拟环境使用默认文件夹名称,那么这是一个不错的解决方案。我曾经在同一个文件夹中有多个repo,导致虚拟环境混乱不堪。现在我已经切换到了默认文件夹。 - 3manuek
12
我对bash等还很陌生。你能扩展一下这个例子,以便显示完整的脚本吗? - AljoSt
显示剩余2条评论

123
你应该使用 source 命令来调用 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指示。


6
甚至只需要执行 ". venv.sh"。 - outmind
1
无论我尝试什么,这个 source "/home/surest/Desktop/testservers/TEST_VENV/venv3/bin/activate" 都会产生以下错误:/home/surest/Desktop/testservers/TEST_VENV/py3.sh: 10: /home/surest/Desktop/testservers/TEST_VENV/py3.sh: source: not found - user4805123
1
当我在shell提示符下键入which source时,我什么也得不到,但是source venv3/bin/activate却按照我的期望打开了虚拟环境。 - user4805123
4
为什么这个命令可以运行,但是 source ./env/bin/activate(使用相同的 #!/bin/bash 前缀)不能?使用引号与不使用有何区别? - blacksite
1
我在脚本中使用源代码时没有问题,不需要加引号。但是我发现source ./env/bin/activate会有问题,因为它是相对于你运行的路径而言的。如果你在脚本中更改了目录,那么就可以使用相对路径。 - Flavio Garcia
显示剩余2条评论

18

虽然它没有向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

8
从技术上讲,你正在生成一个子shell。这不一定是个问题,但你应该明确告诉提问者。 - richo
它起作用了,但我必须先给我的“激活”文件授予权限。 - Adrian Lopez
1
这在2019年可行!在macOS上,我只需要将“/bin/bash”更改为“/usr/bin/env bash”。 - valem
在2020年,Ubuntu 18.04 AWS EC2上运行正常。我想知道如何使用相同的逻辑来停用它? - C. S. F. Junior
你可以使用 exit 或 Ctrl+d 从子shell 中 deactivate - Alexx Roche
要在shell提示符中显示virtualenv名称,请在调用.env/bin/activate之后使用以下命令:export PS1="($VIRTUAL_ENV) [\\u@\\h \\W]\\$ " - Eric

12

Sourcing会在当前shell中运行shell命令。当您像上面那样在脚本中进行源操作时,您会影响该脚本的环境,但是当脚本退出时,环境更改会被撤消,因为它们有效地超出了作用域。

如果您的目的是在虚拟环境中运行shell命令,则可以在激活脚本之后在您的脚本中执行该操作。如果您的目的是在虚拟环境内与shell交互,则可以在脚本内生成一个子shell,该子shell将继承该环境。


8

这是我经常使用的脚本。通过以下命令运行:$ source 脚本名称

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

activate

6
您也可以使用子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"

当满足以下条件时,这种风格尤其有用:

  • /opt/bin 下存在 commandAcommandC 的不同版本
  • commandB 存在于系统的 PATH 中或者非常常见
  • 这些命令在虚拟环境下失败了
  • 需要多个不同的虚拟环境

1
不要忘记在 $(...) 前后加上双引号,否则会丢失输出中包含的空格和制表符。 - Eric
1
"${VAR}" is strictly equivalent to "$VAR" you don't need curly brackets around shell variables because double quotes are actually more powerful. The exception is when using modifiers like for instance "${VAR:-default_value}" - Eric
1
PATH=$PATH:/opt/bin 需要适当引用以处理带有空格和制表符的路径。 - Eric
1
@Eric 谢谢,虽然你可以使用下面帖子的“编辑”按钮来建议更改!此外,让人们知道,尽管这通常是一项要求和安全性重要的工作,但任何故意向PATH添加IFS字符的人都是恐怖分子。 - ti7

5

正如其他人已经指出的那样,你做错了的是没有引用你创建的脚本。当你像你展示的那样运行脚本时,它会创建一个新的shell,激活虚拟环境,然后退出,因此没有对你运行脚本的原始shell进行任何更改。

你需要使用source命令来引用该脚本,这将使其在当前shell中运行。

你可以通过调用source shell.sh. shell.sh来实现。

为了确保该脚本被引用而不是正常执行,最好在脚本中加入一些检查来提醒你,例如我使用的脚本如下:

#!/bin/bash
if [[ "$0" = "$BASH_SOURCE" ]]; then
    echo "Needs to be run using source: . activate_venv.sh"

else
    VENVPATH="venv/bin/activate"
    if [[ $# -eq 1 ]]; then 
        if [ -d $1 ]; then
            VENVPATH="$1/bin/activate"
        else
            echo "Virtual environment $1 not found"
            return
        fi

    elif [ -d "venv" ]; then 
        VENVPATH="venv/bin/activate"

    elif [-d "env"]; then 
        VENVPATH="env/bin/activate"
    fi

    echo "Activating virtual environment $VENVPATH"
    source "$VENVPATH"
fi

虽然它并非万无一失,但易于理解且实现其功能。


5

为什么要使用bash脚本?

  1. 如果你想在多个虚拟环境之间切换或快速进入某个虚拟环境,你可以尝试使用 virtualenvwrapper。它提供了许多实用程序,如 workon venvmkvirtualenv venv 等等。

  2. 如果你只想在特定的虚拟环境中运行 Python 脚本,请使用 /path/to/venv/bin/python script.py 运行它。


实际上,我想从bash脚本中调用workon ...命令。(因为我想在每次启动后执行其他操作。)但是我找不到让它工作的方法。 - Daniel B.

2

我只是将这个添加到了我的.bashrc-personal配置文件中。

function sv () {
    if [ -d "venv" ]; then
      source "venv/bin/activate"
    else
      if [ -d ".venv" ]; then
        source ".venv/bin/activate"
      else
        echo "Error: No virtual environment detected!"
      fi
    fi
}

1

如其他答案所述,运行脚本时会创建一个子 Shell。 当脚本退出时,对该 Shell 的所有修改都将丢失。

我们实际需要的是运行一个新的 Shell,在其中激活虚拟环境,并且不要从中退出。 请注意,这是一个新的 Shell,而不是在运行脚本之前使用的那个 Shell。 这意味着,如果你在其中输入 exit,它将会从子 Shell 退出,并返回到之前的那个 Shell(即运行脚本的那个 Shell),它不会关闭你的 xterm 或其他任何东西,正如你可能预期的那样。

问题在于,当我们执行 bash 时,它会读取其 rc 文件(/etc/bash.bashrc,~/.bashrc),这些文件更改 Shell 环境。解决方案是提供一种方法让 bash 像往常一样设置 Shell,同时另外激活虚拟环境。为此,我们创建一个临时文件,重现原始的 bash 行为,并添加一些需要启用 venv 的内容。然后我们要求 bash 使用它替代其通常的 rc 文件。

拥有一个专门用于我们的 venv 的新 Shell 的“优点”是,要停用虚拟环境,只需要退出 Shell 即可。 我在下面公开的脚本中使用它来提供“停用”选项,该选项通过向新的 Shell 发送信号(kill -SIGUSR1)来执行,并且这个信号被拦截(trap ...),并引发退出 Shell 的操作。 注:我使用 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接