which craft
Any sufficiently advanced magic is indistinguishable from technology.
(Niven's Law re Clark's Third Law)

An introduction to how the BASH shell figures
out what to run when you enter a command.


References

 Bash Reference Manual
3.2.1 Simple Commands
3.7.2 Command Search and Execution
4.2 Builtin Commands
3.3 Shell Functions
6.6 Aliases(What 3.7.2 did not tell you.)
 
TL;DR

NAME [OPTION]...
  1. If NAME contains "/", execute it as literal path.
  2. If NAME is an alias, substitute the alias value for NAME.
    If resulting NAME is different go to step 1.
  3. If NAME is a function, execute the function.
  4. If NAME is a BASH builtin, execute the builtin command.
  5. Search $PATH for NAME and run the first matching executable.


man which

NAME
       which - locate a command
SYNOPSIS which [options] commandname ...
DESCRIPTION which returns the pathnames of the files (or links) which would be executed in the current environment, had its arguments been given as commands in a strictly POSIX-conformant shell. It does this by searching the PATH for executable files matching the names of the arguments. It does not follow symbolic links.
OPTIONS -a Print all matching pathnames of each argument.

       --read-alias, -i
          Read aliases from stdin, reporting matching ones on stdout. This is
          useful in combination with using an alias for which itself.  For
          example:

             alias which='alias | which -i'

       --read-functions
          Read shell function definitions from stdin, reporting matching ones
          on stdout. This is useful in combination with using a shell func-
          tion for which itself.  For example:

             which() { declare -f | which --read-functions $@ }
             export -f which

       --version, -V
          Print version information on standard output.
       

basic which craft


$

which which
/usr/bin/which

$

which -a which
/usr/bin/which
/bin/which

$

which -a which | xargs stat -c %N
'/usr/bin/which' -> '/bin/which'
'/bin/which'

printf (practice target)


$

printf 'Hello %s\n' 'Mr. Bond'
Hello Mr. Bond

$

printf -v greeting 'Hello %s\n' James
$echo $greeting
Hello James

$

which printf
/usr/bin/printf

$

/usr/bin/printf -v greeting Aloha ; echo '$?'=$? greeting=$greeting

# WTF moment
-v/usr/bin/printf: warning: ignoring excess arguments, starting with ‘greeting’
$?=0 greeting=Hello James

builtins


$

builtin printf -v greeting Aloha ; echo '$?'=$? greeting=$greeting
$?=0 greeting=Aloha

$

builtin printf || echo '$?'=$?
printf: usage: printf [-v var] format [arguments]
$?=2

$

builtin which || echo '$?'=$?
bash: builtin: which: not a shell builtin
$?=1

functions


$

function printf { echo -n function:\ ; builtin printf "$@"; }
$printf 'Hello %s\n' Michael
function: Hello Michael

$

which printf
/usr/bin/printf

$

declare -f printf || echo '$?'=$?
printf ()
{
  echo -n function:\ ;
  builtin printf "$@"
}

$

declare -f which || echo '$?'=$?
$?=1

aliases


$

alias printf='echo -n alias:\ ; printf'
$printf 'Hello %s\n' Michael# WTF moment
alias: function: Hello Michael

$

alias printf='echo -n alias:\ ; /usr/bin/printf'
$printf 'Hello %s\n' Michael
alias: Hello Michael

$

which printf
/usr/bin/printf

$

alias printf || echo '$?'=$?
alias printf='echo -n alias:\ ; /usr/bin/printf'

$

alias which || echo '$?'=$?
bash: alias: which: not found
$?=1

Putting it all together (alias, declare -f, type -t, and which)


$

function which {   # Success !~}>
  local _fmt=%n _opt='' _all=continue _cmd
  case "$1" in
    -a) _fmt=%N _opt=-a _all=true; shift ;;
    -*) \builtin printf 'Illegal option %s\nUsage: which [-a] args\n' "$1"; return 2 ;;
  esac
  for _cmd in "$@"; do 
    \builtin alias "$_cmd" 2>/dev/null && $_all
    \builtin declare -f "$_cmd" && $_all
    \builtin "$_cmd" &>/dev/null ; [ $? -ne 1 ] && echo builtin $_cmd && $_all
    env which $_opt "$_cmd" | xargs stat -c $_fmt
  done }

$

function which {    # A longer but cleaner implementation
  local _fmt=%n _opt='' _cmd _type
  case "$1" in
    -a) _fmt=%N _opt=-a; shift ;;
    -*) \builtin printf 'Illegal option %s\nUsage: which [-a] args\n' "$1"; return 2 ;;
  esac
  for _cmd in "$@"; do
    for _type in $(type -t $_opt "$_cmd" | uniq); do
      case $_type in
        alias)    \builtin alias "$_cmd" 2>/dev/null ;;
        function) echo -n 'function '; \builtin declare -f "$_cmd" ;;
        file)     env which $_opt "$_cmd" | xargs -I{} stat -c $_fmt {} ;;
        *)        echo $_type $_cmd ;;
      esac
    done
  done }

$

type -at printf
# Facepalm
alias
function
builtin
file

$

which printf
alias printf='echo -n alias:\ ; /usr/bin/printf'

$

unalias printf
$which printf
function printf ()
{
    echo -n function:\ ;
    builtin printf "$@"
}

$

unset -f printf
$which -a printf
builtin printf
'/usr/bin/printf'

$

time -f%E sleep 5
# WTF moment
-f%E: command not found

$

builtin time
bash: builtin: time: not a shell builtin

$

which time
keyword time

$

which -a which
function which () 
{ 
    local _fmt=%n _opt='' _cmd _type;
    case "$1" in 
        -a)
            _fmt=%N _opt=-a;
            shift
        ;;
        -*)
            \builtin printf 'Illegal option %s\nUsage: which [-a] args\n' "$1";
            return 2
        ;;
    esac;
    for _cmd in "$@";
    do
        for _type in $(type -t $_opt "$_cmd" | uniq);
        do
            case $_type in 
                alias)
                    \builtin alias "$_cmd" 2> /dev/null
                ;;
                function)
                    echo -n 'function ';
                    \builtin declare -f "$_cmd"
                ;;
                file)
                    env which $_opt "$_cmd" | xargs -I{} stat -c $_fmt {}
                ;;
                *)
                    echo $_type $_cmd
                ;;
            esac;
        done;
    done
}
'/usr/bin/which' -> '/bin/which'
'/bin/which'