postmodern/chruby

Non-interactive `chruby-exec` fails with `zsh:1: command not found: chruby`

amomchilov opened this issue · 4 comments

Description

chruby.sh is typically sourced from ~/.zshrc, which is only run in interactive shells. For non-interactive shells, chruby-exec calls zsh without the -i:

else exec "$SHELL" -l -c "$command"

This causes the ~/.zshrc to never be sourced, and thus, for chruby to be unavailable.

Steps To Reproduce

Steps to reproduce the bug:

  1. Install the latest release (0.39.0 right now) with brew install chruby

  2. Run chruby-exec with a non-terminal STDIN. E.g. with:

    echo "abc" | chruby-exec "ruby-3.3.0" -- ruby --version

Expected Behavior

Run the command successfully, (in this example, ruby --version). It works correctly if you drop the echo "abc" |:

$ chruby-exec "ruby-3.3.0" -- ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]

This works fine because it hits this other branch, which include -i and runs ~/.zshrc:

if [[ -t 0 ]]; then exec "$SHELL" -i -l -c "$command"

This also works on the latest master branch, so it looks like it's a packaging/release issue:

$ brew uninstall chruby && brew install chruby --head
$ echo "abc" | chruby-exec "ruby-3.3.0" -- ruby --version
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23]

Actual Behavior

$ echo "abc" | chruby-exec "ruby-3.3.0" -- ruby --version
zsh:1: command not found: chruby

Notes

https://github.com/postmodern/chruby/releases/tag/v0.3.9 was release in April 19, 2023, but for some reason, it doesn't include this change to chruby-exec from 2014. I confirmed this in 2 ways:

  1. Inspecting bin/chruby-exec of the chruby-0.3.9.tar.gz downloaded right from the release page
  2. Inspecting /opt/homebrew/bin/chruby-exec installed by homebrew.

It looks to me like the release process just didn't package up this file correctly.

Environment

$ bash --version
GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.2.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ zsh --version
zsh 5.9 (x86_64-apple-darwin23.0)
$ chruby --version
Untitled 10.sh: line 8: chruby: command not found
$ ruby --version
ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin23]
$ gem --version
3.0.3.1
$ gem env
RubyGems Environment:
  - RUBYGEMS VERSION: 3.0.3.1
  - RUBY VERSION: 2.6.10 (2022-04-12 patchlevel 210) [universal.arm64e-darwin23]
  - INSTALLATION DIRECTORY: /Library/Ruby/Gems/2.6.0
  - USER INSTALLATION DIRECTORY: /Users/Alex/.gem/ruby/2.6.0
  - RUBY EXECUTABLE: /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
  - GIT EXECUTABLE: /usr/bin/git
  - EXECUTABLE DIRECTORY: /usr/local/bin
  - SPEC CACHE DIRECTORY: /Users/Alex/.gem/specs
  - SYSTEM CONFIGURATION DIRECTORY: /Library/Ruby/Site
  - RUBYGEMS PLATFORMS:
    - ruby
    - universal-darwin-23
  - GEM PATHS:
     - /Library/Ruby/Gems/2.6.0
     - /Users/Alex/.gem/ruby/2.6.0
     - /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/gems/2.6.0
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - https://rubygems.org/
  - SHELL PATH:
     - /opt/homebrew/bin
     - /Users/Alex/.cargo/bin
     - /usr/local/bin
     - /System/Cryptexes/App/usr/bin
     - /usr/bin
     - /bin
     - /usr/sbin
     - /sbin
     - /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin
     - /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin
     - /var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin
     - /Library/Apple/usr/bin
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/lib
     - /Users/Alex/.antigen/bundles/marlonrichert/zsh-autocomplete-main
     - /Users/Alex/.antigen/bundles/marlonrichert/zsh-autocomplete-main/functions
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/git
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/last-working-dir
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/macos
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/sudo
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/alias-finder
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/rust
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/colored-man-pages
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/command-not-found
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/common-aliases
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/dircycle
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/encode64
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/extract
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/jsontools
     - /Users/Alex/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/swiftpm
     - /Users/Alex/.antigen/bundles/dracula/zsh-syntax-highlighting
     - /Users/Alex/.antigen/bundles/zsh-users/zsh-syntax-highlighting
     - /Applications/CodeRunner.app/Contents/SharedSupport/Developer/bin

@amomchilov Could you post your ~/.zshrc and /etc/zshrc files? I suspect there's extra logic there that is exiting if the shell is not in interactive mode. On Fedora Linux with zsh 5.9, if I added echo "loaded" to my ~/.zshrc, it shows that the configuration file is loaded when running zsh and zsh -i.

We cannot always specify -i because that would assume you are always running in an interactive shell, which might not always be the case (ex: running chruby-exec in a cronjob).

Also note that chruby-0.3.9 was indeed released 2014-11-23. GitHub releases are separate from the git tags, and I didn't start adding GitHub releases for git tags until much later.

Could you post your ~/.zshrc and /etc/zshrc files? I suspect there's extra logic there that is exiting if the shell is not in interactive mode.

My ~/.zshrc was pretty extensive, but I minimized it down to the bare minimum (and still reproduced the issue with the steps in the OP):

zsh_user_path=(
    "/opt/homebrew/bin"
)

export path=($zsh_user_path $path)

if [[ -f"/opt/homebrew/opt/chruby/share/chruby/chruby.sh" ]]; then
	source "/opt/homebrew/opt/chruby/share/chruby/chruby.sh"
fi

My /etc/zshrc is stock from macOS:

/etc/zshrc
# System-wide profile for interactive zsh(1) shells.

# Setup user specific overrides for this in ~/.zshrc. See zshbuiltins(1)
# and zshoptions(1) for more details.

# Correctly display UTF-8 with combining characters.
if [[ "$(locale LC_CTYPE)" == "UTF-8" ]]; then
    setopt COMBINING_CHARS
fi

# Disable the log builtin, so we don't conflict with /usr/bin/log
disable log

# Save command history
HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history
HISTSIZE=2000
SAVEHIST=1000

# Beep on error
setopt BEEP

# Use keycodes (generated via zkbd) if present, otherwise fallback on
# values from terminfo
if [[ -r ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR} ]] ; then
    source ${ZDOTDIR:-$HOME}/.zkbd/${TERM}-${VENDOR}
else
    typeset -g -A key

    [[ -n "$terminfo[kf1]" ]] && key[F1]=$terminfo[kf1]
    [[ -n "$terminfo[kf2]" ]] && key[F2]=$terminfo[kf2]
    [[ -n "$terminfo[kf3]" ]] && key[F3]=$terminfo[kf3]
    [[ -n "$terminfo[kf4]" ]] && key[F4]=$terminfo[kf4]
    [[ -n "$terminfo[kf5]" ]] && key[F5]=$terminfo[kf5]
    [[ -n "$terminfo[kf6]" ]] && key[F6]=$terminfo[kf6]
    [[ -n "$terminfo[kf7]" ]] && key[F7]=$terminfo[kf7]
    [[ -n "$terminfo[kf8]" ]] && key[F8]=$terminfo[kf8]
    [[ -n "$terminfo[kf9]" ]] && key[F9]=$terminfo[kf9]
    [[ -n "$terminfo[kf10]" ]] && key[F10]=$terminfo[kf10]
    [[ -n "$terminfo[kf11]" ]] && key[F11]=$terminfo[kf11]
    [[ -n "$terminfo[kf12]" ]] && key[F12]=$terminfo[kf12]
    [[ -n "$terminfo[kf13]" ]] && key[F13]=$terminfo[kf13]
    [[ -n "$terminfo[kf14]" ]] && key[F14]=$terminfo[kf14]
    [[ -n "$terminfo[kf15]" ]] && key[F15]=$terminfo[kf15]
    [[ -n "$terminfo[kf16]" ]] && key[F16]=$terminfo[kf16]
    [[ -n "$terminfo[kf17]" ]] && key[F17]=$terminfo[kf17]
    [[ -n "$terminfo[kf18]" ]] && key[F18]=$terminfo[kf18]
    [[ -n "$terminfo[kf19]" ]] && key[F19]=$terminfo[kf19]
    [[ -n "$terminfo[kf20]" ]] && key[F20]=$terminfo[kf20]
    [[ -n "$terminfo[kbs]" ]] && key[Backspace]=$terminfo[kbs]
    [[ -n "$terminfo[kich1]" ]] && key[Insert]=$terminfo[kich1]
    [[ -n "$terminfo[kdch1]" ]] && key[Delete]=$terminfo[kdch1]
    [[ -n "$terminfo[khome]" ]] && key[Home]=$terminfo[khome]
    [[ -n "$terminfo[kend]" ]] && key[End]=$terminfo[kend]
    [[ -n "$terminfo[kpp]" ]] && key[PageUp]=$terminfo[kpp]
    [[ -n "$terminfo[knp]" ]] && key[PageDown]=$terminfo[knp]
    [[ -n "$terminfo[kcuu1]" ]] && key[Up]=$terminfo[kcuu1]
    [[ -n "$terminfo[kcub1]" ]] && key[Left]=$terminfo[kcub1]
    [[ -n "$terminfo[kcud1]" ]] && key[Down]=$terminfo[kcud1]
    [[ -n "$terminfo[kcuf1]" ]] && key[Right]=$terminfo[kcuf1]
fi

# Default key bindings
[[ -n ${key[Delete]} ]] && bindkey "${key[Delete]}" delete-char
[[ -n ${key[Home]} ]] && bindkey "${key[Home]}" beginning-of-line
[[ -n ${key[End]} ]] && bindkey "${key[End]}" end-of-line
[[ -n ${key[Up]} ]] && bindkey "${key[Up]}" up-line-or-search
[[ -n ${key[Down]} ]] && bindkey "${key[Down]}" down-line-or-search

# Default prompt
PS1="%n@%m %1~ %# "

# Useful support for interacting with Terminal.app or other terminal programs
[ -r "/etc/zshrc_$TERM_PROGRAM" ] && . "/etc/zshrc_$TERM_PROGRAM"

We cannot always specify -i because that would assume you are always running in an interactive shell, which might not always be the case (ex: running chruby-exec in a cronjob).

Yeah, agreed. This seems like more of a defect in the zsh conventions, but that isn't something we can really change.

If zsh kept the same convention as ~/.bashrc (minimal, always run) vs ~/.bash_profile (more "bloat", for interactive shells only), then we could have just always used non-interactive shells, and rely on the path being set.

But since most people set their $PATH in ~/.zshrc... welp, idk what we can do to reasonably handle that.

I'm also totally confused as to what --head does to make this work, that 0.38.0 didn't.

Also note that chruby-0.3.9 was indeed released 2014-11-23. GitHub releases are separate from the git tags, and I didn't start adding GitHub releases for git tags until much later.

Ah, that explains that part.

FWIW there is ~/.zshenv which is always sourced. That's where I do PATH modifications.
And my ~/.zshrc is then for interative-only stuff like completion, etc.

Hey @eregon,

I agree that's the right approach in the abstract, but regrettably, that's just not the common convention with ZSH.

For example, Homebrew's official installation instructions used to suggest adding to your .zshrc. They only recently switched to using .zprofile in Sept 2023. There's a contention between what's "right" and what's most common in people's real-world usage.

To me, the most puzzling part here is why --head works. I would have expected it to fail with the same issue.