I learn a non-trivial number of things, because there’s Debian drama about them. The fact that the
which command is not part of any standard just joined the flock.
I’ve been using
which since my earliest Amiga 500 days to find the path of an executable. Recent Debian turmoil taught me that I can’t take neither the existence nor the shape of the command for granted on UNIX-like OSes, because it was never part of the POSIX (or any other AFAIK) standard.
TL;DR: If you want to be on the safe side, don’t rely on
which to find the location of an executable.
command -v instead. On an interactive command line,
type is also a great option.
which is widespread but not standardized. On Debian it’s just a shell script that they considered to remove from the essential package debianutils; making it an optional command whose presence on a Debian system isn’t given anymore.
The argument being that packagers should use standard tools, and if users insist on having a
which command, they can install one of the available implementations.
The lack of any standardization aside,
which in its barest form fails to handle builtins and aliases:
$ which ls; echo $? /bin/ls 0 $ which ll; echo $? # alias 1 $ which if; echo $? # shell builtin 1
Let me be clear that my point here is not about
which not ticking some bureaucratic boxes. The problem is that there’s many different
whichs, each with different options and behaviors. Just compare the arguments of the most common ones:
I’m also not trying to take away your toys – feel free to use whatever you like. I’m just pointing out compatibility problems across UNIX-like operating systems that could surprise you, and that
which could suddenly vanish from your Debian installation.
And I’m presenting alternatives!
POSIX-confirming shells are required to have a builtin confusingly called
command. Its function is to bypass any builtins, functions, or aliases and run the executable with the passed name.
For example in zsh:
$ alias ls="echo nope" $ ls nope $ command ls [directory listing]
This is useful in scripts if you want to ensure to get the proper command and not some convenience alias from the user1.
Pertinent to this post, though, is its option
-v that doesn’t run the command, but takes a list of names and tells you how the shell would interpret each name, if you’d run it without “wrapping” it with
$ command -v ls ll if echo /bin/ls alias ll='ls -l' if echo
As you can see, although the main purpose of
command is to literally execute a command and ignore functions and aliases,
command -v recognizes builtins and expands aliases on bash and zsh.
If you pass
-V (uppercase v), you get human-readable output:
$ command -V ls ll if echo ls is /bin/ls ll is an alias for ls -l if is a reserved word echo is a shell builtin
Therefore, if you want to write cross-platform shell scripts, stick to
command -v that works everywhere and has machine-readable output.
Unfortunately, my favorite shell fish has only very rudimentary support for
-V, no support for aliasing), so here’s a bonus tidbit:
type is another shell builtin that’s standardized, and does what you’d expect:
$ type ls ll if echo # output from zsh ls is /bin/ls ll is an alias for ls -l if is a reserved word echo is a shell builtin
One thing I like about
type is the (non-standard, but common)
-a argument that shows you all possible resolutions for a name:
$ type -a echo echo is a shell builtin echo is /bin/echo
On my beloved fish shell, I also get the full function definition:
$ type j j is a function with definition # Defined in /Users/hynek/.config/fish/conf.d/z.fish @ line 24 function j --description 'jump around' __z $argv end
type -p you can limit the output to proper executables (similar to
$ type -p brew /usr/local/bin/brew $ type -p ll -
type is human-readable, better supported on fish, and easier to type, it’s what I use day-to-day on the shell.
command -v is better, because it’s simpler and machine-readable.