My default shell is bash run with the M-x shell in Emacs. I’ve found it extremely useful and elegant, and after some trial-and-error quite usable as well. In this post I’ll present my configuration in detail.

Background

I used to be one of those people who wondered why on earth anyone in their right mind would want to run shell under Emacs. Then one post in Reddit changed my perspective. The post is specific to eshell, but many concepts apply to any shell under Emacs, for example the bash I’m using.

I realized that what I’m actually doing with shell quite often is this: I run programs that produce textual output, and then I use all sorts of pagers and filtering, and try to search for results I’m after, and then often copy the output to Emacs. All that could be done by Emacs itself, in much more efficient and consistent manner. So why not just run the shell in Emacs buffer, and let the output flow to the buffer, which I can then search and manipulate freely with the Emacs?

I started with ansi-term which is quite impressive in its own right, but soon found myself switching back and forth in line -and char-mode. I did some experiments with shell, but I was still depending on quite many curses programs that simply broke without proper terminal emulation. Since then my setup has been settled down to using shell as my main shell, and then I have a copy of real non-emacs terminal around for few things that are not working in the shell, namely couple of long-running tasks inside screen sessions. I also still use ansi-term for ssh, maybe more on that in some later post.

What is also really great with the shell integration is the fact that bash is under total control of Emacs.

As a practical example: one day I was editing a set of Beamer slides, but for a certain reason Emacs was not able to export to PDF directly. So I exported to latex, and then jumped to another window where I had a shell, and typed in my latex->pdf command, and then jumped back. On another frame I had a PDF-viewer that reflected the change. I found myself doing this for a few times, and thought it’s time to define a keyboard macro:

<F3>          Start macro
C-c C-e       Export
l b           Latex/Beamer
C-x o         Jump to other window
M-p <enter>   Execute previous command
C-x o         Jump back to my document
<F4>          Stop recording

After this, I could just generate the slides with a single button <F4>.

I know this is silly example because I should have fixed the PDF export, but still I think it represents the benefits of shell integration. It’s hard to think how to automate this sort of thing if the shell is run by window manager as external program, like gnome-terminal.

Now let’s dive in to the actual configurations.

bash-completion

One of the first problems in running the shell was its lack of Unix program’s argument completion. Simple example: if I don’t remember what was the name of some argument in some specific program, let’s say openssl, in the normal shell I could just hit the tab to find out some candidates. Shell under Emacs didn’t have that sort of knowledge by default, but this was solved with the bash-completion package in ELPA. I have this in my .emacs:

(require 'bash-completion)
(bash-completion-setup)

Now I can just hit the tab as I would in any other terminal, and I get the candidates I can then select with helm.

bash configuration

I have this in my .bashrc to deal with some curses-based application that my come out of my muscle memory:

vi ()
{
    if [ $TERM = "dumb" ]; then
	emacsclient "$@";
    else
	/usr/bin/vi "$@"
    fi
}
git ()
{
    if [ $TERM = "dumb" ]; then
	emacsclient -e "(magit-status)";
    else
	/usr/bin/git "$@"
    fi
}
man ()
{
    if [ $TERM = "dumb" ]; then
	emacsclient -e "(man \"$1\")";
    else
	/bin/man "$@"
    fi
}
top ()
{
    if [ $TERM = "dumb" ]; then
	emacsclient -e "(helm-top)";
    else
	/usr/bin/top "$@"
    fi
}
less ()
{
    if [ $TERM = "dumb" ]; then
	emacs-pager "$@"
    else
	/bin/less "$@"
    fi
}
ssh ()
{
    if [ $TERM = "dumb" ]; then
	echo "Makes no sense to run ssh in emacs shell!"
    else
	/usr/bin/ssh "$@"
    fi
}

emacs-pager is a simple shell script:

#!/bin/bash
INPUT="-"
if [ $# -ge 1 ]; then
    INPUT="$1"
fi
t=$(mktemp) || exit 1
cat $INPUT >> $t
emacsclient "$t"
rm -f -- $t

I use it sometimes when I’m expecting some really long output, or when I want the output verbatim, not affected by the “long lines” problem, and its solution.

Some Emacs utilities are useful on any terminal:

alias magit='emacsclient -e "(magit-status)"'
alias dired='emacsclient -e "(dired \"`pwd`\")"'

Problematic long lines

Emacs comint is not really good in dealing with longs lines in the shell output. Try cat for example some huge ass minified JavaScript… To my knowledge this problem has no perfect solution, but as a work-around I use this snippet in my .emacs:

;; Shorten lines in comint mode
(defun my-comint-shorten-long-lines (text)
  (let* ((regexp "\\(.\\{300\\}[;,:\({ ]\\)")
         (shortened-text (replace-regexp-in-string regexp "\\1\n" text)))
    (if (string= shortened-text text)
        text
      shortened-text)))
(add-hook 'comint-preoutput-filter-functions 'my-comint-shorten-long-lines)

Original source here. I just modified the regexp to cover some extra characters and allow longer lines.

Finally, running the shell

I always have several shells open, in multiple Emacs frames. I’ve found very handy to define this global shortcut in .emacs:

;; Go to shell, or:
;; - If this buffer is already shell, spawn new
;; - If there's no shell, create one
(defun goto-shell ()
  (interactive)
  (if (string= (buffer-name) "*shell*")
      (shell (generate-new-buffer-name "*shell*"))
    (if (get-buffer "*shell*")
	(switch-to-buffer "*shell*")
      (shell))))
;; Bind to key
(global-set-key (kbd "<f2>") 'goto-shell)

With this, I can always hit <F2> and get my shell, or get a new shell, if necessary.

If I want to go to a specific shell (instead of the first), I use helm and filter out shell instances *shell.