201

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?

2
  • 16
    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. Commented Oct 29, 2012 at 13:04
  • 3
    @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
    Commented Oct 29, 2012 at 14:50

12 Answers 12

133

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"
7
  • 2
    for windows c:\tutorial>.\env\Scripts\activate
    – max4ever
    Commented Apr 11, 2014 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! Commented Jan 27, 2016 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
    Commented Aug 6, 2016 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
    Commented Dec 5, 2017 at 18:43
  • 12
    I'm quite new to bash etc. Can you expand this example so that it shows the full script?
    – AljoSt
    Commented Jul 5, 2018 at 16:19
131

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.

7
  • 6
    or even just ". venv.sh"
    – outmind
    Commented Apr 17, 2016 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
    – user4805123
    Commented Feb 8, 2018 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. ...
    – user4805123
    Commented Feb 8, 2018 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? Commented Nov 27, 2018 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. Commented Nov 27, 2018 at 17:52
19

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
6
  • 8
    technnically you're spawning a subshell. It's not necessarily a problem, but you should spell that out for the OP.
    – richo
    Commented Oct 30, 2012 at 4:37
  • It worked, but i had to give permission to my "activate" file before. Commented Dec 15, 2014 at 14:03
  • 1
    This works in 2019! On macos I just had to change the /bin/bash to /usr/bin/env bash
    – valem
    Commented Jan 17, 2019 at 16:38
  • Works in Ubuntu 18.04 AWS EC2 in 2020. I wonder how do I deactivate using the same logic? Commented Mar 26, 2020 at 19:16
  • You deactivate from the subshell with exit or Ctrl+d Commented Apr 10, 2020 at 12:46
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.

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
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
4
  • 1
    Don't forget to double quote the $(...) or you'll be missing spaces and tabs contained in the output.
    – Eric
    Commented Jun 8, 2020 at 9: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
    Commented Jun 8, 2020 at 9:15
  • 1
    PATH=$PATH:/opt/bin needs proper quoting to handle paths with spaces and tabs.
    – Eric
    Commented Jun 8, 2020 at 9: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
    Commented Jun 8, 2020 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.

1
  • 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.
    Commented Jul 10, 2020 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.

2

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

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
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.