4
# which mkdir
/bin/mkdir
# which mkdi
# 

How can I get the path of the e.g.: mkdir's binary without knowing the name of the binary file? (command). So that which mkdi would output the /bin/mkdir too.

4

In zsh:

echo $path/mkdi*(N)

In other shells, for human consumption:

set -f; IFS=:
for x in $PATH; do set +f; ls $x/mkdi* 2>/dev/null; done
  • 3
    @LanceBaynes, this is a better answer than mine. – glenn jackman Jul 21 '11 at 2:00
  • Good, but not grep-like. no regex here. – Philomath Jul 21 '11 at 15:28
  • @Philomath In ksh, bash and zsh, wildcards are regexps (with an unusual syntax). – Gilles Jul 21 '11 at 15:40
  • ~something like this – LanceBaynes Jul 26 '11 at 6:08
4

locate

locate mkdi

Update: this will restrict the output to files in the $PATH

locate mkdi | 
while read filename; do 
  [[ ":$PATH:" == *:$(dirname $filename):* ]] && echo $filename
done
  • 3
    locate searches the whole file-system based on a data-base, unlike which which searches the files in $PATH as per the current state. – Philomath Jul 20 '11 at 18:59
  • On my system that returns thousands of files that happen to be in a project folder with that string fragment as part of the path. Not a replacement for which. – Caleb Jul 20 '11 at 19:55
  • A slightly better way in practice is locate bin/mkdi. Still, it looks in public parts of the filesystem (or whatever updatedb indexes), without considering $PATH. – Gilles Jul 20 '11 at 21:27
  • Note that if you want to search grep-like (regex), you have to pass -r or --regex to locate. It's still database based, though. – Philomath Jul 21 '11 at 15:31
4

run-parts can do exactly that, given the right parameters. Here is a simple wrapper I wrote around it:

#!/bin/bash
# swhich - search the path (like which) but with support for regular expressions
# "swhich '.*ch$' '^wh'" to find all in path ending with 'ch' and all starting with 'wh'
set -f; IFS=:
for all in $PATH
  do
  set +f
  for each in "$@"
    do
    run-parts --list --regex "$each" "$all"
  done
done
2

If you're not sure of the executable you're looking for, you can also query the man database with the apropos command, for example apropos dir will list commands commonly used when working with directories, find the command you need, pass it to which as normal.

  • IF the executable has a manpage AND you have done makewhatis recently. – Philomath Jul 20 '11 at 22:20
2

compgen is a BASH builtin that generates completions for a given string. The -c switch completes command names in the path:

$ compgen -c mkd
mkdirhier
mkdir
mkdiskimage
mkdir

Note: mkdir is on here twice because it's in both /bin and /usr/bin on my system.

Programmable completion is cool. It will let you do things like this:

$ shopt -s progcomp; complete -c which # set up progcomp
$ which mkd<ALT-*>
$ which mkdir mkdirhier mkdiskimage

...which may be closer to what you're looking for.

0

Here's yet another way of getting the binary path (if the prefix is known) by using printf and file name globbing in Bash.

mywhich() (
  bin="$1"
  PATH="${PATH// /\\ }"  # escape spaces
  #eval 'echo "$PATH"'
  #printf '%s\n' "printf '%s\n' ${PATH//://${bin}* }/${bin}*" | bash -s -f -O nullglob --
  result="$(printf '%s\n' "printf '%s\n' ${PATH//://${bin}* }/${bin}*" | bash -s +f -O nullglob --)"
  [[ -n "$result" ]] && printf '%s\n' "$result" && exit 0 || exit 1
)

mywhich mkdi
0

The following approach lists all full paths to the matched binaries using only Bash builtins:

# mywhich version 2
mywhich() ( # using a subshell the parent shell is not affected by set & shopt, ...
  bin="$1"
  set +f
  shopt -s nullglob
  IFS=':' read -a array <<<"$PATH"
  IFS=""
  result="$( printf '%s\n' ${array[@]/%//${bin}*} )"
  [[ -n "$result" ]] && printf '%s\n' "$result" && exit 0 || exit 1
)

mywhich mkdi
0

Look for man pages

As you are searching commands, instead of matching the names of the command binaries, you could match names of man pages as an alternative; This could make sense because man already provides this feature by default:

$ man --sections=1,8 --where --all --regex '^mkdi' 
/usr/share/man/man1/mkdir.1.gz
/usr/share/man/man1/mkdirhier.1.gz
/usr/share/man/man1/mkdiskimage.1.gz

Using sed to extract the command names only:

$ man --sections=1,8 --where --all --regex '^mkdi' | \
    sed -r 's/.*\/([^/]+)\.[^.]+\.[^.]+$/\1/'
mkdir
mkdirhier
mkdiskimage

And running which for the command names too:

$ man --sections=1,8 --where --all --regex '^mkdi' | \
    sed -r 's/.*\/([^/]+)\.[^.]+\.[^.]+$/\1/' | xargs which 
/bin/mkdir
/usr/bin/mkdirhier
/usr/bin/mkdiskimage

The patterns are regular expressions, so the ^ is added to match only at the beginning of the name.
For a pattern syntax similar to a shell glob, replace --regex by --wildcard,
like man ... --wildcard 'mkdi*' | ....

As a shell function:

whichmatch() {       
    man --sections=1,8 --where --all --regex "$@" |
        sed -r 's/.*\/([^/]+)\.[^.]+\.[^.]+$/\1/' |
        xargs which
}

used like:

whichmatch '^mkdi'

For approximate matching of command names, whichman could be used in place of man.

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.