Wednesday, August 21, 2013

Finally wrapping my head around eshell (the emacs shell)

For years, I'd see eshell (an emacs command shell) come across my radar, and being an emacs guy, I'd naturally try it out. And within a few minutes, I'd be back to my bash and screen ways. It recently popped up again (and for the life of me, I can't recall where) and I decided I'd take a closer look. I realized while perusing the manual that I'd had a key misconceptions about eshell: I assumed it was more complicated than it is.

Perhaps it was the important sounding name, but I just assumed that learning eshell was going to be like learning calc or gnus. Sure, these are both is an amazing tools, but they've got so much you can learn, you might feel like you've never mastered it all.

eshell it turns out isn't particularly complicated at all. In fact, after playing with it for a couple of days, I've come to realize it has one especially cool feature: you can enter regular unix-like commands (eg. ls -l) *and* you can enter in elisp functions (find-file foo.php). eshell does exactly what you'd expect it to do, if it can't find the command as an alias, or executable on disk, it tries to execute it as a lisp function. While this may be clever, it doesn't seem especially useful at first. For example, you can type the following:

  substring "hello world" 3  9

This is converted to (substring "hello world" 3 9), which is novel, but not something you typically need to do at a command line.

What gets especially interesting is when you type this:

  find-file index.html

This invokes (find-file "index.html") which opens up the current file. In other words, you've entered a shell command and seamlessly jumped into emacs world. Here are some more examples:

  dired .             # kick off M-x dired on the current directory
  rgrep TODO "*.html" # kick off M-x rgrep on all HTML files below the current directory
  svn-update ${pwd}   # kick off M-x svn-update on the current working directory

Notice in the last case the use of ${...} notation. This notation actually invokes a sub-eshell so you can put in arbitrary commands or elisp within ${...}.

With this elisp integration, I'm truly starting to see the light: eshell isn't about creating a shell experience that's better than say bash + screen, it's about giving you a new way to interact with emacs. Rather than thinking "OK, I'm going to do some unixy command line stuff, I better switch to an xterm" you can think "hey, I bet half of this could be done in command-line-land and half could be done in emacs, and eshell gives me a seamless way to do that."

Thanks to eshell, my fingers are finding new and faster ways, to get stuff done. And we all know, that's what emacs is all about.

Definitely go off and read Mastering Eshell, and learn about the other cool features of eshell. But don't do what I did, and psych yourself out thinking there's going to be a huge learning curve.


  1. One really nice thing about eshell is its tramp integration, eg.

    cd /

    The built-in commands like cp and diff can use tramp paths too, so I can do:

    cp -r /ssh:root@staging:/var/www/site /ssh:root@live:/var/www
    diff /home/chris/devsite/abc.php /ssh:root@live:/var/www/site/abc.php

    It's become second nature for me to do this, so it's a bit jarring when I try a native (not eshell) command and it gets confused by the paths :(

    Three gripes I have with eshell:
    * It can't do pipes, which again is a bit jarring since it breaks the illusion that I'm in a shell
    * It doesn't handle interactive commands well at all. By buffering programs' output (which hides prompts) and not passing through stdin, a program waiting for input will often look like it's hung. There are specific workarounds in place, for example sudo, but even these can be hit-and-miss. I've taken to writing shell scripts with the pattern "gksudo true; sudo blah", which uses gksudo's password prompt rather than eshells (but still uses sudo, so the output will appear in the terminal).
    * Very minor, but eshell's "ls" doesn't accept the "-A" option (show hidden, excluding "." and ".."), which I often use out of habit.

    I also have some eshell tweaks in my init.el, eg. I have a custom command to launch eshells which puts a counter in the buffer name, so I always get a new shell rather than conflicting with the default "*eshell*". I've also made eshell's tab-completion stop at the first ambiguity, more like bash:

  2. > One really nice thing about eshell is its tramp integration, eg.

    I keep meaning to start using tramp. Back in the day (oh, about 15 years ago) I used ange-ftp, but that's ancient history now.

    > since it breaks the illusion that I'm in a shell

    Well said. eshell is just that - a shell like illusion. Turns out, I think it can be useful, too.

    > also have some eshell tweaks in my init.el,

    Ooh, thanks for sharing. Especially the bash like completion, I'm sure the default behavior is going to get on my nerves.

  3. Agreed, thanks for the bash-like completion, that's handy.

    Note that you don't need a custom command to create a new eshell instance, `C-u M-x eshell` does what you want.