How to use which(1) with the system’s default $PATH

environment-variablespathwhich

I would like to use which with the system's default path, ignoring any embellishments from the user's shell configuration files.

Motivation

I am attempting to write a script to find the system's Ruby binary. Many Ruby developers use a Ruby version manager, which adds something like ~/.rvm/bin to the start of their $PATH. I want to bypass this and use the version of Ruby that came with the system, or was installed via the system's package manager.

Current solution

Here's what I've tried so far:

$ env -i sh -c "which ruby"

This gives no output, and exits with 1. I would expect it to work though, because the path includes /usr/bin, and my system came with a Ruby binary at /usr/bin/ruby:

$ env -i sh -c "echo \$PATH"
/usr/gnu/bin:/usr/local/bin:/bin:/usr/bin:.
$ which -a ruby
# ...
/usr/bin/ruby

A few additional details:

  • env -s bash -c "which ruby" also doesn't find anything.
  • env -i zsh -c "which ruby" does find /usr/bin/ruby, but I can't depend on zsh.
  • Using the full path to which (to make sure I'm using the binary, not the shell built-in) doesn't make any difference.

My environment

I'm writing this in Bash on OS X, but would like it to be portable to other shells and operating systems.

Best Answer

command -pv uses a "default value for PATH".

$ which ruby
/home/mikel/.rvm/rubies/ruby-1.9.3-p484/bin/ruby

$ command -pv ruby
/usr/bin/ruby

Unfortunately that doesn't work in zsh, so based on Stephane's comment, we could use getconf PATH:

$ PATH=$(getconf PATH) which ruby

or use command -v in place of which, as recommended in Why not use "which"? What to use then?

$ PATH=$(getconf PATH) command -v ruby

The downside with these approaches is that if the system administrator installed a system-wide version into say /usr/local/bin or /opt/local/bin, and all users had that in PATH (e.g. via /etc/profile or /etc/environment or similar), the above probably wouldn't find it.

In your specific case, I'd suggest trying something specific to Ruby. Here's some ideas that might work:

  1. Filter out versions in the user's home directory (and relative paths):

    (
        IFS=:
        set -f
        for dir in $PATH; do
            case $dir/ in
                "$HOME/"*) ;;
                /*/)
                    if [ -f "$dir/ruby" ] && [ -x "$dir/ruby" ]; then
                        printf '%s\n' "$dir/ruby"
                        break
                    fi;;
            esac
        done
    )
    
  2. Filter out versions in the rvm directory

    (
        IFS=:
        set -f
        for dir in $PATH; do
            case $dir/ in
                "$rvmpath/"*) ;;
                /*/)
                    if [ -f "$dir/ruby" ] && [ -x "$dir/ruby" ]; then
                        printf '%s\n' "$dir/ruby"
                        break
                    fi;;
            esac
        done
    )
    
  3. Filter out writeable rubies (last resort, assumes not running as root)

    (
        IFS=:
        set -f
        for dir in $PATH; do
            case $dir/ in
                /*/)
                    ruby=$dir/ruby
                    if [ -f "$ruby" ] && [ -x "$ruby" ] && [ ! -w "$ruby" ]; then
                        printf '%s\n' "$ruby"
                        break
                    fi;;
            esac
        done
    )
    
  4. Ask rvm, chruby, etc.

    (
        rvm system
        chruby system
        command -v ruby
    )
    

The last way makes rvm select the default Ruby, but we do it in a subshell, so that the user's preferred Ruby is restored afterwards. (chruby part untested.)

Related Question