Small bash script to enhance virtual environments on directory navigation
No shebang
It doesn't make sense to execute this as a program - it's intended to be sourced into an interactive shell.  So we shouldn't have the #! line.
Quote expansions
$1 could expand into multiple words here:
    if [ -d ~/projects/$1 ]; then
        cd ~/projects/$1;

and here:
    if [ $1 == &quot;-&quot;] || [ -d $1 ]; then

Send errors to stderr
    echo &quot;Directory $1 does not exist!&quot; &gt;&amp;2
    #                                   ^^^ IMPORTANT

Don't ask questions when used non-interactively
funcCheckVirtualEnvironment can misbehave when standard input is not a terminal.  We should test that case and return early:
test -r &quot;venv/bin/activate&quot; || return 0;
if ! test -t 0
then echo &quot;Warning: not activating virtual environment&quot; &gt;&amp;2
     return 0   # or 1 if this should be a failure
fi
read -p &quot;Execute this project's activation script? [y/N]&quot; -n 1 -r; echo
[[ REPLY =~ [Yy] ]] || return 0;

I've transformed some of the other if and else above into early returns, to reduce the nesting depth - I find that easier to read.  Also, I've capitalised N in the prompt, to provide the usual indication of the default.  And we know that $REPLY is a single character, so we can lose the anchors.
Let cd do its own checking instead
There's a possible race between checking for the existence of a directory and actually changing into it, so it's easier and safer to simply check whether cd succeeded:
command cd &quot;$@&quot; &amp;&amp; test -d 'venv' &amp;&amp; funcCheckVirtualEnvironment

This also allows cd to produce the error message (in the user's preferred $LANGuage).
Consider the exit status of the command
If we cd but there's no venv directory at the destination, that should be considered a success:
command cd &quot;$@&quot; || return $?
if test -d &quot;venv&quot;
then funcCheckVirtualEnvironment
else true
fi

Actually, there's no need to check for the existence of venv/, given that funcCheckVirtualEnvironment does that itself, and should succeed if it's not present:
command cd &quot;$@&quot; &amp;&amp; funcCheckVirtualEnvironment

Similarly, in cdp -r, instead of return 0, a plain return is better, as that will result in the return value of the last executed command (funcCheckVirtualEnvironment in this case).
Use standard directory completion
It's bad practice to parse the output of ls.  Instead, we can ask compgen to give us directory names; we can eliminate absolute paths and subdirectories by excluding anything containing /:
_cdp()
{
        COMPREPLY=( $(cd ~/projects &amp;&amp; compgen -X '*/*' -d $2) )
}


My version
funcCheckVirtualEnvironment()
{
    test -r 'venv/bin/activate' || return 0;
    if ! test -t 0
    then
        # Not a terminal - don't ask questions
        echo &quot;Warning: not activating virtual environment&quot; &gt;&amp;2
        return 0   # or 1 if this should be a failure
    fi

    read -p &quot;Execute this project's activation script? [y/N]&quot; -n 1 -r; echo
    [[ REPLY =~ [Yy] ]] || return 0;
    source venv/bin/activate || return $?
    
    test -e 'requirements.txt' || return 0;
    read -p &quot;Install any missing PyPi packages? [y/N]&quot; -n 1 -r; echo
    [[ REPLY =~ [Yy] ]] || return 0;
    pip install -r requirements.txt
}

funcCDP()
{
    # Add more flags in future, maybe
    case &quot;$1&quot; in
    &quot;-r&quot; )
        funcCheckVirtualEnvironment
        return
        ;;
    esac

    cd ~/projects/&quot;$1&quot; &amp;&amp; funcCheckVirtualEnvironment
}

funcCD()
{
    command cd &quot;$@&quot; &amp;&amp; funcCheckVirtualEnvironment
}

_cdp()
{
    COMPREPLY=( $(cd ~/projects &amp;&amp; compgen -X '*/*' -d $2) )
}
complete -F _cdp cdp
alias cdp=funcCDP
alias cd=funcCD