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:
- Full Emacs editing on command output. You just ran
git logand want to search for a commit hash?C-sworks. Want to copy three lines from the middle? Normal region selection. - History navigation with
M-p/M-n(comint-mode history). - Completions integrate with Emacs completion framework.
- Zero setup. It’s built in. Always works.
What breaks:
- No terminal emulation. Anything that uses ANSI escape codes for cursor movement, color, or screen clearing will vomit raw escape sequences into your buffer.
htop? No.less? Barely.vim? Absolutely not. - Prompts can get confused. Multi-line prompts, right-aligned segments (like Starship or Powerlevel10k), anything fancy — it’s going to look wrong.
- Password prompts sometimes don’t hide input properly (there’s
comint-password-prompt-regexp, but it’s fragile).
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:
- Char mode (default): Every keystroke goes to the terminal. Your Emacs keybindings are gone.
C-xdoesn’t switch buffers, it sends Ctrl-X to the shell. You’re trapped. - Line mode (
C-c C-j): The buffer becomes editable like a normal Emacs buffer. You get your keybindings back, but the terminal stops updating. Toggle back withC-c C-k.
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:
- Runs anything. Full-screen TUI programs, ncurses apps,
sshsessions with remote interactive programs. If it works in xterm, it works here. - Ships with Emacs. No external dependencies.
What breaks:
- Speed. The terminal emulation is written in Elisp. It’s slow. Scroll a lot of output and you’ll feel it.
cata large file and watch Emacs freeze. - Keybinding hell. Char mode eats all your keybindings. The escape hatch is
C-c(which becomes the prefix for Emacs commands in char mode), but nothing about this is intuitive. - Buffer management. Each
ansi-terminvocation creates a new buffer. No multiplexing, no tabs. You end up with*ansi-term*,*ansi-term*<2>,*ansi-term*<3>.
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:
- Elisp integration. Call any Emacs function from the command line. Pipe output to buffers. Use Elisp expressions as arguments. This is the killer feature — no other terminal option gives you this.
- Cross-platform. Eshell’s built-in commands work on Windows, macOS, Linux. Same
ls, samecat, everywhere. No cygwin required. - Plan 9 influences. Eshell borrowed ideas from Plan 9’s
rcshell. Redirects to multiple targets, virtual devices (/dev/clipfor clipboard), process substitution that feels more natural than bash. - Tramp integration.
cd /ssh:host:/pathand you’re editing remote files. Commands run remotely. It’s transparent. - Full Emacs buffer. The output is text in a buffer. Search it, edit it, save it, narrow it.
What breaks:
- Not bash-compatible. Complex bash scripts won’t run. Brace expansion works differently.
$()subshell syntax is different. If you’re pasting Stack Overflow one-liners, half of them won’t work. - No terminal emulation. Same as
shell-mode—htop,vim, anything full-screen is out. Eshell detects this and can launchtermfor those commands viaeshell-visual-commands, but it’s a workaround. - Performance. The built-in
lsis slow on large directories. The built-ingrepis slow on large codebases. You can alias them to the real binaries (alias ls 'ls --color=auto $*'), but then you lose the Elisp integration for those commands. - Quirky completion. Eshell has its own completion system that doesn’t always play well with
corfuorcompany. It’s gotten better withpcompleteintegration, but it’s still not as smooth as a real shell withfzf-taborzsh-autosuggestions.
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:
- Speed. Night and day compared to
term. Scrolling, large output, color rendering — all fast because the heavy lifting is in C. - Full terminal emulation.
htop,vim,tmux,ssh— everything works. TRUECOLOR support. Mouse support. Alternate screen buffer. - Emacs integration preserved. Unlike
term, vterm has a well-designed way to send messages back to Emacs. You can set up shell-side functions that call Emacs commands (directory tracking, buffer switching, etc.) via escape sequences. - Proper directory tracking. With the right shell config, Emacs always knows your
$PWD.find-fileopens relative to the terminal’s current directory. - Toggle mode.
vterm-togglelets you pop the terminal in and out like a dropdown (Quake-style), which is genuinely useful.
What breaks:
- Requires compilation.
libvtermis a C library. You needcmake, a C compiler, andlibvterm-devon your system. On macOS with Homebrew it’s fine (brew install libvterm cmake). On locked-down corporate machines, good luck. - Not built in. It’s a third-party package. You install it from MELPA.
- Keybinding negotiation. Vterm has a passthrough mode where keys go to the terminal, and Emacs keybindings need explicit configuration to work. It’s better than
term’s char/line mode split, but you still have to set upvterm-keymap-exceptionsto whitelist your Emacs bindings. - Buffer is not normal text. You can’t freely edit vterm output the way you can in
shell-modeoreshell. The buffer is owned by the terminal emulator. To copy text, you use vterm’s copy mode.
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:
- No compilation. Pure Elisp. Install from MELPA, done. No cmake, no C compiler, no library dependencies.
- Surprisingly fast. Not as fast as vterm (C will always beat Elisp for raw throughput), but dramatically faster than
term. For typical interactive use — notcating gigabyte files — you won’t notice the difference. - Multiple modes. Semi-char mode lets some keys through to Emacs while sending most to the terminal. More granular than term’s binary char/line toggle.
- Eshell integration.
eat-eshell-modeadds terminal emulation to eshell, letting you run full-screen programs inside eshell without theeshell-visual-commandshack. Best of both worlds. - Actively maintained. The project is under active development with a responsive maintainer. New features and fixes land regularly.
What breaks:
- Young. Edge cases exist. Some programs render slightly differently than in a real terminal. These get fixed quickly, but you’re more likely to hit a bug than with vterm.
- Not yet in Emacs core. Third-party package, though it’s a candidate for eventual inclusion.
- Elisp speed ceiling. For extremely output-heavy workloads (compiling a large C project, tailing a high-throughput log), vterm’s C backend will still be faster.
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:
- GPU rendering. iTerm2, Wezterm, Kitty — they render with the GPU. Emacs renders with… Emacs. For raw scroll speed on massive output, a dedicated terminal is faster even compared to vterm.
- Tabs and profiles. iTerm2’s profile system (different colors, fonts, shells per profile) is more mature than anything Emacs offers out of the box.
- Mouse-heavy workflows. If you’re clicking links in terminal output, selecting text with mouse drag, using terminal splits with mouse resize — a real terminal emulator is designed for this. Emacs can do it, but it’s not the primary interaction model.
- Team pair programming. Screen-sharing with someone who doesn’t use Emacs, and you need to show terminal output? A visible standalone terminal is clearer than “let me switch to my eshell buffer.”
- Multiple isolated sessions. If you routinely have 8+ terminal tabs for different tasks, iTerm2’s tab management is cleaner. Emacs can handle it (especially with
project-eshell), but dedicated terminal emulators have had decades to optimize this UX. - When you just need to type one command. Spotlight, Terminal, command, close. Sometimes the overhead of “make sure Emacs is running” isn’t worth it.
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.