190

How do you create a Bash script to activate a Python virtualenv?

I have a directory structure like:

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

I can activate my virtualenv by:

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

However, doing the same from a Bash script does nothing:

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

What am I doing wrong?

Cerin
  • 60,957
  • 96
  • 316
  • 522
  • 15
    When you run a shell script you actually are creating a new shell. The point of using `source` is to change something in the current shell. You can use the virtualenv's python using the full path `./env/bin/python`. – Pablo Navarro Oct 29 '12 at 13:04
  • 2
    @NgureNyaga, No, that question is not the same as mine. They're asking how to source from an arbitrary location. I already know how to do this. I'm asking how to source within a custom bash script and maintain the source. – Cerin Oct 29 '12 at 14:50

12 Answers12

128

When you source, you're loading the activate script into your active shell.

When you do it in a script, you load it into that shell which exits when your script finishes and you're back to your original, unactivated shell.

Your best option would be to do it in a function

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

or an alias

alias activate=". ../.env/bin/activate"
starball
  • 20,030
  • 7
  • 43
  • 238
richo
  • 8,717
  • 3
  • 29
  • 47
  • 2
    for windows c:\tutorial>.\env\Scripts\activate – max4ever Apr 11 '14 at 11:08
  • 9
    I had absolutely no idea that was what was happening when I do source This has vastly changed my bash scripting for the better. Thank you! – Robert Townley Jan 27 '16 at 21:45
  • 3
    Your alias idea worked nicely for me too. Just a note: I had to put it (alias abcdef="source .../bin/activate") in my .zshrc script (or .bashrc for the bash users) for it to work. – happyhuman Aug 06 '16 at 21:15
  • 1
    This is a nice solution if you have your virtualenvs using the default folder name. I used to have more than one repo in the folder, making a mess on the virtualenvs. I switched to this default now. – 3manuek Dec 05 '17 at 18:43
  • 12
    I'm quite new to bash etc. Can you expand this example so that it shows the full script? – AljoSt Jul 05 '18 at 16:19
  • I put my activation alias in ~/.bash_aliases which is sourced from ~/.bashrc when found. Reduces cluttering in .bashrc, easier to manage and makes the alias available in all bash shells. In Debian just activate the code under 'Alias definitions' header to set it up and create your ~/.bash_aliases file. – zbinkz Aug 13 '21 at 16:42
  • none of these steps worked for me – turtle_in_mind Mar 29 '22 at 16:04
120

You should call the bash script using source.

Here is an example:

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

On your shell just call it like that:

> source venv.sh

Or as @outmind suggested: (Note that this does not work with zsh)

> . venv.sh

There you go, the shell indication will be placed on your prompt.

Pubudu Dodangoda
  • 2,742
  • 2
  • 22
  • 38
Flavio Garcia
  • 1,491
  • 1
  • 14
  • 20
  • 6
    or even just ". venv.sh" – outmind Apr 17 '16 at 13:05
  • 1
    no matter what I try, this `source "/home/surest/Desktop/testservers/TEST_VENV/venv3/bin/activate"` produces: `/home/surest/Desktop/testservers/TEST_VENV/py3.sh: 10: /home/surest/Desktop/testservers/TEST_VENV/py3.sh: source: not found` –  Feb 08 '18 at 18:02
  • 1
    I also get nothing when I type `which source` at a shell prompt, yet, `source venv3/bin/activate` does what I expect and open the venv. ... –  Feb 08 '18 at 18:15
  • 4
    Why does this work, but `source ./env/bin/activate` (with the same `#!/bin/bash` prefix) does not? What's the difference between using quotes and not? – blacksite Nov 27 '18 at 16:14
  • 1
    I have no problem using the source inside the script without quotes. I see a problem with `source ./env/bin/activate` because this is relative to the path you are running right? If you change the directory inside the script than you can go relative. – Flavio Garcia Nov 27 '18 at 17:52
  • 1
    I have used this recipe, but called the bash file `activate_venv`, so that, when I want to activate the venv, I use the command `source activate_venv`, which is more similar to `source {venv_path}/bin/activate` than `source venv.sh`. This is just a personal preference of mine. – WhyWhat Aug 28 '21 at 10:10
  • `. ./venv.sh` will work with zsh. mind that "./" is for the absolute path of venv.sh – ashuvssut Feb 12 '23 at 14:18
18

Although it doesn't add the "(.env)" prefix to the shell prompt, I found this script works as expected.

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

e.g.

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
Cerin
  • 60,957
  • 96
  • 316
  • 522
12

Sourcing runs shell commands in your current shell. When you source inside of a script like you are doing above, you are affecting the environment for that script, but when the script exits, the environment changes are undone, as they've effectively gone out of scope.

If your intent is to run shell commands in the virtualenv, you can do that in your script after sourcing the activate script. If your intent is to interact with a shell inside the virtualenv, then you can spawn a sub-shell inside your script which would inherit the environment.

Mattie
  • 20,280
  • 7
  • 36
  • 54
8

Here is the script that I use often. Run it as $ source script_name

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

activate
san1512
  • 914
  • 1
  • 9
  • 16
6

You can also do this using a subshell to better contain your usage - here's a practical example:

#!/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"

This style is especially helpful when

  • a different version of commandA or commandC exists under /opt/bin
  • commandB exists in the system PATH or is very common
  • these commands fail under the virtualenv
  • one needs a variety of different virtualenvs
ti7
  • 16,375
  • 6
  • 40
  • 68
  • 1
    Don't forget to double quote the `$(...)` or you'll be missing spaces and tabs contained in the output. – Eric Jun 08 '20 at 09:13
  • 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 Jun 08 '20 at 09:15
  • 1
    `PATH=$PATH:/opt/bin` needs proper quoting to handle paths with spaces and tabs. – Eric Jun 08 '20 at 09:17
  • 1
    @Eric Thanks, though you can use the `edit` button below posts to suggest changes to them! Further, let it be known that while it is often a requirement and important for safety, anyone who knowingfully adds `IFS` chars to `PATH` is a terrorist. – ti7 Jun 08 '20 at 19:30
5

What does sourcing the bash script for?

  1. If you intend to switch between multiple virtualenvs or enter one virtualenv quickly, have you tried virtualenvwrapper? It provides a lot of utils like workon venv, mkvirtualenv venv and so on.

  2. If you just run a python script in certain virtualenv, use /path/to/venv/bin/python script.py to run it.

iMom0
  • 12,493
  • 3
  • 49
  • 61
  • Actually, I would like to call `workon ...` from a bash script. (Because I want to execute further stuff afterwards every time on its startup.) Can't find a way to make it work, though. – Daniel B. Jul 10 '20 at 22:39
5

As others already stated, what you are doing wrong is not sourcing the script you created. When you run the script just like you showed, it creates a new shell which activates the virtual environment and then exits, so there are no changes to your original shell from which you ran the script.

You need to source the script, which will make it run in your current shell.

You can do that by calling source shell.sh or . shell.sh

To make sure the script is sourced instead of executed normally, its nice to have some checks in place in the script to remind you, for example the script I use is this:

#!/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

It's not bulletproof but it's easy to understand and does its job.

DennyiCZ
  • 51
  • 2
  • 2
1

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 :)

1

When I was learning venv I created a script to remind me how to activate it.

#!/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

This has the advantage that it changes the prompt.

Alexx Roche
  • 3,151
  • 1
  • 33
  • 39
1

As stated in other answers, when you run a script, it creates a sub-shell. When the script exits, all modifications to that shell are lost.

What we need is actually to run a new shell where the virtual environment is active, and not exit from it. Be aware, this is a new shell, not the one in use before you run your script. What this mean is, if you type exit in it, it will exit from the subshell, and return to the previous one (the one where you ran the script), it won't close your xterm or whatever, as you may have expected.

The trouble is, when we exec bash, it reads its rc files (/etc/bash.bashrc, ~/.bashrc), which will change the shell environment. The solution is to provide bash with a way to setup the shell as usual, while additionnally activate the virtual environment. To do this, we create a temporary file, recreating the original bash behavior, and adding a few things we need to enable our venv. We then ask bash to use it instead of its usual rc files.

A beneficial side-effect of having a new shell "dedicated" to our venv, is that to deactivate the virtual environment, the only thing needed is to exit the shell. I use this in the script exposed below to provide a 'deactivate' option, which acts by sending a signal to the new shell (kill -SIGUSR1), this signal is intercepted (trap ...) and provoke the exit from the shell. Note: i use SIGUSR1 as to not interfere with whatever could be set in the "normal" behavior.

The script i use:

#!/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
Domovoy
  • 11
  • 4
1

I simply added this into my .bashrc-personal config file.

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
}
Magomed Shamaev
  • 198
  • 2
  • 5