Why "hash /bin/ls" does nothing? - bash

Writing the command hash with some relative or an absolute path, whether to a file, directory or a to nowhere, it seems to do nothing.
$> hash /bin/ls
prints nothing ***
$> hash /path/to/nowhere
prints nothing ***
$> hash ../same/as/above
same thing ***
Why is that?

hash does a command search and remembers the resolved location. Part of command search is skipping it when there's a slash in the name*. Therefore, hash foo/bar does nothing.
Relevant POSIX spec:
hash:
The hash utility shall affect the way the current shell environment remembers the locations of utilities found as described in Command Search and Execution. [...] It shall add utility locations to its list of remembered locations
Command Search and Execution:
If the command name contains at least one <slash>, the shell shall execute the utility in a separate utility environment with actions equivalent to calling the execl() function
[...]
Once a utility [invoked without a slash] has been searched for and found (either as a result of this specific search or as part of an unspecified shell start-up activity), an implementation may remember its location and need not search for the utility again
* This is incidentally why you execute a script in the current dir with ./myfile: it's the shortest way to make the filename contain a slash.

Related

Why *nix command line enable execution via path relative to current directory?

For a long time I was under impression that *NIX shells do not support running executables via path relative to current directory. Also there're a number of posts explaining why having working directory in path is a bad practice. Still it's easy to see that following example works:
% cat<<EOF > temp/a
? #!/bin/sh
? echo "Hello looser!"
? EOF
% chmod 750 temp/a
% temp/a
Hello looser!
(I tested at CentOS and OSX).
Why it works?
Upd: it's not purely academic question, I had cases when binaries run via full path and the way described above worked different way.
Upd 2: libc execv accepts both absolute and relative paths with no problem. Hence, support for subpath/exe while failing to run exe looks as shell feature common to bash and tcsh. There must be some logic behind it??
You can always execute a program by specifying its relative path. There is no requirement that you would have to use an absolute path. Actually, the way you are executing your program, is quite common.
If you write command with no / then the shell searches $PATH for the command.
If you write dir/command with a relative directory, there's a / and the shell does not search $PATH. It interprets it relative to the current directory. dir/command always resolves to ./dir/command.
Putting . in the $PATH is dangerous because you may type command intend ing to run /usr/bin/command but actually get ./command if it exists in the current directory. Indeed, it's not just . that's dangerous: any relative path is dangerous. $PATH should only ever contain absolute paths.
So, you're saying that relative paths shall be implicitly prefixed with current directory, with exceptional case when relative path is empty that shall be treated as direction to search in $PATH?
Yes, you could say that. But I prefer to reframe it. A better way to think of it is to understand what the shell is doing versus what the kernel is doing.
Interpreting relative paths is handled implicitly by the kernel. When the shell performs a system call like execve("dir/command", ...) to execute a program the kernel knows the parent process's current directory and resolves dir/command relative to it. The shell does not have to first convert the path into an absolute one. The kernel can handle both absolute and relative paths.
The kernel knows nothing about $PATH, though. That's a shell construct. Keep in mind that the shell is a higher level piece of software. The shell decides that if you type a simple command name without a / in it, it will not simply pass that command to the kernel. If it did, typing cat would simply execute ./cat. Nobody wants that.
Instead, the shell decides that the lack of a / means it will search for the command in the $PATH. It'll search /bin, /usr/bin, $HOME/bin, etc. If you listed . in the $PATH then it would also search the current directory—don't do that! You don't want it to run ./cat. No sir.
If the shell finds an executable in the $PATH then it converts it to an absolute path name and passes that to the kernel. The kernel sees execve("/usr/bin/cat", ...).
If so and you know it from some formal specification, I'd appreciate the reference. I do need to know precisely.
See the bash man page:
If the command name contains no slashes, the shell attempts to locate it. …
If the name … contains no slashes, bash searches each element of the PATH for a directory containing an executable file by that name. …
If the search is successful, or if the command name contains one or more slashes, the shell executes the named program ….
This as nothing to do with execution. It is due to the fact that you can name a file with an absolute or a relative name, and that executables are just ordinary files.
Why is it dangerous? (.in PATH)
Think about a script that calls a command like cat foo, if you have . in the path then it could be possible to create a command cat in the same directory that would be executed in place of the original one.

Bash's 'hash' command always succeeds when trying to check existance of a command with a slash in the name

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

What is the bash equivalent of `modpath` in csh?

This is the error that I got.
modpath: Command not found.
I'm suspecting that it's because modpath is a csh command but not the right syntax for bash. So, I want to know what's its equivalent in bash. Thanks!
modpath - change global search path for dynamically loadable kernel modules
modpath allows users with appropriate privilege to modify the global search path used to locate object files for dynamically loadable kernel modules. The search path modifications take effect immediately and affect all subsequent loads for all users on the system.
pathname may be either a colon-separated list of absolute pathnames or NULL. If the former, these path names represent directories which should be searched for all autoloads of loadable kernel modules and for demand loads (see modload(2)) where the module is given by a simple file name. This list of directories will be prepended to the existing list of directories and so will be searched before any directories given in previous calls to modpath and before the default location which is always searched last. The directories do not have to exist on the system at the time modpath is called, or when a load actually takes place. If pathname is equal to NULL, the global search path is set back to its initial default value, /stand/dlkm/mod.d.
Notes: modpath is currently implemented as a macro.
[source: http://modman.unixdev.net/?sektion=2&page=modpath&manpath=HP-UX-11.11]
Based on the documentation now included in the question, modpath has nothing to do with $PATH, and it's not specific to csh, bash, or any other shell.
On my system (Ubuntu 16.10), there is no command, system call, or library function by that name, and there appears to be no installable package that provides it. The documentation you quoted is for HP-UX, which is a different flavor of UNIX. I suspect it's a system call that exists only on HP-UX.
Since you've been able to invoke modpath from csh, there's probably a command that's some kind of wrapper around the system call, though the documentation is for the system call itself, not for the command.
If such a command exists, you should be able to find it by typing
which modpath
from csh. If that gives you the full path to the command, then from bash you can either use that full path to invoke the command, or you can add the appropriate directory to your $PATH. (You very likely have a different setting for $PATH in csh vs. bash.)

What does the colon do in PATH

I am new to bash, and I saw people often add : after a directory when modifying PATH. After searching for a while, I did not find an answer for that, or I believe I did not search it correctly. So I hope I could get an answer here.
Example:
/Users/chengluli/anaconda/bin:/Users/chengluli/.rbenv/shims:/
What does the : after bin and shims do?
: is the separator. The PATH variable is itself a list of folders that are "walked" through when you run a command.
In this case, the folders on your PATH are:
/Users/chengluli/anaconda/bin
/Users/chengluli/.rbenv/shims
/
As others have said, the : is a separator (Windows uses a semi-colon ;). But you are probably thinking of a trailing colon : at the end of the PATH variable. For example:
/Users/chengluli/anaconda/bin:/Users/chengluli/.rbenv/shims:
From the bash man pages:
A zero-length (null) directory name in the value of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon.
Putting the current directory in the PATH is generally considered a security risk and a bad idea. It is particularly dangerous when using the root user.
By the way, bash only uses $PATH on the first call of an external program, after that it uses a hash table. See man bash and the hash command
If you run ls -l 123 at the command line, you are telling bash to find the command called ls in the filesystem. However, ls is just a file name, bash needs the absolute path of ls in the filesystem. So bash searches for a file called ls in a list of default directories, one by one in order.
A list of default directories is stored in the PATH variable, separated by :.
Th quotation from output of man bash command
PATH
The search path for commands. It is a colon-separated list of directories in which the shell looks for commands (see COMMAND EXECUTION below). A zero-length (null) directory
name in the value of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon. The default path is sys‐tem-dependent, and is set by the administrator who installs bash. A common value is ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin''.
If you have questions about bash script or environment variable, please use man bash firstly.

Blacklist program from bash completion

Fedora comes with "gstack" and a bunch of "gst-" programs which keep appearing in my bash completions when I'm trying to quickly type my git aliases. They're of course installed under /usr/bin along with a thousand other programs, so I can't just remove their directory from my PATH. Is there any way in Linux to blacklist these specific programs from appearing for completion?
I've tried the FIGNORE and GLOBIGNORE environment variables but they don't work, it looks like they're only for file completion after you've entered a command.
In 2016 Bash introduced an option for that. I'm reproducing the text from this newer answer by zuazo:
This is rather new, but in Bash 4.4 you can set the EXECIGNORE variable:
aa. New variable: EXECIGNORE; a colon-separate list of patterns that
will cause matching filenames to be ignored when searching for commands.
From the official documentation:
EXECIGNORE
A colon-separated list of shell patterns (see Pattern Matching) defining the list of filenames to be ignored by command search using
PATH. Files whose full pathnames match one of these patterns are not
considered executable files for the purposes of completion and command
execution via PATH lookup. This does not affect the behavior of the [,
test, and [[ commands. Full pathnames in the command hash table are
not subject to EXECIGNORE. Use this variable to ignore shared library
files that have the executable bit set, but are not executable files.
The pattern matching honors the setting of the extglob shell option.
For Example:
$ EXECIGNORE=$(which pytest)
Or using Pattern Matching:
$ EXECIGNORE=*/pytest
I don't know if you can blacklist specific files, but it is possible to complete from your command history instead of the path. To do that add the following line to ~/.inputrc:
TAB dynamic-complete-history
FIGNORE is for SUFFIXES only. It presumes for whatever reason that you want to blacklist an entire class of files. So you need to knock off the first letter.
E.g. To eliminate gstack from autocompletion:
FIGNORE=stack
Will rid gstack but also rid anything else ending in stack.

Resources