Linux Basics 01: Terminals, Shells, and How Commands Are Actually Found

Published 2022-04-09 20:30 Updated 2022-04-09 22:10 990 words 5 min read ... Page views Handwritten Every word here was typed by me.

Linux Basics 25: Wrapping Up — Self-Audit Checklists And Where To Go DeeperLinux Basics 24: Boot And Rescue — UEFI, GRUB, initramfs And Emergency ModeLinux Basics 23: Containers And Kernel Boundaries — Namespaces, Cgroups And What docker exec DoesLinux Basics 22: Logs And A Debugging Method — Six Steps From Symptom To Root CauseLinux Basics 21: Scheduled Tasks — The Traps Of crontab And systemd TimersLinux Basics 20: Shell Scripting, Part Two — trap, Logging, Idempotence And LockingLinux Basics 19: Shell Scripting, Part One — Quoting, Tests And Exit CodesLinux Basics 18: Performance And Tracing — Where Is The Machine Actually StuckLinux Basics 17: Firewalls — How iptables, nftables, firewalld And ufw RelateLinux Basics 16: SELinux — When Every Permission Is Correct And The Service Is Still DeniedLinux Basics 15: Advanced Permissions — sudoers Wildcards, ACLs And CapabilitiesLinux Basics 14: SSH And Remote Operations — Keys, Permissions And Three Kinds Of TunnelLinux Basics 13: Networking Fundamentals — Troubleshoot From The Bottom UpLinux Basics 12: Disks And Filesystems — From Block Device To Mount PointLinux Basics 11: systemd — units, systemctl And journalctl Done ProperlyLinux Basics 10: Package Management — What apt Actually Does, And The Four Common ErrorsLinux Basics 09: Users And Groups — passwd, shadow And sudoersLinux Basics 08: Environment Variables And Shell Startup Files — Why ssh Cannot Find Your PATHLinux Basics 07: Processes And Signals — The Real Difference Between TERM And KILLLinux Basics 06: Pipelines And Redirection — Three Streams, One Ordering TrapLinux Basics 05: Text Processing — Turning grep/sort/uniq Into Your Analysis ToolkitLinux Basics 04: Permissions And Ownership — rwx Is Not The Same On Files And DirectoriesLinux Basics 03: The Real Semantics Of ls, cp, mv, rm — And The * That The Shell ExpandsLinux Basics 02: Why / Looks Like This, and What An Inode Really StoresLinux Basics 01: Terminals, Shells, and How Commands Are Actually Found
Three things to keep apart — the terminal is not the shell, and the shell is not the kernel. Then PATH order, builtins versus external commands, and why cd can be “not found”.

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' | nl

PATH 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/ls

Two 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 builtin

cd 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 i

My 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. himBH

4. Two Real Accidents

Accident one — putting . in PATH.

export PATH=.:$PATH

Now 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>}"'   # bar

5. 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 %1

In 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.
  • $SHELL is the login shell, not the current process; check $0 / $$ / ps / $-.
  • Never put . in PATH, and never change PATH without export.

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.

Written by Handwritten

If you enjoyed this, leave a comment~

Loading...