如何在一个 Bash 脚本中源虚拟化激活

如何创建一个 Bash 脚本来激活一个 Python viralenv?

我的目录结构是这样的:

.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$

我做错了什么?

221325 次浏览

当您提供源代码时,您正在将激活脚本加载到活动 shell 中。

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

最好的选择就是在函数中进行

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

或者化名

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

希望这个能帮上忙。

源代码在当前 shell 中运行 shell 命令。当您像上面所做的那样在脚本内部进行源代码开发时,您会影响该脚本的环境,但是当脚本退出时,环境更改将被撤消,因为它们实际上已经超出了作用域。

如果您的意图是在 viralenv 中运行 shell 命令,那么您可以在源代码激活脚本之后在您的脚本中这样做。如果您的意图是与 viralenv 中的 shell 交互,那么您可以在脚本中生成一个子 shell,它将继承环境。

Bash 脚本的来源是什么?

  1. 如果您打算在多个 viralenv 之间切换或快速输入一个 viralenv,是否尝试了 virtualenvwrapper?它提供了很多工具,比如 workon venvmkvirtualenv venv等等。

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

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

您应该使用 source 调用 bash 脚本。

这里有一个例子:

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

在你的壳上就这么叫吧:

> source venv.sh

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

> . venv.sh

好了,shell 指示将显示在提示符上。

你也可以使用 subshell 来更好地包含你的用法——这里有一个实际的例子:

#!/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中或非常常见
  • 这些命令在 Virtual alenv 下失败
  • 一个人需要各种不同的虚拟世界

下面是我经常使用的脚本

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


activate

您应该在一行中使用多个命令。例如:

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

当您在一行中激活您的虚拟环境时,我认为它会忘记其他命令行,您可以通过在一行中使用多个命令来防止这种情况。 这对我很有效:)

当我学习 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

这样做的好处是可以更改提示符。

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

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

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

将一个新的 shell“专门”用于我们的 venv 的一个有益的副作用是,要禁用虚拟环境,唯一需要的就是退出 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

正如其他人已经指出的那样,您所做的错误之处在于没有为您创建的脚本提供源代码。当您像上面展示的那样运行脚本时,它会创建一个新的 shell 来激活虚拟环境,然后退出,这样就不会对运行脚本的原始 shell 进行任何更改。

您需要为脚本提供源代码,这将使它在您当前的 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

它不是防弹的,但是它很容易理解并且起作用。