18

What a Shell Actually Is

The shell is not the terminal. It is a program that reads commands and runs them.

You have been typing commands into a terminal for the last few articles. You pressed keys, text appeared, programs ran. But what exactly was reading those commands? What was deciding which program to start, and how?

The answer is the shell. It is not the terminal. It is not the window on your screen. It is a separate, ordinary program that happens to be very good at one thing: taking text commands from a human and turning them into running processes.

The Terminal Is Not the Shell

Think of a restaurant. The terminal is the dining room -- the table, the chair, the menu. The shell is the waiter. You tell the waiter what you want. The waiter carries your order to the kitchen. The kitchen does the actual cooking.

The terminal handles input and output. It draws characters on screen. It captures your keystrokes. But it does not know what ls means. It does not know how to start a program. It is just a conduit.

The shell is the program running inside that terminal. It prints the prompt. It reads the line you type. It figures out what you meant. It asks the kernel to run the right program. When the program finishes, the shell prints the next prompt.

Key term: Shell A command-line interpreter -- a program that reads text commands, parses them, and executes them by asking the kernel to create new processes. It is called a "shell" because it wraps around the kernel, forming the outer layer that humans interact with.
Fig. 18.0 -- Terminal vs. shell vs. kernel
TERMINAL draws characters captures keystrokes displays output SHELL parses commands looks up programs manages jobs KERNEL creates processes manages memory schedules CPU handles I/O ls grep python3

YOU TYPE HERE

The terminal displays text and captures keystrokes. The shell interprets commands. The kernel creates and manages the actual programs that do the work.

You can prove this separation yourself. Open a terminal and type echo $SHELL. It will print something like /bin/bash or /usr/bin/zsh. That is the path to a real file on disk -- the shell binary. You can even run a different shell inside your current one by typing its name, like sh or zsh.

What Happens When You Type a Command

You type ls -l /tmp and press Enter. Here is exactly what the shell does:

Step 1: Read. The shell reads the entire line of text from standard input.

Step 2: Parse. It splits the line into tokens. The first token is the command name: ls. The remaining tokens are arguments: -l and /tmp.

Step 3: Find. The shell needs to locate the program called ls. It searches a list of directories, one by one, looking for an executable file with that name. This list is the PATH variable, and we will cover it in detail in Article 21.

Step 4: Execute. The shell calls fork() to create a copy of itself, then the copy calls exec() to replace itself with the ls program. The original shell waits for ls to finish.

Step 5: Wait. When ls exits, the shell collects its exit status -- a number that indicates success or failure. Then it prints the next prompt and goes back to Step 1.

Fig. 18.1 -- The shell's read-eval-execute loop
1. Read line 2. Parse tokens 3. Find program 4. fork() + exec() 5. Wait for exit loop

$ ls -l /tmp ["ls","-l","/tmp"] /usr/bin/ls PID 4821 running exit status: 0

The shell runs in an infinite loop: read a command, parse it, find the program, execute it, wait for it to finish, then repeat. This loop is sometimes called the REPL -- read, evaluate, print, loop.

This loop is the core of every shell. Everything else -- variables, conditionals, scripting -- is built on top of this basic cycle.

The shell is a loop. It reads a line, figures out what program to run, asks the kernel to run it, waits for it to finish, and then reads the next line. That is the fundamental operation, and it never changes.

Built-in Commands vs. External Programs

Not every command you type launches a separate program. Some commands are built into the shell itself.

Consider cd /tmp. If the shell started a new process, changed that process's working directory, and then that process exited -- well, the shell's own working directory would be unchanged. The child process's directory change would die with it. So cd has to be handled inside the shell itself, without forking a new process.

These are called built-in commands (or just builtins). Common builtins include:

  • cd -- change the shell's working directory
  • echo -- print text (also exists as an external program)
  • export -- set environment variables
  • exit -- terminate the shell
  • type -- tell you whether a command is a builtin or an external program

You can check any command with type:

$ type cd
cd is a shell builtin
$ type ls
ls is /usr/bin/ls

External programs are separate executable files stored somewhere on disk. When you run ls, the shell finds /usr/bin/ls, creates a new process, and runs that file. When you run cd, the shell handles it internally and no new process is created.

Key term: Built-in command A command that the shell executes directly, without creating a new process. Built-ins exist because certain operations -- like changing the working directory or setting variables -- must modify the shell's own state, which a child process cannot do.

How the Shell Finds Programs: PATH

When you type ls, the shell does not magically know where /usr/bin/ls lives. It searches through a list of directories in order. That list is stored in an environment variable called PATH.

You can see yours:

$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

The directories are separated by colons. The shell checks each one, left to right, for a file matching the command name. The first match wins.

If you type a command and get command not found, it means the shell searched every directory in PATH and found nothing. The program either does not exist, is not installed, or is in a directory that is not listed in PATH.

You can bypass PATH entirely by specifying the full path to a program:

$ /usr/bin/ls

Or a relative path:

$ ./my-program

The ./ prefix tells the shell to look in the current directory specifically, not to search PATH.

Fig. 18.2 -- PATH search order

$ grep

/usr/local/bin/ not found /usr/bin/ FOUND: /usr/bin/grep Search stops /bin/ not checked /usr/sbin/ not checked
When you type "grep", the shell walks through PATH directories left to right. It finds /usr/bin/grep and stops searching. Later directories are never checked.

The Major Shells

The concept of a command-line shell is older than Linux. Here are the shells you are most likely to encounter:

sh -- The Bourne Shell

Written by Stephen Bourne at Bell Labs in 1979. It established the basic syntax that all later shells build on: command arguments, pipes, redirection, variables, control structures. On modern Linux systems, /bin/sh is usually a symlink to a lighter shell like dash, not the original Bourne shell.

bash -- Bourne Again Shell

The default shell on most Linux distributions. Written by Brian Fox for the GNU Project in 1989. It is backward-compatible with sh and adds interactive features like command history, tab completion, and programmable completion. When people say "shell scripting" without qualification, they usually mean bash.

zsh -- Z Shell

The default shell on macOS since 2019. It offers everything bash does plus more advanced features: better tab completion, spelling correction, extended globbing patterns, and a large plugin ecosystem. Its syntax is mostly compatible with bash.

dash -- Debian Almquist Shell

A minimal, fast shell used by Debian and Ubuntu for system scripts. It implements only the POSIX sh standard -- no bash extensions. When a system script says #!/bin/sh, it often runs under dash because dash starts faster than bash.

All of these shells do the same fundamental job: read commands, find programs, run them. They differ in interactive features and scripting extensions, but the core loop is identical. If you learn bash, you can get by in any of them.

The Shell Is Just a Program

This is the key insight that trips up many beginners: the shell is not special hardware. It is not an operating system component. It is an ordinary user-space program, no different in principle from ls or firefox or python3.

The kernel does not know or care that bash is a "shell." From the kernel's perspective, bash is just another process that occasionally calls fork(), exec(), and wait(). You could write your own shell in any language. Some people do -- it is a classic programming exercise.

What makes the shell feel special is that it is the first thing you interact with when you log in, and it is the thing that starts every other program you use from the command line. But that is a matter of convention, not privilege.

You can verify this by running a shell inside a shell:

$ bash
$ echo "I am in a nested shell"
I am in a nested shell
$ exit
$ echo "Back to the original"
Back to the original

The inner bash is a child process of the outer bash. When you type exit, the inner shell terminates and you return to the outer one. There is no limit to how deep you can nest, though there is rarely a reason to go more than one level.

Fig. 18.3 -- Nested shells are just nested processes
bash (PID 1000) bash (PID 1001) -- child of 1000 ls (PID 1002)

$ exit kills PID 1001 returns to PID 1000

Running "bash" inside bash creates a child shell process. Programs you run in the inner shell are grandchildren of the outer shell. Exiting the inner shell returns you to the outer one.

The Login Shell and Interactive Shells

When you log in to a Linux system, the login process checks /etc/passwd to find which shell to start for your user account. That shell is your login shell. It is the root of your session -- every other program you launch descends from it.

A login shell reads extra configuration files when it starts. For bash, that means ~/.bash_profile or ~/.profile. These files set up your environment: your PATH, your preferred editor, your prompt format.

When you open a new terminal window in a graphical desktop, you usually get a non-login interactive shell. It reads ~/.bashrc instead. The distinction matters because some settings only need to happen once per login (like adding directories to PATH), while others should apply to every new shell (like aliases and prompt configuration).

We will explore these configuration files in detail in Article 21.

Key term: Login shell The shell started when you first log in to the system. It reads additional startup files (like ~/.profile) that set up your session environment. You can check if your current shell is a login shell by running "echo $0" -- a login shell's name starts with a hyphen, like "-bash".

What You Have Learned

The shell is a program. It lives on disk at a path like /bin/bash. It runs in a loop: read a command, parse it, find the program, fork a child process, execute the program in that child, wait for it to finish, repeat. Some commands are built into the shell itself because they need to modify the shell's own state. The shell finds external programs by searching directories listed in the PATH variable. There are several shells to choose from -- bash, zsh, dash, and others -- but they all implement the same fundamental loop.

The terminal is the window. The shell is the brain inside it.

Next: Standard I/O