Skip to content
Shell & TerminalDeep Dive Published Updated 6 min readViews unavailable

Terminal Emulators vs. Shells: What Each One Actually Does

Terminal and shell are separate programs linked by a kernel pty: one renders characters and handles input, the other interprets commands.

“Terminal” and “shell” get used interchangeably in casual conversation constantly — they’re genuinely separate programs with genuinely separate responsibilities, connected through a specific kernel mechanism most users never need to think about directly.

What the terminal emulator actually does

A terminal emulator (Terminal.app, GNOME Terminal, Windows Terminal, iTerm2) is a graphical application responsible for rendering text, handling keyboard input, managing colors and fonts, and interpreting a specific set of escape sequences that control cursor movement, colors, and screen clearing — historically emulating the behavior of physical hardware terminals like the DEC VT100, hence the name.

What the shell actually does

The shell (Bash, Zsh, sh, tcsh) is a completely separate program running inside whatever the terminal emulator provides — it reads the text you type, interprets it as commands, manages variables and control flow, and launches other programs. The shell has no idea how its output is actually being rendered on screen; that’s the terminal emulator’s job entirely.

The kernel mechanism connecting them: the pseudo-terminal

A pseudo-terminal (pty) is the kernel-level construct that actually connects a terminal emulator to a shell — the terminal emulator holds one end (the “master”), the shell holds the other (the “slave”), and the kernel relays data between them. This same pty mechanism is what lets SSH, tmux, and screen all provide “a terminal” to a shell without any physical terminal hardware being involved at all.

Why this separation explains a specific class of “terminal is broken” bugs

stty sane
reset

When binary data accidentally gets sent to a terminal (commonly from running cat on a non-text file), the escape sequences interspersed in that binary data can put the terminal emulator into an unexpected display state — garbled colors, invisible typed text, a corrupted cursor. The shell itself is completely fine in this scenario; it’s the terminal emulator’s interpretation of stray escape sequences that’s broken, which is exactly why reset or stty sane (commands that tell the terminal to restore sane defaults) fix it rather than restarting the shell.

How the terminal tells programs what capabilities it has: terminfo

Terminal emulators vary in exactly which escape sequences they understand — the terminfo database describes a given terminal type’s specific capabilities, and the $TERM environment variable tells programs which terminfo entry to consult. A TUI application queries terminfo rather than hardcoding escape sequences, which is what lets the same program display correctly across genuinely different terminal emulators.

Why remote sessions make this distinction concrete

When you SSH into a remote machine, your local terminal emulator keeps rendering and handling input exactly as before — what changes is which shell, running on which machine, is on the other end of the pty relayed over the network connection. The terminal emulator itself never “moves” anywhere; only the process it’s connected to does.

Why this distinction is worth internalizing

Understanding terminal emulator and shell as two separate, cooperating programs — connected through a pty, each with entirely distinct jobs — is what makes sense of otherwise-confusing scenarios: why a “broken terminal” and a “broken shell” require different fixes, why the same shell behaves identically across different terminal emulators, and why tools like tmux can present a shell session that outlives the specific terminal window that first launched it.

Where the escape sequences a terminal emulator interprets actually came from

The specific control codes a modern terminal emulator parses — move the cursor, clear the screen, set a color — trace back to a real piece of 1970s hardware: DEC’s VT100, introduced in August 1978 as the successor to the earlier VT50 and VT52. Its defining choice was adopting the newly emerging ANSI X3.64 standard (itself building on ECMA-48, ratified in 1976) for cursor and display control, rather than the proprietary command language DEC’s own earlier terminals had used. That standardization is exactly why the term “ANSI escape sequence” exists at all, and why it stuck: with over six million VT-series units eventually sold, software vendors converged on targeting VT100-compatible escape sequences rather than writing separate drivers per terminal brand, and that convergence is the direct ancestor of every terminal emulator today still advertising itself as broadly “VT100-compatible” or setting $TERM to something in the xterm or vt100 family.

Modern terminal emulators moved rendering onto the GPU

For decades, terminal emulators rendered text the same way any other windowed application drew 2D graphics — through the operating system’s standard, CPU-involved windowing APIs. A newer generation, including Alacritty (first released in 2017, written in Rust), Kitty, and WezTerm, instead pushes character rendering through a GPU pipeline originally built for real-time game graphics, uploading a glyph atlas to the GPU once and compositing new frames from it far faster than a CPU-bound text renderer manages, particularly when a program dumps a large volume of output at once. The three tools still make different tradeoffs on top of that shared GPU-rendering foundation: Alacritty deliberately stays minimal, expecting a separate multiplexer like tmux for tabs and splits; Kitty builds those features in directly, plus inline image rendering; WezTerm goes further still, bundling its own multiplexing and SSH client alongside a Lua-based configuration language. The underlying lesson is the same across all three, though — a terminal emulator’s job (rendering characters, handling input, interpreting escape sequences) hasn’t fundamentally changed since the VT100; what’s changed is how fast and how richly that same job can now be done.

How a mouse click reaches a text-only interface at all

A terminal is fundamentally a character-and-cursor protocol with no native concept of a pointing device — mouse support is itself just another category of escape sequence, negotiated between the terminal emulator and whatever program happens to be running inside it. When an application enables one of several standardized mouse-reporting modes (the modern, widely supported one is called SGR mode), the terminal emulator starts encoding clicks, drags, and scroll-wheel movement as specially formatted escape sequences written directly into the program’s own input stream — at the protocol level, indistinguishable from someone typing unusual characters very quickly. This is exactly why mouse support in a terminal program is opt-in and per-application rather than universal: an ordinary shell prompt has never requested mouse reporting, so clicking around inside one does nothing beyond whatever text-selection behavior the terminal emulator itself provides independently, while vim or tmux can explicitly request mouse reporting and then parse the resulting escape sequences as clicks on specific rows and columns.

Related:

Sources:

Comments