cd进入目录时如何自动激活virtualenvs

Posted

技术标签:

【中文标题】cd进入目录时如何自动激活virtualenvs【英文标题】:How to automatically activate virtualenvs when cd'ing into a directory 【发布时间】:2017-12-26 06:23:09 【问题描述】:

我的~/Documents 中有很多项目。我几乎只在 python 中工作,所以这些基本上都是 python 项目。每一个,例如~/Documents/foo 有自己的 virtualenv,~/Documents/foo/venv(它们总是被称为 venv)。每当我在项目之间切换时(每天大约 10 次),我都会这样做

deactivate
cd ..
cd foo
source venv/bin/activate

我已经厌倦了输入deactivatesource venv/bin/activate我正在寻找一种方法,只需 cd ../foo 并为我处理 virtualenv 操作。

我对@9​​87654321@ 很熟悉,在我看来这有点过分。据我所知,它似乎将您所有的 virtualenvs 移到了其他地方,并且增加了比它删除的复杂性更多的复杂性。 (欢迎提出不同意见!)

我对 shell 脚本不太熟悉。如果您可以推荐一个低维护脚本添加到我的~/.zshrc 来完成此操作,那将绰绰有余,但通过一些快速的谷歌搜索,我还没有找到这样的脚本。

我是zsh/oh-my-zsh 用户。 oh-my-zsh 似乎没有这个插件。这个问题的最佳答案是有人贡献了一个oh-my-zsh 插件来做到这一点。 (如果这里的答案乏善可陈,我可能会这样做。

【问题讨论】:

【参考方案1】:

在你的 .zshrc 中加入类似的东西

function cd() 
  if [[ -d ./venv ]] ; then
    deactivate
  fi

  builtin cd $1

  if [[ -d ./venv ]] ; then
    . ./venv/bin/activate
  fi

编辑:如 cmets cd 中所述,进入当前虚拟环境的子文件夹会停用它。一种想法可能是仅在 cd-ing 进入新环境时停用当前环境,例如

function cd() 
  builtin cd $1

  if [[ -n "$VIRTUAL_ENV" && -d ./venv ]] ; then
    deactivate
    . ./venv/bin/activate
  fi

这仍然可以改进,也许将其变成“提示命令”或文件夹名称上的attempting some prefix matching,以检查路径上某处是否存在虚拟环境,但我的 shell-fu 不够好。

【讨论】:

嗨@agnul。感谢您的回答!为什么是. ./venv/bin/activate 而不是source venv/bin/activate/ "。"相当于“source”,至少对于 zsh 和 bash [[ -d ./venv ]] 仅在当前目录是项目的根目录时才有效。更好地测试环境变量:[ -n "$VIRTUAL_ENV" ]. 正如 phd 指出的那样,如果我 cd 进入带有 virtualenv 的项目的子目录,我就会退出 virtualenv。但是,@phd 的解决方案根本不适合我。你们当中有人愿意为此提供帮助吗?当我在上面任何地方都有 virtualenv 的目录中时,如何留在 virtualenv 中? 仍在寻找这个问题的答案!【参考方案2】:

在您的 .bashrc 或 .zshrc 中添加以下内容

function cd() 
  builtin cd "$@"

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    ## If env folder is found then activate the vitualenv
      if [[ -d ./.env ]] ; then
        source ./.env/bin/activate
      fi
  else
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate
      parentdir="$(dirname "$VIRTUAL_ENV")"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        deactivate
      fi
  fi

即使有人进入子文件夹,此代码也不会停用 virtualenv。灵感来自@agnul 和@Gilles 的答案。

如果virtualenv是由pipenv制作的,那么请考虑this wiki page。

此外,为了增加安全性,请考虑direnv。

【讨论】:

刚刚看到如果您在使用 venv 的项目中并执行 cd ../other_proj,这不起作用 @aivision2020 澄清一下:两个项目中的虚拟环境文件夹的名称都是.env吗?此代码假定虚拟环境文件夹的文件夹名称为 .env @boliva 感谢您指出这一点。我已经相应地更新了代码。 @Gauthier 只需抓住ifelse(不包括)之间的第一个块,并将其放在倒数第二个fi 之后。 @Ari 是的,我将其表述为一个问题,但它更像是一个评论。看看我自己的答案。【参考方案3】:

您可以使用direnv,而不是编写自定义脚本。它不是 zsh 特定的解决方案(您可以尝试 zsh-autoenv),但它维护良好且易于与 zsh 一起使用。安装后,您需要将eval "$(direnv hook zsh)" 放在.zshrc 的末尾。那时你可以这样做:

$ source ~/.zshrc
$ cd foo
$ echo "layout python" > .envrc
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
$ direnv allow
direnv: loading .envrc
direnv: export +VIRTUAL_ENV ~PATH

现在你应该在你的 virtualenv 中了。您可以通过运行pip freeze 进行测试,以查看您的 virtualenv 特定软件包是否已安装。停用

$ cd ..
direnv: unloading

【讨论】:

【参考方案4】:

这是我的解决方案:

检查是否已经在当前活动的venv,在这种情况下什么也不做 如果有venv 文件夹,如果有则停用活动文件夹 不管有没有旧的venv,都可以激活。

在我的bash_aliases

function cd() 
    builtin cd "$@"

    if [ $(dirname "$VIRTUAL_ENV") == $(pwd) ] ; then
        # Already at the active virtual env
        return
    fi

    if [[ -d ./venv ]] ; then
        if type deactivate > /dev/null 2>&1 ; then
            printf "Deactivating virtualenv %s\n" "$VIRTUAL_ENV"
            deactivate
        fi

        source ./venv/bin/activate
        printf "Setting up   virtualenv %s\n" "$VIRTUAL_ENV"
    fi

【讨论】:

嗨@Gauthier。此代码仅在您进入目标虚拟环境的根目录时才有效。但是,如果你 cd 进入目标虚拟环境的任何子目录,它仍然会保持旧的虚拟环境处于活动状态。 @MS_ 没错,这不是我需要的。我想一直 cd 到 / 或者直到你找到一个 dir venv 不会太难?我考虑过,但只有在需要时才会这样做。不仅如此,它对目录的名称寄予厚望,而它应该真正尝试变得更聪明,并通过检查目录的内容来检测目录是否是 virtualenv 环境。但是您的回答也没有做任何事情,我错了吗? 进入非项目目录时不会停用(不一定重要,但我认为这样做很好)。【参考方案5】:

为了后代:我使用了@MS_ 的解决方案,但遇到了cding 直接从一个项目到另一个项目的问题,它会停用旧的 virtualenv,但不会激活新的 virtualenv。这是解决此问题的解决方案的略微修改版本:

# auto activate virtualenv
# Modified solution based on https://***.com/questions/45216663/how-to-automatically-activate-virtualenvs-when-cding-into-a-directory/56309561#56309561
function cd() 
  builtin cd "$@"

  ## Default path to virtualenv in your projects
  DEFAULT_ENV_PATH="./env"

  ## If env folder is found then activate the vitualenv
  function activate_venv() 
    if [[ -f "$DEFAULT_ENV_PATH/bin/activate" ]] ; then 
      source "$DEFAULT_ENV_PATH/bin/activate"
      echo "Activating $VIRTUAL_ENV"
    fi
  

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    activate_venv
  else
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate then run a new env folder check
      parentdir="$(dirname $VIRTUAL_ENV)"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        echo "Deactivating $VIRTUAL_ENV"
        deactivate
        activate_venv
      fi
  fi

【讨论】:

【参考方案6】:

这是没有cd'ing 的解决方案,将zsh 设置为setop auto_cd 将能够在没有cd 的情况下更改目录,只需键入目录名称并按Enter。 这是上述解决方案的基础:

    # auto activate virtualenv
# Modified solution based on https://***.com/questions/45216663/how-to-automatically-activate-virtualenvs-when-cding-into-a-directory/56309561#56309561
function auto_active_env() 

  ## Default path to virtualenv in your projects
  DEFAULT_ENV_PATH="./env"

  ## If env folder is found then activate the vitualenv
  function activate_venv() 
    if [[ -f "$DEFAULT_ENV_PATH/bin/activate" ]] ; then 
      source "$DEFAULT_ENV_PATH/bin/activate"
      echo "Activating $VIRTUAL_ENV"
    fi
  

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    activate_venv
  else
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate then run a new env folder check
      parentdir="$(dirname $VIRTUAL_ENV)"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        echo "Deactivating $VIRTUAL_ENV"
        deactivate
        activate_venv
      fi
  fi

chpwd_functions=($chpwd_functions[@] "auto_active_env")

【讨论】:

【参考方案7】:

这是(还)另一种自动激活虚拟环境的解决方案;它基于这里已经发布的一些答案。

这适用于任何虚拟环境名称或目录(不仅仅是./env./venv 等)。还支持子目录,以及cd-ing 到虚拟环境(父)文件夹的符号链接。

此代码搜索pyvenv.cfg 文件而不是特定的命名目录。如果在当前文件夹的子目录中找到一个,则会自动激活环境。一旦进入虚拟环境,该状态将一直保留,直到您移出父虚拟环境目录,此时环境被停用。

把它放在你的.bashrc.bash_profile 中。

function cd() 
  builtin cd "$@"

  if [[ -z "$VIRTUAL_ENV" ]] ; then
      # If config file is found -> activate the vitual environment
      venv_cfg_filepath=$(find . -maxdepth 2 -type f -name 'pyvenv.cfg' 2> /dev/null)
      if [[ -z "$venv_cfg_filepath" ]]; then
        return # no config file found
      fi

      venv_filepath=$(cut -d '/' -f -2 <<< $venv_cfg_filepath)
      if [[ -d "$venv_filepath" ]] ; then
        source "$venv_filepath"/bin/activate
      fi
  else
    # If the current directory is not contained 
    # within the venv parent directory -> deactivate the venv.
      cur_dir=$(pwd -P)
      venv_dir="$(dirname "$VIRTUAL_ENV")"
      if [[ "$cur_dir"/ != "$venv_dir"/* ]] ; then
        deactivate
      fi
  fi

我个人认为这是对这里很多解决方案的改进,因为它应该适用于任何虚拟环境

【讨论】:

【参考方案8】:

这是我的解决方案:

    如果未设置 VIRTUAL_ENV,则:
      检查我们是否在虚拟环境中 如果是,则激活它
    否则(VIRTUAL_ENV 已定义),检查当前文件夹是否以 $VIRTUAL_ENV 开头(删除/venv 部分)并验证是否存在停用命令
      停用环境

这是脚本:

function cd() 
  builtin cd $1

  if [[ -z "$VIRTUAL_ENV" ]]; then
    if [[ -d ./venv && -f ./venv/bin/activate ]]; then
      source ./venv/bin/activate
    fi
  elif [[ ! "$(pwd)" == $VIRTUAL_ENV:0:n-5* && ! -z "$(command -v deactivate)" ]]; then
    deactivate
  fi

注意:您需要将此添加到.bashrc。如果它不起作用,请检查您的 .profile 是否没有覆盖您的命令(它发生在我身上)

【讨论】:

这不支持从一个 venv 目录移动到另一个【参考方案9】:

基于@MS_的解决方案:

function cd() 
  builtin cd "$@"

  ## If env folder is found then activate the vitualenv
  if [[ -d ./venv ]] ; then
    source ./venv/bin/activate
  fi

  if [[ -n "$VIRTUAL_ENV" ]] ; then
    ## check the current folder belong to earlier VIRTUAL_ENV folder
    # if yes then do nothing
    # else deactivate
      parentdir="$(dirname "$VIRTUAL_ENV")"
      if [[ "$PWD"/ != "$parentdir"/* ]] ; then
        deactivate
      fi
  fi

【讨论】:

【参考方案10】:

最简单的选择(2019 年及以后)是将virtualenvwrapper 添加到您的~/.zshrc plugins

例如:

plugins=(
  git pip python brew virtualenvwrapper
)

【讨论】:

【参考方案11】:

如果不是direnv,你应该尝试类似autoenv。

第一个被认为是“轻量级”,而第二个被认为是“简单,更高质量的软件”,分别听各自的作者,谈论别人的项目。因此,在我看来,它们似乎是相当不错的选择,可以尝试两者!

无论如何,两者都在zsh shell 上进行了测试。 特别是autoenv,安装后使用起来真的很简单:

$ git clone git://github.com/inishchith/autoenv.git ~/.autoenv
$ echo 'source ~/.autoenv/activate.sh' >> ~/.bashrc

只需“跟随白兔”并尝试例如

$ mkdir project
$ echo "echo 'whoa'" > project/.env
$ cd project
whoa

"如果一个目录包含.env文件,当你cd进入它时会自动执行。启用时(设置AUTOENV_ENABLE_LEAVE为非空字符串),如果目录包含.env.leave文件,离开时会自动执行。”

查看https://github.com/inishchith/autoenv 以获得更详细的说明!...

【讨论】:

感谢您不仅推荐了一个库,还包括解释和命令行语法。这是 Stack Overflow 上的最佳实践,但许多新贡献者未能遵守。 我非常同意你的观点(不过,这是我的第一个答案!),再加上一点解释,这与@mc_kaiser 已经指出的类似。【参考方案12】:

这是一个仅限 zsh 的解决方案。

这是对daveruinseverything's answer 的改进,这是对MS_'s answer 的改进。

我们正在使用precmd 挂钩而不是覆盖cd

我们添加了另一个额外的功能。假设目录结构是

├── .venv
│   ├── bin
│   │   └── activate
├── subdir
│   ├── subdir1
│   │   ├── subdir2
│   │   │   └── test2.txt
│   │   └── test1.txt
│   └── test.txt
└── testing.py

如果你现在在 subdir2 中打开新终端,或者直接从其他地方 cd 到 subdir2,它会激活 venv。

解决办法是:

autoload -Uz add-zsh-hook
add-zsh-hook precmd automatically_activate_python_venv

function automatically_activate_python_env() 
  if [[ -z $VIRTUAL_ENV ]] ; then
    activate_venv
  else
    parentdir="$(dirname $VIRTUAL_ENV)"
    if [[ "$PWD"/ != "$parentdir"/* ]] ; then
      deactivate
      activate_venv
    fi
  fi


function activate_venv()   
  local d n
  d=$PWD
  
  until false 
  do 
  if [[ -f $d/.venv/bin/activate ]] ; then 
    source $d/.venv/bin/activate
    break
  fi
    d=$d%/*
    # d="$(dirname "$d")"
    [[ $d = *\/* ]] || break
  done

【讨论】:

如果您能根据您的回答评估我创建的插件的功能,那就太好了。更多信息here。欢迎请求请求! @DanielKaminskideSouza 我很高兴有人发现代码足够有用,可以用它制作插件。我感觉很好。非常感谢。 您的研究导致我相信迄今为止最好的解决方案。该插件是您研究的延续......我刚刚添加了一个可以使用任何环境名称的功能并进行了一些速度优化。【参考方案13】:

对于像我这样的python开发人员,我使用this plugin制作,在cding进入python项目时激活python虚拟环境,在cding进入另一个目录后它也会停用。

【讨论】:

【参考方案14】:

对于使用(或考虑使用)pyenv 的任何人,可以使用pyenv-virtualenv 插件非常方便地实现这一点,如here 所述。

基本上,您只需将.python-version 文件添加到指定virtualenv 名称的目录中。

【讨论】:

【参考方案15】:

类似于Jake's answer,但支持cd从一个virtualenv 到另一个virtualenv。在这种情况下,它会停用旧的 virtualenv,然后激活新的。

function cd() 
  builtin cd "$@"

  if [[ ! -z "$VIRTUAL_ENV" ]] ; then
    # If the current directory is not contained
    # within the venv parent directory -> deactivate the venv.
    cur_dir=$(pwd -P)
    venv_dir="$(dirname "$VIRTUAL_ENV")"
    if [[ "$cur_dir"/ != "$venv_dir"/* ]] ; then
      deactivate
    fi
  fi

  if [[ -z "$VIRTUAL_ENV" ]] ; then
    # If config file is found -> activate the vitual environment
    venv_cfg_filepath=$(find . -maxdepth 2 -type f -name 'pyvenv.cfg' 2> /dev/null)
    if [[ -z "$venv_cfg_filepath" ]]; then
      return # no config file found
    fi

    venv_filepath=$(cut -d '/' -f -2 <<< $venv_cfg_filepath)
    if [[ -d "$venv_filepath" ]] ; then
      source "$venv_filepath"/bin/activate
    fi
  fi

【讨论】:

【参考方案16】:

正如其他人所提到的,我过去曾使用过direnv。 Lyft 使用 aactivator 这个exact scenario。

一旦构建了 venv,它就必须被激活(添加到 $PATH)。我们使用 aactivator 来在用户每次进入服务目录时自动激活 venv,并在他们离开时停用。

【讨论】:

以上是关于cd进入目录时如何自动激活virtualenvs的主要内容,如果未能解决你的问题,请参考以下文章

用cmd 如何输入命令,进入文件夹

linux切换下一个目录自动打印

如何让cygwin启动时进入指定目录

Linux - 快速进入目录的方法

linux如何自动获取ip地址

Linux 命令行 进入目录“---”