Five Ways to Run a Shell in Emacs (And When to Use Each One)

2024/07/20

BUILDemacsproductivity🍞

Emacs has at least five different ways to run a shell. This is either a sign of deep design richness or a sign that the Emacs community can never agree on anything. Probably both.

The five: shell-mode, term / ansi-term, eshell, vterm, and eat. They all put a command line in an Emacs buffer. They are not interchangeable. They make different tradeoffs along the same axis: how much of the terminal do you emulate, and how much of Emacs do you keep?

Nobody explains when to use which. The Emacs Wiki has separate pages for each one, written by different people, in different decades. So here’s the unified comparison I wish I’d found six months ago.

The spectrum

Think of it as a slider between two extremes:

Full Emacs ◄──────────────────────────────► Full Terminal
  eshell    shell-mode    eat    vterm    term/ansi-term

On the left: everything is an Emacs buffer. You get isearch, evil-mode, yank rings, the whole editor. But programs that expect a real terminal (curses, colors, htop) will break.

On the right: you’re running a real terminal emulator inside Emacs. Programs work perfectly. But your Emacs muscle memory stops working because the terminal is intercepting your keybindings.

Every option is a different point on this slider.

shell-mode: the classic

M-x shell. Runs your login shell (bash, zsh, fish) in an Emacs buffer. The oldest option. Been around since the 1980s.

How it works: Emacs spawns your shell as a subprocess and connects its stdin/stdout to a buffer. The buffer is a regular Emacs buffer — you can move the cursor anywhere, select text, use isearch, copy with normal Emacs commands. When you press Enter on the last line, that line gets sent to the shell.

What’s great:

What breaks:

Use it for: Quick one-off commands. Piping output you want to edit. Any workflow where you type a command, read text output, type another command. Basically: if your workflow would work over a teletype, shell-mode handles it beautifully.

term / ansi-term: the real terminal

M-x term or M-x ansi-term. These are actual terminal emulators written in Elisp. ansi-term is term with slightly better defaults (dedicated buffer per shell, handles ANSI colors).

How it works: Emacs emulates a VT100-compatible terminal. The buffer isn’t a normal text buffer — it’s a screen that the shell draws on. htop works. vim works. less works. Colors, cursor movement, alternate screen buffer — all emulated.

The two modes:

This mode-switching is the fundamental tension. In char mode, you have a working terminal but no Emacs. In line mode, you have Emacs but the terminal is frozen. You spend your life toggling.

What’s great:

What breaks:

Use it for: When you need a real terminal inside Emacs and can’t install external packages. SSH sessions. One-off TUI usage. But honestly, if you’re reaching for term, you probably want vterm instead.

eshell: the Emacs-native shell

M-x eshell. This one is different from everything else on this list. Eshell is not a terminal emulator. It’s not wrapping bash. It’s a shell written entirely in Elisp, running inside Emacs.

How it works: Eshell implements its own command interpreter. It understands basic shell syntax (|, >, &&, ||), has its own built-in commands (ls, cat, cp, grep), and falls back to external programs for anything it doesn’t recognize. But the key insight: it’s Elisp all the way down.

What this means in practice:

~ $ echo (+ 1 2)
3

~ $ echo (buffer-name)
*eshell*

~ $ for f in *.org { echo (file-name-base f) }

~ $ ls | grep .el > #<buffer scratch>

That last line pipes ls output through grep and redirects it into an Emacs buffer called scratch. Not a file. A buffer. You can pipe to Elisp functions. You can use Elisp expressions inline. The shell and the editor are the same thing.

What’s great:

What breaks:

Use it for: Quick project commands (make, git status, pytest). Workflows that benefit from Elisp integration (piping to buffers, scripting with Emacs functions). Your “I never leave Emacs” daily driver — as long as you don’t need full-screen TUI programs.

vterm: the fast one

M-x vterm. Based on libvterm, a C library for terminal emulation. This is what you install when term is too slow and you want a real terminal that doesn’t make you leave Emacs.

How it works: The actual terminal emulation happens in compiled C code (libvterm), not in Elisp. Emacs renders the result. This means the terminal is fast — as fast as a standalone terminal emulator for most workloads.

What’s great:

What breaks:

Use it for: Your primary terminal when you want to live in Emacs. Daily driving. Running any program that needs a real terminal. The “I replaced iTerm2” option.

eat: the new contender

M-x eat. “Emulate A Terminal.” The newest entry, aiming to be vterm without the compilation requirement.

How it works: Pure Elisp terminal emulation, like term, but written from scratch with modern Emacs features and performance tricks. It uses text properties and overlays more efficiently than term ever did.

What’s great:

What breaks:

Use it for: When you want vterm-like capability without the compilation step. When you want to augment eshell with terminal emulation. When you’re on a machine where installing libvterm isn’t practical.

The comparison table

shell-mode term/ansi-term eshell vterm eat
Terminal emulation None Full (Elisp) None Full (C) Full (Elisp)
Speed Fast (no emulation) Slow Medium Fast Good
Full-screen TUI No Yes No (delegates) Yes Yes
Emacs editing Full Line mode only Full Copy mode Semi-char mode
Elisp integration Low None Deep Medium (via escape codes) Low
Bash compatibility Full (runs bash) Full (runs bash) Partial (own shell) Full (runs bash) Full (runs bash)
Built-in Yes Yes Yes No (needs libvterm) No (MELPA)
Compilation needed No No No Yes No
Cross-platform Yes Yes Yes Mostly (needs C build) Yes
TRUECOLOR No Partial No Yes Yes
Directory tracking Fragile Fragile Native Good (with config) Good

What I actually use

Here’s my honest setup:

Eshell for 80% of my terminal needs. git status, make, pytest, quick file operations. Anything where I type a command and read text output. The Elisp integration is addictive once you get used to it — piping to buffers, using Emacs functions inline, the Tramp transparency for remote work. I have eshell bound to C-c t and it’s the first thing I hit.

Vterm for the other 20%. When I need a real terminal — htop to check a process, an ssh session with tmux, running a Docker container interactively, anything that needs a TUI. I keep vterm-toggle set up so I can pop it in and out with a single keybinding.

I don’t use term or ansi-term at all. Vterm is strictly better. I don’t use shell-mode because eshell covers that niche with more power. I haven’t fully committed to eat yet, but the eshell integration is compelling — being able to htop inside eshell without being kicked to a separate term buffer would simplify things.

The case against iTerm2

I used iTerm2 for years. It’s excellent. Wezterm is excellent. Kitty, Alacritty — all excellent. So why would you run a terminal inside a text editor?

Context switching is the real cost. Not the seconds lost Alt-Tabbing. The mental context switch. You’re reading code in Emacs, you need to run a test, you switch to iTerm, you run the test, it fails, you need to look at line 47, you switch back to Emacs, you find the file, you fix it, you switch to iTerm, you run the test again. Each switch is a small interruption to your train of thought.

With an Emacs terminal: you’re reading code. C-c t, type the test command, it fails, the error mentions foo.py:47, you click it (or C-x C-f from eshell with Tramp awareness), you’re in the file at line 47. Fix. Switch back. Run again. Your eyes never left Emacs. Your hands never left the keyboard.

Output is text in buffers. In iTerm2, command output scrolls by and is gone (yes, you can scroll back, search, copy — but it’s the terminal’s scroll buffer, not a file). In Emacs, output lands in a buffer. You can isearch it. Narrow it. Save it to a file. Pipe it to another command. Run a macro over it. The output is a first-class citizen in your editor.

One window manager. Emacs already manages windows and buffers. Adding a terminal means your code, your shell, your git interface (magit), your file manager (dired), your notes (org-mode) — everything lives in one window manager. You split with C-x 3, not with iTerm’s split pane hotkeys. You navigate with C-x o or ace-window, not Cmd-Tab.

The integration points multiply. Magit is already there. Compile mode already parses error output and jumps to source lines. Projectile already knows your project root. When your terminal is also in Emacs, these integration points connect:

compile error → click → source file → fix → recompile → pass

All without leaving the editor. All with the same keybindings. All in buffers you can search, save, and manipulate.

When iTerm2 still wins

Emacs-as-terminal is not always the right call. Be honest about the tradeoffs:

The honest answer: I keep iTerm2 installed. I use it maybe twice a week. But it’s there for the 2% of cases where a standalone terminal is genuinely better.

Getting started: the minimum viable setup

If you want to try this, here’s the low-investment version. Don’t configure everything. Just add two keybindings:

;; Quick eshell toggle
(global-set-key (kbd "C-c t") #'eshell)

;; If you install vterm:
(use-package vterm
  :ensure t
  :bind ("C-c T" . vterm-toggle))

Use C-c t for eshell when you want to type commands and read output. Use C-c T for vterm when you need a real terminal. That’s it. That’s the whole system.

After a week, you’ll know which one you reach for more. Then you can customize.

If you want eat’s eshell integration (so full-screen programs work inside eshell):

(use-package eat
  :ensure t
  :hook (eshell-load . eat-eshell-mode))

Now eshell handles everything — regular commands natively, full-screen programs via eat’s terminal emulation. One shell to rule them all.

Live in it for a month. If you find yourself Alt-Tabbing to iTerm less and less, Emacs already won.