notes from /dev/null

by Charles Choi 최민수


Take Two: Eshell

30 Jun 2025  Charles Choi

This is a contribution to the Emacs Carnival 2025-06: Take Two collection of posts on Christian Tietze’s blog.

My first take with Eshell many years back did not leave a good impression. My early expectations was that it should act like any other shell, only to be unpleasantly surprised by it. It took a long time for me to warm up to Eshell. Upon reflection, it was because I wasn’t ready for it.

Now Eshell is an inseparable part of my Emacs experience. Paradoxically though, I find little occasion to use Eshell in the same way I’ve used shells in the past. Much of what I used to use the shell for, I do today with Emacs modes instead.

It was not always this way. Like so many users who started Unix computing in the 80’s, I started with the command line (for myself ksh and later bash), studiously internalizing command line tools and their arcane syntaxes to support file management, running programs, and systems administration. Overwhelmingly though, it was file management and running programs that I did through the command line.

For file management these days, Dired is my interface of choice. It is simply the more elegant tool for the job.

Case in point: renaming a bunch of files in a directory to arbitrary names. With a shell, my standard approach would have been to run some variant of ls -1 > foo.sh, edit foo.sh to insert mv on each line to a renamed target file, chmod u+x foo.sh to be executable, run foo.sh, delete foo.sh and call it a day.

With Dired, the above steps are replaced as such: make the buffer writable (via Wdired). Change the target filenames to a desired result, then commit the changes. This is even easier if there is a pattern you can use a regexp for. Unsurprisingly, all the basic file management operations that you would want are supported by Dired. Add Casual Dired to help make these operations discoverable and you’ll soon consider how quaint using mv, cp, rm, and ls are.

Running a simple command on a file? Again, you can do that with Dired. Move the point to the Dired line containing the file. Type “!” and enter a command in the prompt. You can optionally reference the filename in the command with the “?” character if you want to redirect its output to a file (e.g. cmd ? > outfile).

Taking file management and running simple commands out of what you would use a shell for, what’s left over? Source code management (SCM)?

Like for many others, git is my goto tool for SCM. Much has been said about the terrible user experience of the git command line and for years I suffered with its design decisions. Thankfully for Emacs users, there is Magit and VC mode which both provide a saner experience for it.

Running a Makefile? Use Emacs compile, which provides command completion for the targets defined in the Makefile and error/warning navigation if needed. (Try out Casual Make for editing Makefiles in Emacs.)

Do you need to run a command with arcane arguments repeatedly? Again, Makefile + compile to the rescue where it can be used as a task runner for that command.

Need robust terminal emulation? Eshell will fail you here and you are likely better off using a dedicated terminal emulator app, separate from Emacs. That said, I’ve found the built-in term to be good enough for many curses-style utilities.

Mining data files with piped commands? Eshell is a power tool here: used wrongly and you could figuratively cut off a limb. But used cleverly, Eshell offers great value. More on this later.

So with all these use cases described, why even use Eshell?

Using Eshell becomes a win only if you know Elisp. With that, you can think of Eshell as an Elisp REPL that can secondarily act as a shell.

Let’s not mince words. For many users this is a high bar, as it takes into account a lot of prerequisite knowledge. That said, learning basic Elisp is trivial for readers already conversant with programming and manageable for those that aren’t. Personally, I was very late (decades!) in taking the time to learn Elisp because I didn’t see the reward in it and so, prioritized accordingly. That hesitancy turned out to be unfounded. Elisp is easy to learn and the reward (or punishment) for knowing it is having the ability to really use the full capabilities of Emacs. (Know Python? Here’s a cheatsheat for Elisp that I wrote, hop in, the water’s fine…)

Once gained, bringing Elisp knowledge to using Eshell gives you a command line super power: the ability to improvise shell commands with Elisp functions.

Consider the following Eshell example where we wish to apply a command on a set of png files in a directory. Let’s start with a simple example that simply echoes filenames with suffix .png.

$ for i in *.png { echo $i }

In Eshell, you can replace echo $i with an Elisp expression.

$ for i in *.png { (format "%s" i) }

You can also mix shell commands and Elisp via dollar ($) expansion. Note that $ expansion in Eshell is different from other shells. A deeper reading is on it is recommended to avoid confusion with what works in conventional shells.

$ for i in *.png { echo $(format "%s" i) }

If you have ImageMagick, installed, you can use the convert utility to change this to a jpeg file, using the Elisp file name components functions to do the work of getting a desired target name.

$ for i in *.png { convert $i $(file-name-with-extension i "jpg") }

Contrast this with the equivalent expression in Bash which I consider to have much more forgettable syntax.

$ for i in *.png; do convert $i ${i/.png/.jpg}; done

Another significance with mixing shell commands and Elisp together is the ability to incorporate Emacs buffers into an improvised workflow. Shell commands are typically designed to work with data organized as files. In contrast, Elisp (rather, Emacs) treats files as a persisted, secondary form of data. Instead, Buffers are the primary data type in Emacs.

Recall the Dired naming example above? Consider the added steps due to using a temporary file foo.sh compared to using a writeable Dired buffer.

Eshell shifts you into thinking differently about working with a prompt, where instead of thinking of output being only a file or (stdout, stderr), the output could also be an Emacs buffer.

With that change in thinking, Eshell is better thought of as a “prompt” interface for Emacs which in turn serves as the interface to all the command line utilities made available to it. In this light, a whole swath of common shell workflows get replaced by Emacs.

Workflow Shell Eshell
File management Orchestrate cd, ls, cp, rm, mv Run dired {optional path}
View a file Run more or less Run view-file
SCM with git Run git command(s) Run magit {optional path}
View man page Run man Run Emacs man
Run Makefile Run make Run make & to send output to a compile buffer
Remote login Run ssh Run Eshell Tramp
Edit a file Run $EDITOR Run find-file
Manage processes Run htop Run proced
Search for pattern in a file Run grep Run Eshell grep to send output to a compile buffer
Locate a file Run locate Run Eshell locate to send output to locate buffer

Suffice to say, Eshell reinforces and rewards keeping Emacs users only in Emacs.

Another common shell workflow is using grep with pipes (|) for data mining files. For example:

$ grep {pat1} {file} | grep -v {pat2} | grep {pat3} >outfile

Using this pattern indiscriminately in Eshell is a recipe for unpleasant surprise if the file to be mined is large. This is because Eshell will ingest said file into Emacs as part of the pipeline processing. This will be slow. To avoid this, you can prefix each | with an * as follows:

$ grep {pat1} {file} *| grep -v {pat2} *| grep {pat3} >outfile

Why store a result in a file (outfile) that you have to open to see it and may not care to keep around? Here Eshell gives you the ability to redirect to a buffer.

$ grep {pat1} {file} *| grep -v {pat2} *| grep {pat3} >#<buffer "*my grep results*">

Note that Eshell supports creating/overwriting (>) and appending (>>) for redirection. Use > with care when working with an existing buffer.

What about piping shell command output to an Elisp function? At the time of this writing, you really can’t though according to this Reddit thread, this may change in the future.

If you are used to workflows that bleed out megabytes of data to stdout, you really shouldn’t use Eshell. But with some cleverness, Eshell can help you get insights to your data faster by leveraging all the tools Emacs can offer for this (occur, highlighting, grep, etc.).

Closing Thoughts

IMHO, Eshell is best thought primarily as a prompt for Elisp/Emacs functionality with the secondary benefit of running command line utilities with a “shell-like” experience. It should not be thought of as a “drop-in” replacement for an actual terminal shell. If full terminal emulation is desired, my guidance is to run a dedicated terminal emulator outside of Emacs.

To get the full benefit of using Eshell, you really need to be conversant with Elisp. That said, Eshell is actually a great way to start learning Elisp as it provides you a prompt interface (REPL) in much the same way that other languages like Python, JavaScript, Ruby, Java, Swift, etc do.1 Especially if you are comfortable with programming, take a day to learn Elisp. You won’t regret it.

Bringing Eshell into my Emacs journey has been amazing and I’m happy to have given it a second take.

Thanks to all the contributors of Eshell.

Footnotes

1 To clarify, these languages adopted the idea of a REPL from Lisp development which Elisp is descended from.

emacs

 

AboutMastodonBlueskyGitHub

Feeds & Tags
Get Scrim for macOSGet Captee for macOS

Powered by Pelican