← Learn··Updated 5 Jul 2026·2 min read

What is a shell?

A short reference on the shell — what it actually does between your keyboard and the kernel, how it differs from a terminal and a tty, what happens to a command line before it runs, and the classic footguns of quoting and sh-vs-bash.

Operating systems
Linux
#linux
#unix
#shell
#terminal

The one-line definition

A shell is an ordinary program that wraps the operating system's kernel for humans and scripts: it reads a line of text, turns it into a program invocation, asks the kernel to run it, and reports back. The name is the metaphor — the soft outer layer around the kernel. Everything else (prompts, tab completion, scripting syntax) is convenience layered on that loop.

Shell vs terminal vs tty vs console

Four words that get used interchangeably and are not the same thing:

Word What it actually is
Shell The command interpreter — bash, zsh, fish. A process
Terminal (emulator) The window that draws text and sends keystrokes — GNOME Terminal, iTerm, Ghostty. Talks to the shell through a pseudo-tty
tty The kernel's device interface between the two, named after 1870s teletypes
Console Historically the physical directly-attached terminal; today, mostly "the text screen the kernel writes to"

The pipeline: terminal draws and forwards keys → tty device carries bytes → shell interprets → kernel executes. When people say "open a shell" they usually mean "open a terminal," and the ambiguity almost never matters — until you're debugging why keys or colors behave strangely, at which point knowing which layer you're in is everything.

What the shell actually does to a command line

cat *.log | grep -i error > found.txt looks like one instruction. The shell performs a whole compilation pass before anything runs:

  1. Tokenize — split on whitespace, respecting quotes.
  2. Expand*.log becomes an actual file list (globbing); $HOME and $(date) are substituted; this happens before the program runs, which is why quoting matters.
  3. Wire plumbing — the | creates a pipe between two processes; > opens the output file. The programs never see these symbols.
  4. fork & exec — the shell asks the kernel to spawn the processes, then waits (or doesn't: &).

Two consequences worth internalizing: programs never see your wildcards (grep receives already-expanded filenames), and the shell is just a program — nothing it does is privileged, which is why there can be so many of them.

Interactive, login, and script shells

  • Interactive — the one with the prompt. Reads your rc file (.bashrc, .zshrc).
  • Login shell — first shell of a session; reads profile files (.profile, .bash_profile). The perennial "works in my terminal, not over ssh" bug is usually a variable set in the wrong one.
  • Non-interactive — running a script. Reads neither by default, which is the other half of the same bug class.

The common shells

Shell One line
sh The POSIX baseline; on Linux usually a symlink to something stricter than bash
bash The GNU default nearly everywhere; Bourne-Again, the pun is the name
zsh macOS default; bash-compatible-ish with better interactive features
fish Friendly defaults, great completion; deliberately not POSIX-compatible
PowerShell The Windows-native answer; pipes objects instead of text

Find yours: echo $SHELL (login default) or ps -p $$ (what's actually running now). Change it with chsh, from the menu in /etc/shells.

The footguns

sh is not bash. A script with #!/bin/sh that uses bash-only features ([[, arrays, local -n) works on machines where sh is bash and explodes on Debian/Alpine where it isn't. Write POSIX for sh, or say #!/usr/bin/env bash honestly.

Word splitting. Unquoted variables are split on whitespace and glob-expanded: rm $file with file="my report.pdf" removes my and report.pdf. Quote by default: rm "$file". Most of shell scripting's reputation for danger is this one rule.

PATH resolution. The shell finds python by walking $PATH left to right. Two installed versions plus a stale hash cache = the eternal "which python am I running" hour. type -a python tells the truth.

Exit codes are the API. 0 is success, anything else failure; && and || branch on it, set -e aborts on it, CI lives by it. A script that ends on a failed command returns that failure — usually what you want, occasionally a mystery.