This has not at all to do with bash
, but it depends on the completions programmed in the package bash-completion
.
From some comments in the file /etc/bash_completion.d/mount
:
# mount(8) completion. This will pull a list of possible mounts out of
# /etc/{,v}fstab, unless the word being completed contains a ':', which
# would indicate the specification of an NFS server. In that case, we
# query the server for a list of all available exports and complete on
# that instead.
#
# umount(8) completion. This relies on the mount point being the third
# space-delimited field in the output of mount(8)
#
Also, you find in the main file /etc/bash_completion
the following comment, that explicitly talk about mount
and umount
commands:
# A meta-command completion function for commands like sudo(8), which need to
# first complete on a command, then complete according to that command's own
# completion definition - currently not quite foolproof (e.g. mount and umount
# don't work properly), but still quite useful.
#
Update:
The comment about mount
and umount
commands was removed from bash_completion
in the commit:
_command_offset: Restore compopts used by called command.
This fixes completions that rely on their compopts, most notably
mount(8).
Fixes bash-completion bug #313183.
Released in bash-completion 1.90
The basic concept to grasp here is that PATH can be defined in many places. As @demure explains in his answer, PATH=$PATH:/new/dir
means add /new_dir
to $PATH
, it will not clear the original $PATH
.
Now, one reason there are many files is intimately connected with the concept of login
and non-login
shells. See here for a nice summary. The following is from the bash man page (emphasis mine):
When bash is invoked as an interactive login shell, or as a
non-interactive shell with the --login option, it first reads and
executes commands from the file /etc/profile, if that file exists.
After reading that file, it looks for ~/.bash_profile,
~/.bash_login, and ~/.profile, in that order, and reads and executes
commands from the first one that exists and is readable. The
--noprofile option may be used when the shell is started to inhibit this behavior.
When you first log into your system, you start a login shell so bash will read the files listed above. Most distributions set a system-wide $PATH
(which applies to all users) at /etc/profile
and this is where you should make any changes that you want applied to all users. This is what I have on my Debian:
PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
Once you have logged in, when you open a terminal you start an interactive, non-login shell. This is what man bash
has to say about those:
When an interactive shell that is not a login shell
is started, bash reads and executes commands from
/etc/bash.bashrc and ~/.bashrc, if these files exist.
So, those files are read every time you open a new terminal. Your filnal $PATH is the combination of the values in all files. In a typical situation, you log in using a graphical log in manager and start a new session. At this pòint your $PATH
is whatever was defined in the various profile
files. If you open a terminal, then you are in an interactive shell and the different bashrc
files are read which may append things to the $PATH
.
To summarize, all you really need to know is that you can make changes to your user's $PATH
by editing $HOME/.profile
.
Best Answer
As you suspect, the exact behaviour is shell-dependent, but a baseline level of functionality is specified by POSIX.
Command search and execution for the standard shell command language (which most shells implement a superset of) has a lot of cases, but we're only interested for the moment in the case where
PATH
is used. In that case:and
In the unsuccessful case, execution fails and an exit code of 127 is returned with an error message.
This behaviour is consistent with the
execvp
function, in particular. All theexec*
functions accept the file name of a program to run, a sequence of arguments (which will be theargv
of the program), and perhaps a set of environment variables. For the versions usingPATH
lookup, POSIX defines that:The behaviour of PATH is defined elsewhere as:
That's a bit dense, so a summary:
/
(slash, U+002F SOLIDUS) in it, treat it as a path in the usual fashion, and skip the rest of this process. For the shell, this case technically doesn't arise (because the shell rules will have dealt with it already).PATH
is split into pieces at each colon, and then each component processed from left to right. As a special (historical) case, an empty component of a non-empty variable is treated as.
(the current directory)./
and the existence of a file by that name is checked, and if one does exist then valid execute (+x) permissions are checked as well. If either of those checks fails, the process moves on to the next component. Otherwise, the command resolves to this path and the search is done.PATH
, or it doesn't exist, do whatever you want.Real shells will have builtin commands, which are found before this lookup, and often aliases and functions as well. Those don't interact with
PATH
. POSIX defines some behaviour around those, and your shell may have much more.While it's possible to rely on
exec*
to do most of this for you, the shell in practice may implement this lookup itself, notably for caching purposes, but the empty-cache behaviour should be similar. Shells have fairly wide latitude here and have subtly different behaviours in the corner cases.As you found, Bash uses a hash table to remember the full paths of commands it's seen before, and that table can be accessed with the
hash
function. The first time you run a command it searches, and when a result is found it gets added to the table so there's no need to bother looking the next time you try it.In zsh, on the other hand, the full
PATH
is generally searched when the shell starts. A lookup table is prepopulated with all discovered command names so that runtime lookups usually aren't necessary (unless a new command is added). You can notice that happening when you try to tab-complete a command that didn't exist before.Very lightweight shells, like
dash
, tend to delegate as much behaviour as possible to the system library and don't bother to remember past command paths.