4

If I want to return the path of a given executable, I can run:

which mysql

Which returns for example:

/usr/bin/mysql

I'd like to return only:

/usr/bin

How can I do that?

7
executable=mysql

executable_path=$(command -v -- "$executable") &&
  dirname -- "$executable_path"

(don't use which).

Of course, that won't work if $executable is a shell builtin, function or alias. I'm not aware of any shell where mysql is a builtin. It won't be a function or alias unless you defined them earlier, but then you should know about it. An exception to that could be bash which supports exported functions.

$ bash -c 'command -v mysql'
/usr/bin/mysql
$ mysql='() { echo test;}' bash -c 'command -v mysql'
mysql
  • 1
    +1, but this would be even better IMO with a short explanation of why one shouldn't use which. – a CVn Aug 1 '13 at 15:04
  • @MichaelKjörling - I attempted to explain why which is bad in my answer. Let me know what you think. – slm Aug 1 '13 at 15:47
  • @slm Nice! +1 to you too. – a CVn Aug 1 '13 at 18:03
  • Would that it were so simple! Sometimes this prints .. (Because there's an alias, which which isn't aware of.) Zsh has the very convenient =mysql syntax, but unfortunately it doesn't stack with ${…##*/} or …:h. – Gilles 'SO- stop being evil' Aug 1 '13 at 23:59
  • @Gilles, you can do ${${:-mysql}:c:h} though. ksh and zsh have whence -p to return paths of commands even if there's a builtin/alias/function for it, but then that might not be what you want as it's not what the shell would invoke. – Stéphane Chazelas Aug 2 '13 at 6:59
4

which

In general this command should be avoided.

Why?

Because it uses your current environment when constructing the $PATH that it's going to evaluate when looking for executables. This can lead to confusion when dealing with shell scripts and such, that will potentially be using the $PATH as constructed by your ~/.bashrc and ~/.bash_profile files when executed.

For example:

# path to start
$ echo $PATH
/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/sam/bin

# let's change it
$ export PATH=/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin

$ echo $PATH
/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin

# simulate a new env. for a shell script
$ bash -lc "echo \$PATH"
/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/sam/bin

Notice that the shell script if executed would have the original environment, not the current one!

type

You can also use the command type to locate executables within your environment:

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

Or to see everything, use the -a switch:

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

type will show you aliases as well as the location. For finding the directory though, @StephaneChazelas solution of using command is best.

whereis

You can also use the command whereis but this doesn't use your environment, it has a hardcoded set of directories that it looks in. See man whereis for more details.

locate

This one might confuse people but some distros include a tool that runs from a cron job, typically daily, that scans the entire disk and builds a little database file which you can then search using the locate command.

  • The cron job is called /etc/cron.daily/mlocate.cron on my Fedora 14 system.
  • The tool doing the scanning is called /usr/bin/updatedb.
  • The DB file is located here: /var/lib/mlocate/mlocate.db.

Using what I'd consider a bit of a hack you can coax locate to only look for files that are named ls as follows:

$ locate -b '\ls'
/bin/ls
/usr/share/javadoc/java-1.6.0-openjdk/api/org/w3c/dom/ls
/usr/share/javadoc/java-1.6.0-openjdk/jre/api/plugin/dom/org/w3c/dom/ls
/usr/share/locale/l10n/ls
/var/cache/abrt-di/usr/src/debug/gcc-4.5.1-20100924/libjava/classpath/external/w3c_dom/org/w3c/dom/ls
/var/cache/abrt-di/usr/src/debug/gcc-4.5.1-20100924/libjava/classpath/gnu/xml/dom/ls

The above works because you're \ is a globbing character, so this disables the implicit replacement of ls with *ls*.

See the locate man page for more details.

whatis

A lesser known tool is called whatis. This one is similar to locate, so it's backed by a DB. It is useful for finding out commands however:

$ whatis ls
ls                   (1)  - list directory contents
ls                   (1p)  - list directory contents

The tool is invoked from a cron job, /etc/cron.daily/makewhatis.cron. However, makewhatis builds its database from the man pages on your system.

You can see more about it in the makewhatis man pages.

Just the directory name?

You can use the command dirpath against any of the output produced by the commands I mentioned above to get just the directory component.

dirpath does no checking so you can give it just about any type of path to a file and it will give you the directory piece:

$ dirname /some/path/to/a/script
/some/path/to/a
  • I don't understand your first section about "why not using which". I've created a separate question for it as we need to have a canonical one here. Feel free to add your answer there. – Stéphane Chazelas Aug 1 '13 at 23:03
  • @StephaneChazelas - will do. Didn't see this before. Are you suggesting the entire answer or just that section? Also do I copy/paste or move it somehow? – slm Aug 1 '13 at 23:07
  • @StephaneChazelas - just saw your Q&A. Show off 8-). – slm Aug 1 '13 at 23:39
1

Its rather simple with xargs:

which mysql | xargs dirname
0

dirname is simplest way of achieving this:

$ which mysql
/usr/bin/mysql
$ dirname `which mysql`
/usr/bin
0

As others have explained, don't use which.

You can write a little shell function that iterates over the PATH variable.

which-directory () (
  IFS=:
  set -g
  for d in $PATH; do
    if [ -z "$d" ]; then d=.; fi
    if [ -x "$d/$1" ]; then printf '%s\n' "$d"; return; fi
  done
  return 1
)

Alternatively, you can call command -v. This reports aliases and functions as well as external commands. In case you have an alias or function mysql that calls the external command of the same name with additional parameters, you can call command -v in a newly-started shell that doesn't have any functions or aliases defined. Note that this does the right thing only if the alias or function calls the external command of the same name. The following two one-liners are equivalent (assuming you have no directory or executable file name on your PATH containing a newline).

dirname -- "$(sh -c 'command -v "$0"' -- mysql)"
sh -c 'command -v "$0"' -- mysql | sed -e 's!.*/!!'

In zsh, this one-liner does the same thing:

echo =mysql(:h)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.