2

Consider the following script:

#!/bin/bash
hash ./a.sh  && echo ./a.sh exists
hash foo     && echo foo exists
hash bar/foo && echo bar/foo exists
bar/foo

It tries to check whether different commands exist, namely ./a.sh, foo and bar/foo (e.g. a foo executable inside bar directory). Afterwards, it tries to run bar/foo command. My output is:

./a.sh exists
./a.sh: line 3: hash: foo: not found
bar/foo exists
./a.sh: line 5: bar/foo: No such file or directory

First two lines are expected, as well as the last one. However, the third line says that hash command did not fail for bar/foo, which is strange.

I have though that using "hash" is preferrable for testing existence of commands which the script is about to use. At least, it's mentioned as a possible alternative in this SO answer. Turns out it does not work very well for commands which are relative paths (haven't tested with absolute paths). Why is that? type works better, but I considered them to be mostly synonymous.

Community
  • 1
  • 1
yeputons
  • 8,478
  • 34
  • 67
  • `hash` and `type` are not synonymous. If you want to check the existence of a command you're better of relying on `which`, which was designed for that use case. – ffledgling Feb 21 '17 at 07:59
  • @ffledgling I've consulted with [this SO answer](http://stackoverflow.com/a/677212/767632), which recommends to avoid `which` and use builtin commands, because they provide same functionality, but do not require to spawn additional process. – yeputons Feb 21 '17 at 08:00
  • 2
    Also, `hash` takes NAMES as arguments, not PATHS. It doesn't make sense to cache relative paths. – choroba Feb 21 '17 at 08:02

1 Answers1

1

Refer to bash's documentation on how commands are looked up and executed:

3.7.2 Command Search and Execution

After a command has been split into words, if it results in a simple command and an optional list of arguments, the following actions are taken.

  1. If the command name contains no slashes, the shell attempts to locate it. If there exists a shell function by that name, that function is invoked as described in Shell Functions.
  2. If the name does not match a function, the shell searches for it in the list of shell builtins. If a match is found, that builtin is invoked.
  3. If the name is neither a shell function nor a builtin, and contains no slashes, Bash searches each element of $PATH for a directory containing an executable file by that name. Bash uses a hash table to remember the full pathnames of executable files to avoid multiple PATH searches (see the description of hash in Bourne Shell Builtins). A full search of the directories in $PATH is performed only if the command is not found in the hash table. If the search is unsuccessful, the shell searches for a defined shell function named command_not_found_handle. If that function exists, it is invoked with the original command and the original command’s arguments as its arguments, and the function’s exit status becomes the exit status of the shell. If that function is not defined, the shell prints an error message and returns an exit status of 127.
  4. If the search is successful, or if the command name contains one or more slashes, the shell executes the named program in a separate execution environment. ...

In short, look-up and hashing is performed only for commands that do not contain slashes. If a command looks like a path (i.e. contains a slash) it is assumed to refer to en executable file at that path and the complex procedure of look-up is not needed. As a result, hash handles arguments with slashes as if they would resolve to themselves and exits with a success status unconditionally (that is without checking that the named file actually exists and is executable).

Leon
  • 31,443
  • 4
  • 72
  • 97