The question of “why doesn’t sudo cd
work?” has come up a couple times in the
last few weeks. I figured that the explanation would make for an interesting
blog post.
What Shells Do
(Interactive) shells repeatedly prompt the user to type in a command-line, and then attempt to execute that command-line. Once that command exits, the cycle is repeated. The big question is: what does execute mean? The first whitespace-separated word in the entered string is the command, and the rest of the words are the arguments. Most commands are either the name of an executable file, or the name of a built-in command. There are a few other possibilities such as aliases and function names, but they aren’t relevant to this explanation.
Executable Files
Executable files are stored on disk, typically in directories such as /bin
,
or /usr/bin
. Whenever the shell executes such a command, it searches for the
file in the current $PATH
, and then creates a new process to run that file,
passing the arguments to the command.
Built-in Commands
Some commands are so common or simple that the shell contains a built-in
implementation of the command. Other commands have to be built-in simply to
operate correctly. Examples of built-ins are echo
, test
, or cd
.
Built-in or External?
At least in bash
, you can determine whether a command is an executable file,
a built-in, or something else using the type
command:
You can also use which
to determine if or where a given executable exists on
disk:
Process State
Every process has certain state associated with it. Examples are the process ID, ulimit configuration, and the current working directory. This state is generally not shared between processes, and state changes in one process do not affect any other process, even parent or child processes.
When a new process is created, the child process’s state is initially a copy of the parent process. The child process can then modify the state if desired and permissions allow it (e.g. ulimit or nice values often can’t be changed in certain ways). Such changes to the child process’s state do not affect the parent process. When a process exits, all aspects of its state are destroyed, and have no lasting effect. Of course, if a process makes changes to objects other than its own state, such as to files on disk, those changes are preserved even after the process exists; only a process’s own state is destroyed when a process exits.
Current Working Directory
Let’s consider what the current working directory of a process is used for. Users can specify paths (e.g. in command-line arguments or a request for an editor to open or save a file) in two forms; absolute and relative.
Absolute paths, those beginning with /
such as /usr/bin
, are meaningful as
written since they refer directly to the desired file.
Relative paths are meaningless without stating where those paths are relative
to. The current working directory is the directory against which any relative
path name is resolved. For example, the relative path lib/i386-linux-gnu
would
refer to /lib/i386-linux-gnu
if the current working directory was /
, or
would refer to /usr/lib/i386-linux-gnu/
if the current working directory was
/usr
.
The cd
Built-in
The cd
command changes the value of the current working directory of the
current process.
What if cd
were an executable file rather than a built-in? The shell would
have to execute cd
as a new process. cd
would change the current working
directory value for its own process; a state change that would not affect any
other process. Then, cd
would exit, and the shell would prompt the user for
another command. Overall, these steps wouldn’t have any side-effects; in
particular, the current working directory value of the shell would not have
changed. This means that a cd
executable file wouldn’t be that useful. For
this reason, cd
must be a built-in command within the shell, so that it
affects the shell’s process state.
cd
Executable in HP-UX
An interesting piece of Unix trivia: Some versions of HP-UX do actually contain
a /usr/bin/cd
executable file. This doesn’t seem very useful on the surface.
One potential use I’ve seen proposed is to test whether a path name is a valid
target for cd
without affecting the current process’s state, e.g.:
What sudo
Does
sudo
is a setuid program which allows running commands as a different user ID.
For example, if given permission by a system administrator, sudo
may allow
certain unprivileged users to run specific commands as root
. This is commonly
used to allow such users to perform specific limited system administration
duties, or if people simply want to hack around permissions issues!
sudo
is an executable file and not a shell built-in. sudo
therefore runs as
a separate process to the shell, and always creates a new process to run the
user-specified command; sudo
has no built-ins that are run in-process.
An example of sudo
usage:
Putting it All Together
A simple cd
command is executed directly by the shell, and hence affects the
shell’s own state.
A sudo cd
command attempts to run a cd
executable. Since this typically
doesn’t exist, this will typically fail as:
Should a cd
executable actually exist, and actually implement a “change
working directory” operation, it still wouldn’t do what we want, since it would
only change the current working directory for the cd
process itself, which
would not affect either the sudo
or shell parent processes.
Credits
Aaron Johnson for the information about the HP-UX /usr/bin/cd
executable.
Bob Proulx for review of this blog post. All mistakes are mine though!
Changelog
2017/09/26: Typo.