#!/usr/bin/env bash # Summary: Configure the shell environment for pyenv # Usage: eval "$(pyenv init [-|--path] [--no-push-path] [--no-rehash] [])" # pyenv init --install [] # pyenv init --detect-shell [] set -e [ -n "$PYENV_DEBUG" ] && set -x # Provide pyenv completions if [ "$1" = "--complete" ]; then echo - echo --path echo --install echo --no-push-path echo --no-rehash echo --detect-shell echo bash echo fish echo ksh echo pwsh echo zsh exit fi mode="help" while [ "$#" -gt 0 ]; do case "$1" in -) mode="print" ;; --path) mode="path" ;; --install) mode="install" ;; --detect-shell) mode="detect-shell" ;; --no-push-path) no_push_path=1 ;; --no-rehash) no_rehash=1 ;; *) shell="$1" ;; esac shift done # If shell is not provided, detect it. if [ -z "$shell" ]; then if shell=$(tr '\0' ' ' 2>/dev/null &2 } # True when tips/install should keep the portable default root form. function pyenv_root_is_default() { [[ ${PYENV_ROOT} == "${HOME}/.pyenv" ]] } # Escape a value for inclusion inside double quotes in a POSIX shell script. function escape_for_double_quoted_posix() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//\$/\\\$}" s="${s//\`/\\\`}" printf '%s' "$s" } # Escape a value for inclusion inside double quotes in a fish script. function escape_for_double_quoted_fish() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//\$/\\\$}" printf '%s' "$s" } # Escape a value for inclusion inside single quotes in a PowerShell script. function escape_for_single_quoted_pwsh() { local s="$1" # Bash 3.2 (macOS / CI docker): avoid \' in ${//} replacement — # it becomes a literal backslash. Double single-quotes via a var instead. local q="'" s="${s//${q}/${q}${q}}" printf '%s' "$s" } # Emit the PYENV_ROOT assignment line for tips/--install (posix|fish|pwsh). # Portable default keeps $HOME / $Env:HOME; custom roots are quoted per target shell. function print_pyenv_root_assignment() { case "$1" in posix ) if pyenv_root_is_default; then echo 'export PYENV_ROOT="$HOME/.pyenv"' else printf 'export PYENV_ROOT="%s"\n' "$(escape_for_double_quoted_posix "${PYENV_ROOT}")" fi ;; fish ) if pyenv_root_is_default; then # Keep master's expandable $HOME form (fish expands unquoted $HOME). echo 'set -Ux PYENV_ROOT $HOME/.pyenv' else printf 'set -Ux PYENV_ROOT "%s"\n' "$(escape_for_double_quoted_fish "${PYENV_ROOT}")" fi ;; pwsh ) if pyenv_root_is_default; then echo '$Env:PYENV_ROOT="$Env:HOME/.pyenv"' else # Single-quoted PowerShell literal: no $ / backtick expansion. printf "\$Env:PYENV_ROOT='%s'\n" "$(escape_for_single_quoted_pwsh "${PYENV_ROOT}")" fi ;; * ) echo "pyenv: internal error: unknown shell family for PYENV_ROOT assignment: $1" >&2 return 1 ;; esac } function print_posix_shell_setup() { print_pyenv_root_assignment posix echo '[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"' echo 'eval "$(pyenv init - '$shell')"' } function print_fish_shell_setup() { echo 'pyenv init - fish | source' } function print_fish_user_path_setup() { print_pyenv_root_assignment fish echo 'if functions -q fish_add_path' echo ' test -d $PYENV_ROOT/bin; and fish_add_path $PYENV_ROOT/bin' echo 'else' echo ' test -d $PYENV_ROOT/bin; and set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths' echo 'end' } function print_pwsh_shell_setup() { print_pyenv_root_assignment pwsh echo 'if (Test-Path -LP "$Env:PYENV_ROOT/bin" -PathType Container) {' echo ' $Env:PATH="$Env:PYENV_ROOT/bin:$Env:PATH" }' echo 'iex ((pyenv init -) -join "`n")' } function expand_home_path() { local path="$1" printf '%s\n' "${path/#\~/$HOME}" } function install_shell_startup_files() { if [[ -z $HOME ]]; then echo "pyenv: HOME must be set to configure shell startup files" >&2 return 1 fi detect_profile local files=() local lines=() local profile_path rc_path setup case "$shell" in bash | zsh | ksh | ksh93 | mksh ) rc_path="$(expand_home_path "$rc")" profile_path="$(expand_home_path "$profile")" setup="$(print_posix_shell_setup)" files=("$rc_path") lines=("$setup") if [[ $profile_path != "$rc_path" ]]; then files+=("$profile_path") lines+=("$setup") fi ;; fish ) rc_path="$(expand_home_path "$rc")" files=("$rc_path") lines=("$(print_fish_shell_setup)") ;; pwsh ) rc_path="$(expand_home_path "$rc")" files=("$rc_path") lines=("$(print_pwsh_shell_setup)") ;; * ) echo "pyenv: cannot automatically configure startup files for $shell" >&2 return 1 ;; esac local index for ((index = 0; index < ${#files[@]}; index++)); do check_startup_file "${files[$index]}" || return 1 done if [[ $shell == fish ]]; then install_fish_user_paths || return 1 fi for ((index = 0; index < ${#files[@]}; index++)); do append_lines "${files[$index]}" "${lines[$index]}" done } function check_startup_file() { local file="$1" local grep_status if [[ ! -e $file ]]; then return 0 fi if [[ ! -f $file || ! -r $file ]]; then echo "pyenv: failed to inspect $file" >&2 return 1 fi if grep -Fi pyenv "$file" >/dev/null; then echo "pyenv: cannot automatically apply changes to $file: it appears to already contain Pyenv-related code." >&2 echo "pyenv: review the file's contents and apply changes manually if necessary." >&2 echo "pyenv: run \`pyenv init $shell\` to see the suggested setup." >&2 return 1 else grep_status=$? if [[ $grep_status == 1 ]]; then return 0 fi echo "pyenv: failed to inspect $file" >&2 return "$grep_status" fi } function install_fish_user_paths() { local fish_setup if ! command -v fish >/dev/null; then echo "pyenv: fish is not available to configure fish universal variables" >&2 return 1 fi fish_setup="$(print_fish_user_path_setup)" if ! fish -c "$fish_setup"; then echo "pyenv: failed to configure fish universal variables" >&2 return 1 fi } function append_lines() { local file="$1" local lines="$2" local dir last_char dir="${file%/*}" if [[ $dir != "$file" ]]; then mkdir -p "$dir" fi if [[ -s $file ]]; then last_char="$(tail -c 1 "$file")" || return 1 if [[ -n $last_char ]]; then echo >> "$file" fi fi printf '%s\n' "$lines" >> "$file" } function init_dirs() { mkdir -p "${PYENV_ROOT}/"{shims,versions} } function print_path() { # if no_push_path is set, guard the PATH manipulation with a check on whether # the shim is already in the PATH. if [ -n "$no_push_path" ]; then case "$shell" in fish ) echo 'if not contains -- "'"${PYENV_ROOT}/shims"'" $PATH' print_path_prepend_shims echo 'end' ;; pwsh ) echo 'if ( $Env:PATH -notmatch "'"${PYENV_ROOT}/shims"'" ) {' print_path_prepend_shims echo '}' ;; * ) echo 'if [[ ":$PATH:" != *'\':"${PYENV_ROOT}"/shims:\''* ]]; then' print_path_prepend_shims echo 'fi' ;; esac else case "$shell" in fish ) echo 'while set pyenv_index (contains -i -- "'"${PYENV_ROOT}/shims"'" $PATH)' echo 'set -eg PATH[$pyenv_index]; end; set -e pyenv_index' print_path_prepend_shims ;; pwsh ) echo '$Env:PATH="$(($Env:PATH -split '"':'"' | where { -not ($_ -match '"'${PYENV_ROOT}/shims'"') }) -join '"':'"')"' print_path_prepend_shims ;; * ) # Some distros (notably Debian-based) set Bash's SSH_SOURCE_BASHRC compilation option # that makes it source `bashrc` under SSH even when not interactive. # This is inhibited by a guard in Debian's stock `bashrc` but some people remove it # in order to get proper environment for noninteractive remote commands # (SSH provides /etc/ssh/sshrc and ~/.ssh/rc for that but no-one seems to use them for some reason). # This has caused an infinite `bashrc` execution loop for those people in the below nested Bash invocation (#2367). # --norc negates this behavior of such a customized Bash. echo 'PATH="$(bash --norc -ec '\''IFS=:; paths=($PATH); ' echo 'for i in ${!paths[@]}; do ' echo 'if [[ ${paths[i]} == "'\'\'"${PYENV_ROOT}/shims"\'\''" ]]; then unset '\'\\\'\''paths[i]'\'\\\'\''; ' echo 'fi; done; ' echo 'echo "${paths[*]}"'\'')"' print_path_prepend_shims ;; esac fi } function print_path_prepend_shims() { case "$shell" in fish ) echo 'set -gx PATH '\'"${PYENV_ROOT}/shims"\'' $PATH' ;; pwsh ) echo '$Env:PATH="'"${PYENV_ROOT}"'/shims:$Env:PATH"' ;; * ) echo 'export PATH="'"${PYENV_ROOT}"'/shims:${PATH}"' ;; esac } function print_env() { case "$shell" in fish ) echo "set -gx PYENV_SHELL $shell" ;; pwsh ) echo '$Env:PYENV_SHELL="'"$shell"'"' ;; * ) echo "export PYENV_SHELL=$shell" ;; esac } function print_completion() { completion="${_PYENV_INSTALL_PREFIX}/completions/pyenv.${shell}" if [ -r "$completion" ]; then case "$shell" in pwsh ) echo "iex (gc $completion -Raw)" ;; * ) echo "source '$completion'" ;; esac fi } function print_rehash() { if [ -z "$no_rehash" ]; then case "$shell" in pwsh ) echo '& pyenv rehash' ;; * ) echo 'command pyenv rehash' ;; esac fi } function print_shell_function() { commands=(`pyenv-commands --sh`) case "$shell" in fish ) echo \ 'function pyenv set command $argv[1] set -e argv[1] switch "$command" case '"${commands[*]}"' source (pyenv "sh-$command" $argv|psub) case "*" command pyenv "$command" $argv end end' ;; pwsh ) cat <