Monday, November 1, 2021

Which Is Not Posix

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: Don’t rely on which to find the location of an executable.

Use command -v instead. On an interactive command line, type is also a great option.

which

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

command -v

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


Additionally it has the option -v that doesn’t run the command but tells you how the shell would interpret a name if you’d run it without using command:

This means that 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:

$ command -v if echo ll
if
echo
alias ll='ls -l'

On most shells (but not fish), if you pass -V, you get a human-readable version:

$ command -V if echo ll
if is a shell keyword
echo is a shell builtin
ll is aliased to `ls -l'

Therefore, if you want to write cross-platform shell scripts, stick to command -v that works everywhere and has machine-readable output.

type

Unfortunately, my favorite shell fish has only very rudimentary support for command (no -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

With type -p you can limit the output to proper executables (similar to which):

$ type -p brew
/usr/local/bin/brew
$ type -p ll
-

Summary

Since type is human-readable, better supported on fish, and easier to type, it’s what I use day-to-day on the shell.

For scripts, command -v is better, because it’s simpler and machine-readable.

Hynek Schlawack

In ❤️ with Python 🐍 & Go 🐹, blogger πŸ“, speaker πŸ“’, PSF fellow πŸ†, big city beach bum πŸ„πŸ».

Is my content helpful and/or enjoyable to you? Please consider supporting me! Every bit helps to motivate me in creating more.

If you’re not into RSS, but would like to be notified about new content (along with exclusive “director’s commentary” about what I did and why), check out my extremely low-volume Hynek Did Something newsletter.



from Hacker News https://ift.tt/2XZLnVp

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.