pipenvで作った複数の仮想環境を管理し、不要になったら削除する話
pipenvを使って開発を進めるにつれ、仮想環境が増えていきます。PCのディスク容量が減ったり、開発が一段落したなどの理由で仮想環境をまとめて削除したいことがあります。しかしながら、pipenvでは複数の仮想環境の操作はできません。
そこで、仮想環境の一覧を表示するスクリプトと、ソースコードをローカルから削除して不要になった仮想環境を削除するシェルスクリプトをまとめました。
コードについて
サンプルコードを https://github.com/Niccari/pipenv-virtualenvs-maintainer に置いています。
※ 本記事のコードはmacOS 12.3~12.5でのみ動作検証しています。
説明に関する前提
~/devに下記のPythonプロジェクトがあり、それぞれ仮想環境を作成済みとします。
- bar-project
- foo-project
- hoge-project
1. 仮想環境の一覧を表示する
pipenvの仮想環境は以下のパスに構築されています[1]。
- mac, linux:
~/.local/share/virtualenvs/1ソースコードの親ディレクトリ名-ユニークな文字列
[2] - windows:
%USERPROFILE%/.virtualenvs/1ソースコードの親ディレクトリ名-ユニークな文字列
仮想環境のパス下について、対応するソースコードのフルパスが.project内に記入されています。これにより、ソースコードのパスと仮想環境のパスを対応づけることができます。
bar-projectでのソースコードのパス表示例
$ cat ~/.local/share/virtualenvs/bar-project-NTU4ZjA2/.project
/Users/some-user/dev/bar-project
以上により、仮想環境の一覧(ソースコードのパスと仮想環境のパスの対応表)を以下のスクリプトで表示できます。
仮想環境の一覧表示スクリプト
#!/bin/bash
function get_virtualenvs_path {
path=$1
# Check if filesystem is based on Windows or not.
if [[ "${path}" =~ ^[a-zA-Z]:.*$ ]] || [[ "${path}" =~ ^\/mnt\/[a-zA-Z]\/.*$ ]]; then
echo "${USERPROFILE}/.virtualenvs"
else
echo "${HOME}/.local/share/virtualenvs"
fi
}
VIRTUALENVS_PATH=$(get_virtualenvs_path $0)
PROJECT_BASENAME=".project"
project_paths=$(ls ${VIRTUALENVS_PATH}/*/${PROJECT_BASENAME})
messages="PROJECT_PATH\tCODE_PATH"
for project_path in ${project_paths[@]}; do
code_path=$(cat $project_path)
messages="$messages\n$project_path\t$code_path"
done
echo -e $messages
仮想環境の一覧の例
PROJECT_PATH CODE_PATH
/Users/some-user/.local/share/virtualenvs/foo-project-7KRJjpxu/.project /Users/some-user/dev/foo-project
/Users/some-user/.local/share/virtualenvs/hoge-project-Pw7hUalW/.project /Users/some-user/dev/hoge-project
/Users/some-user/.local/share/virtualenvs/bar-project-NTU4ZjA2/.project /Users/some-user/dev/bar-project
このうち、CODE_PATHが存在しない仮想環境は削除することができます。そのため、以下スクリプトにより削除できる仮想環境とそうでないものをリストアップできます。なお、出力結果ではUNLINKED列で仮想環境を削除可能かどうか、VIRTUALENV_SIZE列で仮想環境のファイルサイズを表示しています。
仮想環境の一覧表示スクリプト(UNLINKED列、VIRTUALENV_SIZE列を追加)
#!/bin/bash
function get_virtualenvs_path {
path=$1
# Check if filesystem is based on Windows or not.
if [[ "${path}" =~ ^[a-zA-Z]:.*$ ]] || [[ "${path}" =~ ^\/mnt\/[a-zA-Z]\/.*$ ]]; then
echo "${USERPROFILE}/.virtualenvs"
else
echo "${HOME}/.local/share/virtualenvs"
fi
}
VIRTUALENVS_PATH=$(get_virtualenvs_path $0)
PROJECT_BASENAME=".project"
project_paths=$(find ${VIRTUALENVS_PATH} -name ${PROJECT_BASENAME} -type f)
messages="PROJECT_PATH\tCODE_PATH\tUNLINKED\tVIRTUALENV_SIZE"
for project_path in ${project_paths[@]}; do
code_path=$(cat $project_path)
if [ ! -d $code_path ]; then
unlinked="o"
else
unlinked="-"
fi
virtualenv_path=${project_path//\/${PROJECT_BASENAME}}
virtualenv_size=$(du -hs $virtualenv_path | cut -f 1)
messages="$messages\n$project_path\t$code_path\t$unlinked\t$virtualenv_size"
done
echo -e $messages
仮想環境の一覧の例(UNLINED列、VIRTUALENV_SIZE列を追加)
PROJECT_PATH CODE_PATH UNLINKED VIRTUALENV_SIZE
/Users/some-user/.local/share/virtualenvs/foo-project-7KRJjpxu/.project /Users/some-user/dev/foo-project - 24M
/Users/some-user/.local/share/virtualenvs/hoge-project-Pw7hUalW/.project /Users/some-user/dev/hoge-project - 105M
/Users/some-user/.local/share/virtualenvs/bar-project-NTU4ZjA2/.project /Users/some-user/dev/bar-project - 201M
各プロジェクトの内、/Users/some-user/dev/hoge-project
と/Users/some-user/dev/bar-project
を削除すると以下の様になります。両プロジェクトについて、UNLINKED列が"o"となっていることが確認できます。
仮想環境の一覧の例(hoge-project, bar-project削除後)
PROJECT_PATH CODE_PATH UNLINKED VIRTUALENV_SIZE
/Users/some-user/.local/share/virtualenvs/foo-project-7KRJjpxu/.project /Users/some-user/dev/foo-project - 24M
/Users/some-user/.local/share/virtualenvs/hoge-project-Pw7hUalW/.project /Users/some-user/dev/hoge-project o 105M
/Users/some-user/.local/share/virtualenvs/bar-project-NTU4ZjA2/.project /Users/some-user/dev/bar-project o 201M
2. 削除可能な仮想環境を削除する
削除可能な仮想環境を以下のスクリプトにより削除できます。処理の流れですが、1.節同様にソースコードのない仮想環境のリストを抽出して、それらを表示・確認の上で削除するようにしています。
#!/bin/bash
function get_virtualenvs_path {
path=$1
# Check if filesystem is based on Windows or not.
if [[ "${path}" =~ ^[a-zA-Z]:.*$ ]] || [[ "${path}" =~ ^\/mnt\/[a-zA-Z]\/.*$ ]]; then
echo "${USERPROFILE}/.virtualenvs"
else
echo "${HOME}/.local/share/virtualenvs"
fi
}
VIRTUALENVS_PATH=$(get_virtualenvs_path $0)
PROJECT_BASENAME=".project"
project_paths=$(find ${VIRTUALENVS_PATH} -name ${PROJECT_BASENAME} -type f)
deletable_virtualenv_paths=""
found_projects_message="## Deletable projects ##"
for project_path in ${project_paths[@]}; do
code_path=$(cat $project_path)
if [ ! -d $code_path ]; then
virtualenv_path=${project_path//\/${PROJECT_BASENAME}}
virtualenv_size=$(du -hs $virtualenv_path | cut -f 1)
deletable_virtualenv_paths="$virtualenv_path $deletable_virtualenv_paths"
found_projects_message="$found_projects_message\n$virtualenv_path <-x- $code_path(size: $virtualenv_size)"
fi
done
deletable_virtualenv_paths=( $deletable_virtualenv_paths )
if [ ${#deletable_virtualenv_paths[@]} -le 0 ]; then
echo "no deletable virtualenvs found."
exit 0
else
echo -e $found_projects_message
fi
read -p "Delete unused virtualenvs? (y/N): " yn
if [[ $yn = [yY] ]]; then
for virtualenv_path in ${deletable_virtualenv_paths[@]}; do
if ! rm -i -rf $virtualenv_path ; then
echo "failed to remove $virtualenv_path."
exit 1
fi
done
echo "succeeded."
else
echo "aborted."
fi
仮想環境の削除例
$ bash delete_unused_projects.sh
## Deletable projects ##
/Users/some-user/.local/share/virtualenvs/hoge-project-Pw7hUalW <-x- /Users/some-user/dev/hoge-project(size: 105M)
/Users/some-user/.local/share/virtualenvs/bar-project-NTU4ZjA2 <-x- /Users/some-user/dev/bar-project(size: 201M)
Delete unused virtualenvs? (y/N): y
succeeded.
$ bash delete_pipenv_removable_projects.sh
no deletable virtualenvs found.
Appendix: 各仮想環境のpythonバージョンをリストアップしたい場合
各仮想環境下にあるpyvenv.cfg
には以下の情報が記載されています。ここからversion_infoを切り出すとOKです。python --version
を都度実行してもOKですが、それだとpython立ち上げに一定の時間を要してしまいます。
home = /opt/homebrew/opt/python@3.11/bin
implementation = CPython
version_info = 3.11.2.final.0
virtualenv = 20.21.0
include-system-site-packages = false
base-prefix = /opt/homebrew/opt/python@3.11/Frameworks/Python.framework/Versions/3.11
base-exec-prefix = /opt/homebrew/opt/python@3.11/Frameworks/Python.framework/Versions/3.11
base-executable = /opt/homebrew/opt/python@3.11/bin/python3.11
prompt = sandbox
# (途中省略)
echo -e "PROJECT_PATH\tCODE_PATH\tPYTHON_VERSION\tUNLINKED\tVIRTUALENV_SIZE"
for project_path in ${project_paths[@]}; do
# (途中省略)
python_version=$(cat ${virtualenv_path}/pyvenv.cfg | grep version_info | cut -d "=" -f 2)
echo -e "$project_path\t$code_path\t$python_version\t$unlinked\t$virtualenv_size"
done
Discussion