Homebrew, pyenv, nvm을 같이 쓰다 보면 같은 PATH를 서로 앞쪽에 넣으면서 brew는 보이는데 python이 안 바뀌거나, nvm은 설치했는데 node가 예전 경로를 계속 가리키는 식의 문제가 자주 생깁니다. 특히 macOS에서 기본 셸이 zsh라면 .zprofile과 .zshrc 역할을 나눠서 정리하는 편이 가장 빠릅니다.
예시는 macOS의 zsh 기준으로 잡았습니다. 핵심은 Homebrew처럼 로그인 환경에서 먼저 잡아두면 좋은 설정은 .zprofile에 두고, pyenv·nvm처럼 인터랙티브 셸에서 쓰는 버전 전환 설정은 주로 .zshrc에 모으는 것입니다. 이 순서만 정리해도 설치 오류와 실행 오류가 한 번에 줄어드는 경우가 많습니다.
Homebrew·pyenv·nvm PATH 정리 흐름을 한눈에 보여주는 대표 이미지
왜 Homebrew·pyenv·nvm을 같이 쓰면 PATH가 꼬일까
세 도구 모두 PATH에 관여하지만 목적이 조금씩 다릅니다. Homebrew는 패키지 관리자라서 실행 파일과 셸 보조 경로를 잡아주고, pyenv는 python, pip 같은 명령을 자기 shims로 먼저 받게 만들며, nvm은 셸 안에서 동작하는 함수로 로드된 뒤 현재 선택된 Node 경로를 반영합니다. 문제는 이 셋을 여러 파일에 중복으로 넣거나, 같은 도구의 예전 설정과 새 설정이 섞일 때 생깁니다.
Node 설치 기준이 아직 애매하다면 Node.js 설치 3가지 비교(2026): nvm vs Homebrew vs 공식 설치(pkg)처럼 먼저 어떤 방식으로 관리할지 정해두는 편이 좋습니다. Homebrew로도 Node를 설치하고 nvm으로도 버전을 바꾸기 시작하면, 어느 쪽이 기본 node인지 헷갈리기 쉬워집니다.
세 도구가 PATH에 끼어드는 방식이 다르다
Homebrew는 설치된 prefix를 기준으로 bin, sbin, completion 경로를 셸에 반영합니다. pyenv는 ~/.pyenv/shims를 PATH 앞쪽에 두고, 같은 shims 경로가 여러 번 들어가면 정리해 주는 방식으로 동작합니다. nvm은 실행 파일이 아니라 sourced shell function에 가깝기 때문에, 경로 자체보다 먼저 “어느 파일에서 로드되었는가”가 중요합니다.
zsh 파일 읽는 순서를 모르면 같은 설정을 여러 곳에 넣게 된다
zsh는 모든 실행에서 .zshenv를 읽고, 로그인 셸에서는 .zprofile을 읽은 뒤 인터랙티브 셸에서 .zshrc를 읽습니다. 그래서 자주 쓰는 별칭이나 버전 관리 초기화는 .zshrc에, 로그인 환경에서 먼저 잡아야 할 PATH 성격의 설정은 .zprofile에 두는 편이 덜 꼬입니다. 반대로 .zshenv에 무거운 초기화나 출력이 들어가면 예기치 않은 오류가 생기기 쉽습니다.
읽는 순서(대표 흐름) 1) ~/.zshenv 2) ~/.zprofile # 로그인 셸 3) ~/.zshrc # 인터랙티브 셸 4) ~/.zlogin # 로그인 셸
수정 전에 먼저 확인할 것
가장 먼저 할 일은 “어디에 무엇이 이미 들어 있는지”를 찾는 것입니다. PATH 문제는 새 줄을 추가하는 것보다, 기존 중복 라인을 지우는 쪽이 더 중요할 때가 많습니다. 특히 예전 bash 설정이 남아 있거나, 설치 가이드마다 다른 줄을 복사해 넣은 상태라면 같은 초기화가 두 번씩 실행되고 있을 가능성이 큽니다.
cp ~/.zshrc ~/.zshrc.bak 2>/dev/null cp ~/.zprofile ~/.zprofile.bak 2>/dev/null cp ~/.zshenv ~/.zshenv.bak 2>/dev/null grep -nE 'brew shellenv|pyenv|nvm|PATH=' ~/.zshenv ~/.zprofile ~/.zshrc ~/.zlogin 2>/dev/null
먼저 지워볼 중복 패턴
brew shellenv가 .zprofile과 .zshrc에 동시에 있거나, eval "$(pyenv init - zsh)"와 수동 shims PATH 추가가 여러 파일에 흩어져 있으면 우선 정리 대상입니다. nvm도 예전 bash용 줄이 .bashrc에만 있고 현재 zsh에서는 비어 있는 경우가 흔합니다. 지금 실제로 쓰는 셸이 zsh라면 zsh 쪽 파일을 기준으로 먼저 정상화하는 편이 낫습니다.
.zshenv에 넣지 않는 편이 좋은 것
.zshenv는 모든 zsh 실행에서 읽히기 때문에 무거운 초기화, 자동 전환, 출력이 생기는 설정을 넣기 좋은 자리가 아닙니다. pyenv와 nvm 초기화를 이 파일에 몰아 넣으면 터미널뿐 아니라 여러 비대화형 실행까지 영향을 받아서 문제를 키우는 경우가 있습니다. PATH 충돌을 정리할 때 .zshenv는 비워 두거나 정말 기본 환경 변수만 남기는 편이 안전합니다.
가장 덜 꼬이는 권장 배치
정리 기준은 단순합니다. Homebrew는 먼저, pyenv와 nvm은 나중입니다. macOS에서 Homebrew 경로를 먼저 안정적으로 잡고, 그다음 인터랙티브 셸에서 pyenv와 nvm을 로드하면 충돌 지점을 많이 줄일 수 있습니다. 아래 예시는 가장 무난한 기본형입니다.
~/.zprofile 예시
# Homebrew: 로그인 셸에서 먼저 경로를 잡음 if [ -x /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)" elif [ -x /usr/local/bin/brew ]; then eval "$(/usr/local/bin/brew shellenv)" fi
~/.zshrc 예시
# pyenv export PYENV_ROOT="$HOME/.pyenv" [[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH" command -v pyenv >/dev/null 2>&1 && eval "$(pyenv init - zsh)" # nvm export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
이렇게 나누면 Homebrew가 먼저 정상 경로를 잡고, pyenv는 그 위에서 Python 관련 명령을 shims로 받으며, nvm은 인터랙티브 셸에서 Node 버전 전환을 맡게 됩니다. 중요한 점은 같은 줄을 다른 파일에 다시 넣지 않는 것입니다. 한 번 정했으면 같은 역할의 코드는 한 자리에서만 관리하는 편이 좋습니다.
로그인 셸이나 GUI 앱에서 pyenv 버전이 안 먹는 경우에는 .zprofile에 아래 한 줄만 추가로 테스트해 볼 수 있습니다. 이 방식은 pyenv 전체 셸 기능이 아니라 shims 쪽 반영이 필요한 상황에서 특히 유용합니다.
command -v pyenv >/dev/null 2>&1 && eval "$(pyenv init --path)"
zsh 시작 파일 순서와 PATH 정리 우선순위를 설명하는 보조 이미지
아직 꼬여 있다면 이 순서로 확인하면 된다
설정을 저장한 뒤에는 터미널을 완전히 새로 열거나 exec zsh로 다시 시작하는 편이 깔끔합니다. 그다음 PATH 순서와 실제 명령 해석 결과를 같이 봐야 합니다. 눈으로 파일만 보는 것보다 현재 셸이 무엇을 먼저 잡는지 확인하는 쪽이 훨씬 정확합니다.
exec zsh print -l $path type -a brew type -a python python3 pip type -a node npm command -v nvm pyenv version nvm current
Python이 안 바뀌면 먼저 볼 것
pyenv global을 바꿨는데도 python이나 python3가 Homebrew 경로나 시스템 Python을 가리킨다면, 대부분은 shims가 PATH 앞쪽에 오지 않았거나 셸이 다시 로드되지 않은 경우입니다. 이럴 때는 수동으로 ~/.pyenv/shims를 여러 군데 추가하기보다, pyenv 초기화 줄을 한 번만 남기고 중복 PATH 수정을 지우는 쪽이 더 안정적입니다.
nvm이 안 보이면 먼저 볼 것
nvm은 실행 파일이 아니라 셸 함수라서 which nvm보다 command -v nvm으로 확인하는 편이 맞습니다. 설치는 됐는데 “command not found”가 뜬다면 .zshrc에 nvm 로드 줄이 실제로 들어 있는지, 저장 후 새 셸이 열렸는지부터 확인하면 됩니다. 예전 bash 설정만 남아 있고 zsh 쪽이 비어 있는 경우도 흔합니다.
Homebrew의 node와 nvm의 node를 같이 기본값으로 두지 않는 편이 낫다
Homebrew는 패키지 관리자 역할에 집중시키고, Node 버전 전환은 nvm 하나로 맡기는 편이 관리가 쉽습니다. 둘을 동시에 기본 node로 쓰기 시작하면 프로젝트마다 버전이 달라질 때 경로 해석이 자꾸 달라집니다. 반대로 Python 쪽도 pyenv를 쓴다면 Python 버전 선택은 pyenv 기준으로 통일하는 편이 덜 헷갈립니다.
VSCode 터미널에서만 증상이 다르게 보인다면 터미널 시작 셸과 PATH 표시 순서를 함께 점검하는 것이 빠릅니다. 같은 설정이어도 앱 안의 통합 터미널에서 읽는 환경이 달라 보일 수 있어서, 이런 경우는 VSCode 터미널 기본기 2026: zsh 설정 + 폰트/한글 깨짐/경로(PATH) 문제처럼 셸 시작 방식부터 같이 보는 편이 정리가 잘 됩니다.
정리하면 수정 순서는 이렇게 보면 된다
첫째. 현재 쓰는 zsh 파일에 무엇이 들어 있는지 찾고 중복 라인을 지웁니다. 둘째. Homebrew는 .zprofile에 두고 로그인 셸에서 먼저 경로를 안정화합니다. 셋째. pyenv와 nvm은 .zshrc에 모아서 인터랙티브 셸 기준으로 정리합니다. 넷째. 새 셸을 열고 type -a, command -v, print -l $path로 실제 해석 결과를 확인합니다.
결국 PATH 문제는 도구가 많아서 생긴다기보다, 같은 도구의 설정이 여러 파일에 흩어져서 생기는 경우가 많습니다. 파일 역할을 나눠서 한 번 정리해 두면 이후에는 Homebrew 업데이트, pyenv 버전 추가, nvm 버전 전환을 해도 훨씬 덜 흔들립니다.
참고자료(외부링크)
zsh가 어떤 파일을 어떤 순서로 읽는지, Homebrew 경로를 어디에 두는지, pyenv와 nvm 초기화를 어떤 형태로 넣는지 공식 설명을 한 번에 다시 확인할 수 있습니다.
설치 자체가 꼬인 상태라면 본문 설정만 손보는 것보다, 먼저 설치 기준 글을 다시 보는 편이 더 빠를 때도 있습니다.
자주 묻는 질문
터미널 PATH 문제는 새 도구를 더 설치하기보다, 이미 들어 있는 초기화 줄을 역할별로 나누는 것만으로도 풀리는 경우가 많습니다. 셸 파일을 한 번 정리해 두면 이후 설치와 버전 전환도 훨씬 단순해집니다.
댓글