What This Chapter Solves
The first time you ssh into a machine, the prompt root@vm:~# invites a wrong idea: that you are talking to Linux.
There are several layers in between. If you don’t separate them, a lot of things later just look like magic: why cd can report “command not found”, why a PATH change has no effect in another terminal, and why echo $SHELL does not match the shell you are actually running.
1. Four Roles, One Chain
keyboard → terminal (pty) → shell → fork/exec child → kernel → disk/network/CPU- Kernel — the one doing the real work: scheduling, memory, filesystems, networking. It does not understand
ls; it understands system calls. - Shell — an ordinary user-space program whose only job is: read a line, parse it, execute it. bash, zsh, dash are all shells.
- Terminal emulator — the window in front of you (iTerm2, GNOME Terminal). It draws characters and collects keystrokes.
- pty (pseudo-terminal) — the wire between a program and your window. An ssh session gets one allocated on the remote side.
What actually happens for ls: the shell reads ls\n from the pty → splits it into words → finds an executable named ls in PATH → fork()s a child → the child execve("/usr/bin/ls", …) → ls itself calls open / readdir / write → the kernel reads the directory → output flows back to the pty → you see text.
The shell does not execute commands. It finds programs, starts processes and wires up pipelines. What executes is the program it execs — that is why it is a command interpreter, not an executor.
2. How A Command Is Found: The Order Of PATH
echo "$PATH" | tr ':' '\n' | nlPATH is searched left to right, first hit wins, and any later program with the same name will never run. That is how ~/bin placed early can “shadow” a system command — convenient, and dangerous (section 4 has the accident).
Don’t guess what a name is; ask the shell:
type -a ls # every match: alias / function / builtin / file on disk
type -a cd
command -v ls # only answers “how would the shell run this?”On Ubuntu, type -a ls prints two lines:
ls is /usr/bin/ls
ls is /bin/lsTwo lines, one file: modern distributions are merged-usr — /bin, /sbin, /lib are symlinks into /usr — while PATH contains both /usr/bin and /bin, so the same file is listed twice.
type -a cd answers differently:
cd is a shell builtincd is “not found” because it is not a program on disk at all — it is a shell builtin. It has to be: cd must change the working directory of the current shell process (the chdir syscall), and an external program is a forked child whose changes cannot affect its parent.
Common builtins: cd, export, read, source, type, pwd, umask, echo (bash has a builtin version and can also call /bin/echo).
3. Which Shell Am I In? Three Answers, Not One
echo "$SHELL" # login shell, from field 7 of /etc/passwd
echo "$0" # name of the shell process you are in
ps -o pid,ppid,cmd -p $$ # $$ is the PID of the current shell
grep "^$(whoami):" /etc/passwd
tty; who am iMy note: $SHELL is a hint, never evidence. It was read from /etc/passwd at login; typing zsh or bash afterwards does not change it. To know what is running now, use $0 / $$ / ps.
$- also tells you which mode the shell is in: an i means interactive, an m means job control is on (that detail comes back when we discuss signals).
echo "$-" # e.g. himBH4. Two Real Accidents
Accident one — putting . in PATH.
export PATH=.:$PATHNow the shell looks in the current directory first for every command you type. Unpack an archive that happens to contain a file named ls or sudo, cd into it, type ls, and you have run someone else’s program. The attack has a name: PATH hijacking.
Accident two — changing PATH without exporting it. This one actually bit me:
PATH="$HOME/bin:$PATH" # changed, not exported: only the current shell knows
export PATH="$HOME/bin:$PATH" # correct
FOO=bar ; bash -c 'echo "${FOO:-<empty>}"' # <empty>
export FOO=bar ; bash -c 'echo "${FOO:-<empty>}"' # bar5. Hands-On Checklist (Five Minutes)
# 1. who am I talking to
echo "$0 | $SHELL | $$"; ps -o pid,ppid,cmd -p $$
# 2. the search order
echo "$PATH" | tr ':' '\n' | nl
# 3. which layer explains each name
type -a ls; type -a cd; type -a sudo
# 4. proof that the shell starts processes: parent PID is your shell
sleep 300 &
ps -o pid,ppid,cmd -p $! # $! = PID of the most recent background job
kill %1In step 4 the ppid is your shell — direct, observable evidence for “the shell only forks and execs”.
Summary
- Terminal ≠ shell ≠ kernel. The shell only reads a line, finds a program and starts a process.
- Command lookup follows PATH order left to right; builtins never take part in it.
$SHELLis the login shell, not the current process; check$0/$$/ps/$-.- Never put
.in PATH, and never change PATH withoutexport.
Next Up
Why / contains exactly these directories, and what an inode really stores — including the failure where the disk has free space but nothing can be written.
If you enjoyed this, leave a comment~