5

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.

  • 3
    I don't understand how this is a duplicate: I am asking how to find a program within the system's default $PATH, not the user's customised $PATH. The other question is asking why it is good practice to avoid which. Other than involving the same tools, they are completely different questions. – georgebrock Nov 23 '13 at 22:49
  • @StephaneChazelas - thanks, I mistakenly thought this was a duplicate! – slm Nov 26 '13 at 03:17

4 Answers4

5

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.)

Mikel
  • 57,299
  • 15
  • 134
  • 153
  • In theory command -pv sounds perfect, but in practice both bash and zsh both return the same result for command -pv and command -v. – georgebrock Nov 23 '13 at 21:35
  • I also can't use solution 3: This script will be used by ruby developers using rvm and chruby, and by Python developers who don't have rvm installed. – georgebrock Nov 23 '13 at 21:35
  • @georgebrock Even after doing source ~/.rvm/scripts/rvm? Can you double check your $PATH? I definitely get different results for command -pv and command -v. – Mikel Nov 25 '13 at 02:05
  • What shell do you use? Under bash (4.2.45) in normal or sh mode, command -pv ruby returns the rvm ruby not the system ruby. Under zsh (5.0.2), command -pv ruby does not recognise the -pv flag and returns command not found -pv. – georgebrock Nov 25 '13 at 09:47
  • 1
    More precisely, command -p uses a value of $PATH (see getconf PATH) that guarantees POSIX commands are used. Not much to do with the system's $PATH (whatever that is). – Stéphane Chazelas Nov 25 '13 at 16:08
  • Thanks, getconf PATH is another way that is more general than command -pv. Unfortunately it also defaults to /usr/bin:/bin, so it wouldn't find packages installed system wide by "make install" or MacPorts, etc. Added a paragraph pointing out this limitation. – Mikel Nov 25 '13 at 16:35
  • Another approach could be to select the first ruby that is owned by root. (find "$ruby" -prune -user root -print | grep -q .), as that's how a ruby installed by an administrator would typically be set up (though not guaranteed). – Stéphane Chazelas Nov 25 '13 at 20:50
3

Explicitly setting the value of $PATH in the sub-shell resolves the problem:

env -i sh -c "PATH=\$PATH which ruby"

Note that the $ in $PATH is escaped, which means that $PATH in the sub-shell command isn't substituted with the parent shell's value of $PATH before the command is executed (this could also be achieved using single quotes).

I'd be really interested in knowing why this is necessary in this case.

3

You generally don't want to use the which command. In Bash you should be using the type or command commands. See this Q&A for reasons why, titled: Why not use “which”? What to use then?.

Examples

$ type -a ls
ls is aliased to `ls --color=auto'
ls is /bin/ls

or this:

$ type -a vim
vim is /usr/bin/vim

or this:

$ command -v ls
alias ls='ls --color=auto'

or this:

$ command -v vim
/usr/bin/vim

From Bash's man page.

excerpt on type

type [-aftpP] name [name ...]
     With no options, indicate how each name would be interpreted if used as a
     command name.  If the -t option is used, type  prints a  string  which  is
     one  of  alias, keyword, function, builtin, or file if name is an alias,
     shell reserved word, function, builtin, or disk file, respectively.  If the
     name is not found, then nothing is  printed, and  an exit status of false
     is returned.  If the -p option is used, type either returns the name of the
     disk file that would be executed if name were specified as a command name,
     or nothing if ``type -t name'' would  not  return file.   The  -P  option
     forces a PATH search for each name, even if ``type -t name'' would not
     return file.  If a command is hashed, -p and -P print the hashed value, not
     necessarily the file that appears first in PATH.  If  the -a option is used,
     type prints all of the places that contain an executable named name.  This
     includes aliases and functions, if and only if the -p option is not also
     used.  The table of hashed commands  is  not  consulted  when using  -a.
     The -f option suppresses shell function lookup, as with the command
     builtin.  type returns true if all of the arguments are found, false if
     any are not found.

excerpt on command

   command [-pVv] command [arg ...]
         Run command with args suppressing the normal shell function lookup.
         Only builtin commands or commands found in the PATH are executed.  If
         the -p option is given, the search for command is performed using a
         default value for PATH that  is  guaranteed to find all of the standard
         utilities.  If either the -V or -v option is supplied, a description of
         command is printed.  The -v option causes a single word indicating the
         command or file name used to invoke command to  be displayed; the -V
         option produces a more verbose description.  If the -V or -v option is
         supplied, the exit status is 0 if command was found, and 1 if not.
         If neither option is supplied and an error  occurred  or command
         cannot be  found, the exit status is 127.  Otherwise, the exit status
         of the command builtin is the exit status of command.
slm
  • 369,824
  • The answer you linked to recommends command -v which works exactly as I want it to when invoked with env -i command -v ruby. Thanks! – georgebrock Nov 23 '13 at 14:23
1

Speecify the PATH setting of simply "/usr/bin" (or set PATH to suit) as a parameter to env. env -i ignores the environment it inherits, the following PATH setting will enable which to find /usr/bin/ruby.

env -i PATH="/usr/bin" sh -c "which ruby" 
suspectus
  • 6,010
  • Thanks @suspectus, but I'm explicitly trying not to inherit the parent shell's $PATH and use the system's default $PATH instead. The parent shell does have /usr/bin in its path, but it also has ~/.rvm/bin which I am trying to avoid. – georgebrock Nov 23 '13 at 11:39
  • ok understood. Would putting /usr/bin first in the PATH settings (preceding ~/.rvm/bin) achieve what you want? – suspectus Nov 23 '13 at 11:41
  • I want to be able to find the system's or package manager's ruby binary in this one specific script (in a portable way, which means I can't assume it's in /usr/bin), while continuing to use the version from ~/.rvm/bin in general. – georgebrock Nov 23 '13 at 11:44
  • ok. Answer replaced. – suspectus Nov 23 '13 at 11:46
  • Thanks. This is still a bit problematic: The sub shell already has a $PATH that includes /usr/bin (see the part of the question about env -i sh -c "echo \$PATH") but in the sub shell which does not work. – georgebrock Nov 23 '13 at 11:51
  • Thanks for your help (up-voted because you got me half way there, and self-answered with the full solution) – georgebrock Nov 23 '13 at 12:00
  • How is this answer different from echo /usr/bin/ruby? – Mikel Nov 23 '13 at 20:23